1 /*
2 * Copyright (c) 2021-2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #define HST_LOG_TAG "Ffmpeg_Au_Encoder"
17
18 #include "audio_ffmpeg_encoder_plugin.h"
19 #include <cstring>
20 #include <map>
21 #include <set>
22 #include "ffmpeg_au_enc_config.h"
23 #include "plugin/common/plugin_caps_builder.h"
24
25 namespace {
26 // register plugins
27 using namespace OHOS::Media::Plugin;
28 using namespace Ffmpeg;
29 void UpdatePluginDefinition(const AVCodec* codec, CodecPluginDef& definition);
30
31 std::map<std::string, std::shared_ptr<const AVCodec>> codecMap;
32 const size_t BUFFER_QUEUE_SIZE = 6;
33 std::set<AVCodecID> g_supportedCodec = {AV_CODEC_ID_AAC, AV_CODEC_ID_AAC_LATM};
34
AuFfmpegEncoderCreator(const std::string & name)35 std::shared_ptr<CodecPlugin> AuFfmpegEncoderCreator(const std::string& name)
36 {
37 return std::make_shared<AudioFfmpegEncoderPlugin>(name);
38 }
39
RegisterAudioEncoderPlugins(const std::shared_ptr<Register> & reg)40 Status RegisterAudioEncoderPlugins(const std::shared_ptr<Register>& reg)
41 {
42 const AVCodec* codec = nullptr;
43 void* ite = nullptr;
44 MEDIA_LOG_I("registering audio encoders");
45 while ((codec = av_codec_iterate(&ite))) {
46 if (!av_codec_is_encoder(codec) || codec->type != AVMEDIA_TYPE_AUDIO) {
47 continue;
48 }
49 if (g_supportedCodec.find(codec->id) == g_supportedCodec.end()) {
50 MEDIA_LOG_DD("codec " PUBLIC_LOG_S "(" PUBLIC_LOG_S ") is not supported right now",
51 codec->name, codec->long_name);
52 continue;
53 }
54 CodecPluginDef definition;
55 definition.name = "ffmpegAuEnc_" + std::string(codec->name);
56 definition.pluginType = PluginType::AUDIO_ENCODER;
57 definition.rank = 100; // 100
58 definition.creator = AuFfmpegEncoderCreator;
59 UpdatePluginDefinition(codec, definition);
60 // do not delete the codec in the deleter
61 codecMap[definition.name] = std::shared_ptr<AVCodec>(const_cast<AVCodec*>(codec), [](void* ptr) {});
62 if (reg->AddPlugin(definition) != Status::OK) {
63 MEDIA_LOG_W("register plugin " PUBLIC_LOG_S "(" PUBLIC_LOG_S ") failed", codec->name, codec->long_name);
64 }
65 }
66 return Status::OK;
67 }
68
UnRegisterAudioEncoderPlugin()69 void UnRegisterAudioEncoderPlugin()
70 {
71 codecMap.clear();
72 }
73
UpdateInCaps(const AVCodec * codec,CodecPluginDef & definition)74 void UpdateInCaps(const AVCodec* codec, CodecPluginDef& definition)
75 {
76 CapabilityBuilder capBuilder;
77 capBuilder.SetMime(OHOS::Media::MEDIA_MIME_AUDIO_RAW);
78 if (codec->supported_samplerates != nullptr) {
79 DiscreteCapability<uint32_t> values;
80 size_t index = 0;
81 for (; codec->supported_samplerates[index] != 0; ++index) {
82 values.push_back(codec->supported_samplerates[index]);
83 }
84 if (index) {
85 capBuilder.SetAudioSampleRateList(values);
86 }
87 }
88 definition.inCaps.push_back(capBuilder.Build());
89 }
90
UpdateOutCaps(const AVCodec * codec,CodecPluginDef & definition)91 void UpdateOutCaps(const AVCodec* codec, CodecPluginDef& definition)
92 {
93 CapabilityBuilder capBuilder;
94 switch (codec->id) {
95 case AV_CODEC_ID_AAC:
96 capBuilder.SetMime(OHOS::Media::MEDIA_MIME_AUDIO_AAC)
97 .SetAudioMpegVersion(4) // 4
98 .SetAudioAacProfile(AudioAacProfile::LC)
99 .SetAudioAacStreamFormat(AudioAacStreamFormat::MP4ADTS);
100 break;
101 case AV_CODEC_ID_AAC_LATM:
102 capBuilder.SetMime(OHOS::Media::MEDIA_MIME_AUDIO_AAC_LATM)
103 .SetAudioMpegVersion(4) // 4
104 .SetAudioAacStreamFormat(AudioAacStreamFormat::MP4LOAS);
105 break;
106 default:
107 MEDIA_LOG_I("codec is not supported right now");
108 }
109 definition.outCaps.push_back(capBuilder.Build());
110 }
111
UpdatePluginDefinition(const AVCodec * codec,CodecPluginDef & definition)112 void UpdatePluginDefinition(const AVCodec* codec, CodecPluginDef& definition)
113 {
114 UpdateInCaps(codec, definition);
115 UpdateOutCaps(codec, definition);
116 }
117 } // namespace
118 PLUGIN_DEFINITION(FFmpegAudioEncoders, LicenseType::LGPL, RegisterAudioEncoderPlugins, UnRegisterAudioEncoderPlugin);
119
120 namespace OHOS {
121 namespace Media {
122 namespace Plugin {
123 namespace Ffmpeg {
AudioFfmpegEncoderPlugin(std::string name)124 AudioFfmpegEncoderPlugin::AudioFfmpegEncoderPlugin(std::string name) : CodecPlugin(std::move(name)), prevPts_(0)
125 {
126 }
127
~AudioFfmpegEncoderPlugin()128 AudioFfmpegEncoderPlugin::~AudioFfmpegEncoderPlugin()
129 {
130 OSAL::ScopedLock lock(avMutex_);
131 OSAL::ScopedLock lock1(parameterMutex_);
132 DeInitLocked();
133 }
134
Init()135 Status AudioFfmpegEncoderPlugin::Init()
136 {
137 auto ite = codecMap.find(pluginName_);
138 if (ite == codecMap.end()) {
139 MEDIA_LOG_W("cannot find codec with name " PUBLIC_LOG_S, pluginName_.c_str());
140 return Status::ERROR_UNSUPPORTED_FORMAT;
141 }
142 OSAL::ScopedLock lock(avMutex_);
143 avCodec_ = ite->second;
144 cachedFrame_ = std::shared_ptr<AVFrame>(av_frame_alloc(), [](AVFrame* frame) { av_frame_free(&frame);});
145 return Status::OK;
146 }
147
Deinit()148 Status AudioFfmpegEncoderPlugin::Deinit()
149 {
150 OSAL::ScopedLock lock(avMutex_);
151 OSAL::ScopedLock lock1(parameterMutex_);
152 return DeInitLocked();
153 }
154
DeInitLocked()155 Status AudioFfmpegEncoderPlugin::DeInitLocked()
156 {
157 ResetLocked();
158 avCodec_.reset();
159 cachedFrame_.reset();
160 return Status::OK;
161 }
162
SetParameter(Tag tag,const ValueType & value)163 Status AudioFfmpegEncoderPlugin::SetParameter(Tag tag, const ValueType& value)
164 {
165 OSAL::ScopedLock lock(parameterMutex_);
166 audioParameter_.insert(std::make_pair(tag, value));
167 return Status::OK;
168 }
169
GetParameter(Tag tag,ValueType & value)170 Status AudioFfmpegEncoderPlugin::GetParameter(Tag tag, ValueType& value)
171 {
172 if (tag == Tag::REQUIRED_OUT_BUFFER_CNT) {
173 value = static_cast<uint32_t>(BUFFER_QUEUE_SIZE);
174 return Status::OK;
175 }
176 OSAL::ScopedLock lock(avMutex_);
177 if (avCodecContext_ == nullptr) {
178 return Status::ERROR_WRONG_STATE;
179 }
180 return GetAudioEncoderParameters(*avCodecContext_, tag, value);
181 }
182
Prepare()183 Status AudioFfmpegEncoderPlugin::Prepare()
184 {
185 {
186 OSAL::ScopedLock lock(avMutex_);
187 if (avCodec_ == nullptr) {
188 return Status::ERROR_WRONG_STATE;
189 }
190 auto context = avcodec_alloc_context3(avCodec_.get());
191 FALSE_RETURN_V_MSG_E(context != nullptr, Status::ERROR_UNKNOWN, "cannot allocate codec context");
192 avCodecContext_ = std::shared_ptr<AVCodecContext>(context, [](AVCodecContext* ptr) {
193 if (ptr != nullptr) {
194 if (ptr->extradata) {
195 av_free(ptr->extradata);
196 ptr->extradata = nullptr;
197 }
198 avcodec_free_context(&ptr);
199 }
200 });
201 {
202 OSAL::ScopedLock lock1(parameterMutex_);
203 ConfigAudioEncoder(*avCodecContext_, audioParameter_);
204 }
205
206 if (!avCodecContext_->time_base.den) {
207 avCodecContext_->time_base.den = avCodecContext_->sample_rate;
208 avCodecContext_->time_base.num = 1;
209 avCodecContext_->ticks_per_frame = 1;
210 }
211
212 avCodecContext_->workaround_bugs =
213 static_cast<uint32_t>(avCodecContext_->workaround_bugs) | static_cast<uint32_t>(FF_BUG_AUTODETECT);
214 }
215 return Status::OK;
216 }
217
ResetLocked()218 Status AudioFfmpegEncoderPlugin::ResetLocked()
219 {
220 audioParameter_.clear();
221 avCodecContext_.reset();
222 {
223 OSAL::ScopedLock l(bufferMetaMutex_);
224 bufferMeta_.reset();
225 }
226 return Status::OK;
227 }
228
Reset()229 Status AudioFfmpegEncoderPlugin::Reset()
230 {
231 OSAL::ScopedLock lock(avMutex_);
232 OSAL::ScopedLock lock1(parameterMutex_);
233 fullInputFrameSize_ = 0;
234 needReformat_ = false;
235 prevPts_ = 0;
236 srcBytesPerSample_ = 0;
237 return ResetLocked();
238 }
239
CheckReformat()240 bool AudioFfmpegEncoderPlugin::CheckReformat()
241 {
242 if (avCodec_ == nullptr || avCodecContext_ == nullptr) {
243 return false;
244 }
245 for (size_t index = 0; avCodec_->sample_fmts[index] != AV_SAMPLE_FMT_NONE; ++index) {
246 if (avCodec_->sample_fmts[index] == avCodecContext_->sample_fmt) {
247 return false;
248 }
249 }
250 return true;
251 }
252
Start()253 Status AudioFfmpegEncoderPlugin::Start()
254 {
255 OSAL::ScopedLock lock(avMutex_);
256 if (avCodecContext_ == nullptr) {
257 return Status::ERROR_WRONG_STATE;
258 }
259 needReformat_ = CheckReformat();
260 if (needReformat_) {
261 srcFmt_ = avCodecContext_->sample_fmt;
262 // always use the first fmt
263 avCodecContext_->sample_fmt = avCodec_->sample_fmts[0];
264 }
265 auto res = avcodec_open2(avCodecContext_.get(), avCodec_.get(), nullptr);
266 FALSE_RETURN_V_MSG_E(res == 0, Status::ERROR_UNKNOWN, "avcodec open error " PUBLIC_LOG_S " when start encoder",
267 AVStrError(res).c_str());
268 FALSE_RETURN_V_MSG_E(avCodecContext_->frame_size > 0, Status::ERROR_UNKNOWN, "frame_size unknown");
269 fullInputFrameSize_ = (uint32_t)av_samples_get_buffer_size(nullptr, avCodecContext_->channels,
270 avCodecContext_->frame_size, srcFmt_, 1);
271 srcBytesPerSample_ = static_cast<uint32_t>((av_get_bytes_per_sample(srcFmt_) * avCodecContext_->channels));
272 if (needReformat_) {
273 Ffmpeg::ResamplePara resamplePara = {
274 static_cast<uint32_t>(avCodecContext_->channels),
275 static_cast<uint32_t>(avCodecContext_->sample_rate),
276 0,
277 static_cast<int64_t>(avCodecContext_->channel_layout),
278 srcFmt_,
279 static_cast<uint32_t>(avCodecContext_->frame_size),
280 avCodecContext_->sample_fmt,
281 };
282 resample_ = std::make_shared<Ffmpeg::Resample>();
283 FALSE_RETURN_V_MSG(resample_->Init(resamplePara) == Status::OK, Status::ERROR_UNKNOWN, "Resample init error");
284 }
285 SetParameter(Tag::AUDIO_SAMPLE_PER_FRAME, static_cast<uint32_t>(avCodecContext_->frame_size));
286 return Status::OK;
287 }
288
Stop()289 Status AudioFfmpegEncoderPlugin::Stop()
290 {
291 Status ret = Status::OK;
292 {
293 OSAL::ScopedLock lock(avMutex_);
294 if (avCodecContext_ != nullptr) {
295 auto res = avcodec_close(avCodecContext_.get());
296 FALSE_RETURN_V_MSG_E(res == 0, Status::ERROR_UNKNOWN,
297 "avcodec close error " PUBLIC_LOG_S " when stop encoder", AVStrError(res).c_str());
298 avCodecContext_.reset();
299 }
300 if (outBuffer_) {
301 outBuffer_.reset();
302 }
303 }
304 return ret;
305 }
306
Flush()307 Status AudioFfmpegEncoderPlugin::Flush()
308 {
309 MEDIA_LOG_I("Flush entered.");
310 OSAL::ScopedLock lock(avMutex_);
311 if (avCodecContext_ != nullptr) {
312 avcodec_flush_buffers(avCodecContext_.get());
313 }
314 MEDIA_LOG_I("Flush exit.");
315 return Status::OK;
316 }
317
QueueInputBuffer(const std::shared_ptr<Buffer> & inputBuffer,int32_t timeoutMs)318 Status AudioFfmpegEncoderPlugin::QueueInputBuffer(const std::shared_ptr<Buffer>& inputBuffer, int32_t timeoutMs)
319 {
320 MEDIA_LOG_DD("queue input buffer");
321 (void)timeoutMs;
322 if (inputBuffer->IsEmpty() && !(inputBuffer->flag & BUFFER_FLAG_EOS)) {
323 MEDIA_LOG_E("encoder does not support fd buffer");
324 return Status::ERROR_INVALID_DATA;
325 }
326 Status ret = Status::OK;
327 {
328 OSAL::ScopedLock lock(avMutex_);
329 if (avCodecContext_ == nullptr) {
330 return Status::ERROR_WRONG_STATE;
331 }
332 ret = SendBufferLocked(inputBuffer);
333 if (ret == Status::OK || ret == Status::END_OF_STREAM) {
334 OSAL::ScopedLock l(bufferMetaMutex_);
335 bufferMeta_ = inputBuffer->GetBufferMeta()->Clone();
336 }
337 }
338 return ret;
339 }
340
QueueOutputBuffer(const std::shared_ptr<Buffer> & outputBuffer,int32_t timeoutMs)341 Status AudioFfmpegEncoderPlugin::QueueOutputBuffer(const std::shared_ptr<Buffer>& outputBuffer, int32_t timeoutMs)
342 {
343 MEDIA_LOG_DD("queue output buffer");
344 (void)timeoutMs;
345 if (!outputBuffer) {
346 return Status::ERROR_INVALID_PARAMETER;
347 }
348 outBuffer_ = outputBuffer;
349 return SendOutputBuffer();
350 }
351
SendOutputBuffer()352 Status AudioFfmpegEncoderPlugin::SendOutputBuffer()
353 {
354 MEDIA_LOG_DD("send output buffer");
355 Status status = ReceiveBuffer();
356 if (status == Status::OK || status == Status::END_OF_STREAM) {
357 {
358 OSAL::ScopedLock l(bufferMetaMutex_);
359 outBuffer_->UpdateBufferMeta(*bufferMeta_);
360 }
361 dataCallback_->OnOutputBufferDone(outBuffer_);
362 }
363 outBuffer_.reset();
364 return status;
365 }
366
FillInFrameCache(const std::shared_ptr<Memory> & mem)367 void AudioFfmpegEncoderPlugin::FillInFrameCache(const std::shared_ptr<Memory>& mem)
368 {
369 uint8_t* sampleData = nullptr;
370 int32_t nbSamples = 0;
371 auto srcBuffer = mem->GetReadOnlyData();
372 auto destBuffer = const_cast<uint8_t*>(srcBuffer);
373 auto srcLength = mem->GetSize();
374 auto destLength = srcLength;
375 if (needReformat_ && resample_) {
376 FALSE_LOG(resample_->Convert(srcBuffer, srcLength, destBuffer, destLength) == Status::OK);
377 if (destLength) {
378 sampleData = destBuffer;
379 nbSamples = static_cast<int32_t>((static_cast<int32_t>(destLength)
380 / av_get_bytes_per_sample(avCodecContext_->sample_fmt) / avCodecContext_->channels));
381 }
382 } else {
383 sampleData = destBuffer;
384 nbSamples = static_cast<int32_t>((destLength / srcBytesPerSample_));
385 }
386 cachedFrame_->format = avCodecContext_->sample_fmt;
387 cachedFrame_->sample_rate = avCodecContext_->sample_rate;
388 cachedFrame_->channels = avCodecContext_->channels;
389 cachedFrame_->channel_layout = avCodecContext_->channel_layout;
390 cachedFrame_->nb_samples = nbSamples;
391 if (av_sample_fmt_is_planar(avCodecContext_->sample_fmt) && avCodecContext_->channels > 1) {
392 if (avCodecContext_->channels > AV_NUM_DATA_POINTERS) {
393 av_freep(cachedFrame_->extended_data);
394 cachedFrame_->extended_data = static_cast<uint8_t**>(av_malloc_array(avCodecContext_->channels,
395 sizeof(uint8_t *)));
396 } else {
397 cachedFrame_->extended_data = cachedFrame_->data;
398 }
399 cachedFrame_->extended_data[0] = sampleData;
400 cachedFrame_->linesize[0] = nbSamples * av_get_bytes_per_sample(avCodecContext_->sample_fmt);
401 for (int i = 1; i < avCodecContext_->channels; i++) {
402 cachedFrame_->extended_data[i] = cachedFrame_->extended_data[i-1] + cachedFrame_->linesize[0];
403 }
404 } else {
405 cachedFrame_->data[0] = sampleData;
406 cachedFrame_->extended_data = cachedFrame_->data;
407 cachedFrame_->linesize[0] = nbSamples * av_get_bytes_per_sample(avCodecContext_->sample_fmt) *
408 avCodecContext_->channels;
409 }
410 }
411
SendBufferLocked(const std::shared_ptr<Buffer> & inputBuffer)412 Status AudioFfmpegEncoderPlugin::SendBufferLocked(const std::shared_ptr<Buffer>& inputBuffer)
413 {
414 bool eos = false;
415 if (inputBuffer == nullptr || (inputBuffer->flag & BUFFER_FLAG_EOS) != 0) {
416 // eos buffer
417 eos = true;
418 } else {
419 auto inputMemory = inputBuffer->GetMemory();
420 if (inputMemory == nullptr) {
421 MEDIA_LOG_E("SendBufferLocked inputBuffer GetMemory nullptr");
422 return Status::ERROR_UNKNOWN;
423 }
424 FALSE_RETURN_V_MSG_W(inputMemory->GetSize() == fullInputFrameSize_, Status::ERROR_NOT_ENOUGH_DATA,
425 "Not enough data, input: " PUBLIC_LOG_ZU ", fullInputFrameSize: " PUBLIC_LOG_U32,
426 inputMemory->GetSize(), fullInputFrameSize_);
427 FillInFrameCache(inputMemory);
428 }
429 AVFrame* inputFrame = nullptr;
430 if (!eos) {
431 inputFrame = cachedFrame_.get();
432 }
433 auto ret = avcodec_send_frame(avCodecContext_.get(), inputFrame);
434 if (!eos && inputFrame) {
435 av_frame_unref(inputFrame);
436 }
437 if (ret == 0) {
438 return Status::OK;
439 } else if (ret == AVERROR_EOF) {
440 return Status::END_OF_STREAM;
441 } else if (ret == AVERROR(EAGAIN)) {
442 return Status::ERROR_AGAIN;
443 } else {
444 MEDIA_LOG_E("send buffer error " PUBLIC_LOG_S, AVStrError(ret).c_str());
445 return Status::ERROR_UNKNOWN;
446 }
447 }
448
ReceiveFrameSucc(const std::shared_ptr<Buffer> & ioInfo,const std::shared_ptr<AVPacket> & packet)449 Status AudioFfmpegEncoderPlugin::ReceiveFrameSucc(const std::shared_ptr<Buffer>& ioInfo,
450 const std::shared_ptr<AVPacket>& packet)
451 {
452 auto ioInfoMem = ioInfo->GetMemory();
453 if (ioInfoMem == nullptr) {
454 MEDIA_LOG_E("ReceiveFrameSucc ioInfo GetMemory nullptr");
455 return Status::ERROR_UNKNOWN;
456 }
457 FALSE_RETURN_V_MSG_W(ioInfoMem->GetCapacity() >= static_cast<size_t>(packet->size),
458 Status::ERROR_NO_MEMORY, "buffer size is not enough");
459 ioInfoMem->Write(packet->data, packet->size);
460 // how get perfect pts with upstream pts ?
461 ioInfo->duration = ConvertTimeFromFFmpeg(packet->duration, avCodecContext_->time_base);
462 uint64_t res = (UINT64_MAX - prevPts_ < static_cast<uint64_t>(packet->duration)) ?
463 (static_cast<uint64_t>(ioInfo->duration) - (UINT64_MAX - prevPts_)) :
464 (prevPts_ + static_cast<uint64_t>(ioInfo->duration));
465 ioInfo->pts = static_cast<int64_t>(res);
466 prevPts_ = static_cast<uint64_t>(ioInfo->pts);
467 return Status::OK;
468 }
469
ReceiveBufferLocked(const std::shared_ptr<Buffer> & ioInfo)470 Status AudioFfmpegEncoderPlugin::ReceiveBufferLocked(const std::shared_ptr<Buffer>& ioInfo)
471 {
472 Status status;
473 std::shared_ptr<AVPacket> packet = std::make_shared<AVPacket>();
474 auto ret = avcodec_receive_packet(avCodecContext_.get(), packet.get());
475 if (ret >= 0) {
476 MEDIA_LOG_DD("receive one frame");
477 status = ReceiveFrameSucc(ioInfo, packet);
478 } else if (ret == AVERROR_EOF) {
479 MEDIA_LOG_I("eos received");
480 if (ioInfo->GetMemory() == nullptr) {
481 return Status::ERROR_NULL_POINTER;
482 }
483 ioInfo->GetMemory()->Reset();
484 ioInfo->flag = BUFFER_FLAG_EOS;
485 status = Status::END_OF_STREAM;
486 } else if (ret == AVERROR(EAGAIN)) {
487 status = Status::ERROR_NOT_ENOUGH_DATA;
488 } else {
489 MEDIA_LOG_E("audio encoder receive error: " PUBLIC_LOG_S, AVStrError(ret).c_str());
490 status = Status::ERROR_UNKNOWN;
491 }
492 av_frame_unref(cachedFrame_.get());
493 return status;
494 }
495
ReceiveBuffer()496 Status AudioFfmpegEncoderPlugin::ReceiveBuffer()
497 {
498 std::shared_ptr<Buffer> ioInfo = outBuffer_;
499 if ((ioInfo == nullptr) || ioInfo->IsEmpty() ||
500 (ioInfo->GetBufferMeta()->GetType() != BufferMetaType::AUDIO)) {
501 MEDIA_LOG_W("cannot fetch valid buffer to output");
502 return Status::ERROR_NO_MEMORY;
503 }
504 Status status = Status::OK;
505 {
506 OSAL::ScopedLock l(avMutex_);
507 if (avCodecContext_ == nullptr) {
508 return Status::ERROR_WRONG_STATE;
509 }
510 status = ReceiveBufferLocked(ioInfo);
511 }
512 return status;
513 }
514
GetAllocator()515 std::shared_ptr<Allocator> AudioFfmpegEncoderPlugin::GetAllocator()
516 {
517 return nullptr;
518 }
519 } // namespace Ffmpeg
520 } // namespace Plugin
521 } // namespace Media
522 } // namespace OHOS