diff --git a/zh-cn/application-dev/media/audio-decoding.md b/zh-cn/application-dev/media/audio-decoding.md new file mode 100644 index 0000000000000000000000000000000000000000..f5c3041218e49e5ff33cbc7a2b941d633088f49c --- /dev/null +++ b/zh-cn/application-dev/media/audio-decoding.md @@ -0,0 +1,285 @@ +# 音频解码 + +开发者可以调用本模块的Native API接口,完成音频解码,即将媒体数据解码为PCM码流。 + +当前支持的解码能力如下: + +| 容器规格 | 音频解码类型 | +| -------- | :--------------------------- | +| mp4 | AAC、MPEG(MP3)、Flac、Vorbis | +| m4a | AAC | +| flac | Flac | +| ogg | Vorbis | +| aac | AAC | +| mp3 | MPEG(MP3) | + +**适用场景** + +- 音频播放 + + 在播放音频之前,需要先解码音频,再将数据输送到硬件扬声器播放。 +- 音频渲染 + + 在对音频文件进行音效处理之前,需要先解码再由音频处理模块进行音频渲染。 +- 音频编辑 + + 音频编辑(如调整单个声道的播放倍速等)需要基于PCM码流进行,所以需要先将音频文件解码。 + +## 开发步骤 + +详细的API说明请参考[API文档](../reference/native-apis/_audio_decoder.md)。 +参考以下示例代码,完成音频解码的全流程,包括:创建解码器,设置解码参数(采样率/码率/声道数等),开始,刷新,重置,销毁资源。 + +在应用开发过程中,开发者应按一定顺序调用方法,执行对应操作,否则系统可能会抛出异常或生成其他未定义的行为。具体顺序可参考下列开发步骤及对应说明。 +完整代码请参考[示例程序](https://gitee.com/openharmony/multimedia_av_codec/blob/master/test/nativedemo/audio_demo/avcodec_audio_decoder_demo.cpp) + +1. 创建解码器实例对象 + +```cpp +//通过 codecname 创建解码器 +OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_AUDIO_MPEG, false); +const char *name = OH_AVCapability_GetName(capability); +OH_AVCodec *audioDec = OH_AudioDecoder_CreateByName(name); +``` + +```cpp +//通过 Mimetype 创建解码器 +OH_AVCodec *audioDec = OH_AudioDecoder_CreateByMime(OH_AVCODEC_MIMETYPE_AUDIO_MPEG); +``` + +```cpp +// 初始化队列 +class ADecSignal { +public: + std::mutex inMutex_; + std::mutex outMutex_; + std::mutex startMutex_; + std::condition_variable inCond_; + std::condition_variable outCond_; + std::condition_variable startCond_; + std::queue inQueue_; + std::queue outQueue_; + std::queue inBufferQueue_; + std::queue outBufferQueue_; + std::queue attrQueue_; +}; +ADecSignal *signal_; +``` + +2. 调用OH_AudioDecoder_SetCallback()设置回调函数。 + 注册回调函数指针集合OH_AVCodecAsyncCallback,包括: + - 解码器运行错误 + - 码流信息变化,如声道变化等。 + - 运行过程中需要新的输入数据,即解码器已准备好,可以输入数据。 + - 运行过程中产生了新的输出数据,即解码完成。 + + 开发者可以通过处理该回调报告的信息,确保解码器正常运转。 + + ```cpp + // 设置 OnError 回调函数 + static void OnError(OH_AVCodec *codec, int32_t errorCode, void *userData) + { + (void)codec; + (void)errorCode; + (void)userData; + } + // 设置 FormatChange 回调函数 + static void OnOutputFormatChanged(OH_AVCodec *codec, OH_AVFormat *format, void*userData) + { + (void)codec; + (void)format; + (void)userData; + } + // 解码输入码流送入InputBuffer 队列 + static void OnInputBufferAvailable(OH_AVCodec *codec, uint32_t index, OH_AVMemory*data, void *userData) + { + (void)codec; + ADecSignal *signal = static_cast(userData); + unique_lock lock(signal->inMutex_); + signal->inQueue_.push(index); + signal->inBufferQueue_.push(data); + signal->inCond_.notify_all(); + // 解码输入码流送入InputBuffer 队列 + } + // 解码完成的PCM送入OutputBuffer队列 + static void OnOutputBufferAvailable(OH_AVCodec *codec, uint32_t index, OH_AVMemory*data, OH_AVCodecBufferAttr *attr, + void *userData) + { + (void)codec; + ADecSignal *signal = static_cast(userData); + unique_lock lock(signal->outMutex_); + signal->outQueue_.push(index); + signal->outBufferQueue_.push(data); + if (attr) { + signal->attrQueue_.push(*attr); + } + signal->outCond_.notify_all(); + // 将对应输出buffer的 index 送入OutputQueue_队列 + // 将对应解码完成的数据data送入outBuffer队列 + } + signal_ = new ADecSignal(); + OH_AVCodecAsyncCallback cb = {&OnError, &OnOutputFormatChanged, OnInputBufferAvailable, &OnOutputBufferAvailable}; + // 配置异步回调 + int32_t ret = OH_AudioDecoder_SetCallback(audioDec, cb, signal_); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` +3. 调用OH_AudioDecoder_Configure()配置解码器。 + 配置必选项:采样率、码率、声道数;可选项:最大输入长度。 + + - AAC解码 需要额外标识是否为adts类型否则会被认为是latm类型 + + - vorbis解码 需要额外标识ID Header和Setup Header数据 +```cpp +enum AudioFormatType : int32_t { + TYPE_AAC = 0, + TYPE_FLAC = 1, + TYPE_MP3 = 2, + TYPE_VORBIS = 3, +}; +// 设置解码分辨率 +int32_t ret; +// 配置音频采样率(必须) +constexpr uint32_t DEFAULT_SMAPLERATE = 44100; +// 配置音频码率(必须) +constexpr uint32_t DEFAULT_BITRATE = 32000; +// 配置音频声道数(必须) +constexpr uint32_t DEFAULT_CHANNEL_COUNT = 2; +// 配置最大输入长度(可选) +constexpr uint32_t DEFAULT_MAX_INPUT_SIZE = 1152; +OH_AVFormat *format = OH_AVFormat_Create(); +// 写入format +OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_SAMPLE_RATE.data(),DEFAULT_SMAPLERATE); +OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_BITRATE.data(),DEFAULT_BITRATE); +OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_CHANNEL_COUNT.data(),DEFAULT_CHANNEL_COUNT); +OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_MAX_INPUT_SIZE.data(),DEFAULT_MAX_INPUT_SIZE); +if (audioType == TYPE_AAC) { + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_AAC_IS_ADTS.data(), DEFAULT_AAC_TYPE); +} +if (audioType == TYPE_VORBIS) { + OH_AVFormat_SetStringValue(format, MediaDescriptionKey::MD_KEY_IDENTIFICATION_HEADER.data(), DEFAULT_ID_HEADER); + OH_AVFormat_SetStringValue(format, MediaDescriptionKey::MD_KEY_SETUP_HEADER.data(), DEFAULT_SETUP_HEADER); +} +// 配置解码器 +ret = OH_AudioDecoder_Configure(audioDec, format); +if (ret != AV_ERR_OK) { + // 异常处理 +} +``` +4. 调用OH_AudioDecoder_Prepare(),解码器就绪。 + +```cpp +ret = OH_AudioDecoder_Prepare(audioDec); +if (ret != AV_ERR_OK) { + // 异常处理 +} +``` +5. 调用OH_AudioDecoder_Start()启动解码器,进入运行态。 + +```c++ +inputFile_ = std::make_unique(); +// 打开待解码二进制文件路径 +inputFile_->open(inputFilePath.data(), std::ios::in | std::ios::binary); +//配置解码文件输出路径 +outFile_ = std::make_unique(); +outFile_->open(outputFilePath.data(), std::ios::out | std::ios::binary); +// 开始解码 +ret = OH_AudioDecoder_Start(audioDec); +if (ret != AV_ERR_OK) { + // 异常处理 +} +``` +6. 调用OH_AudioDecoder_PushInputData(),写入待解码的数据。 + 如果是结束,需要对flag标识成AVCODEC_BUFFER_FLAGS_EOS +```c++ +// 配置buffer info信息 +OH_AVCodecBufferAttr info; +// 设置输入pkt尺寸、偏移量、时间戳等信息 +info.size = pkt_->size; +info.offset = 0; +info.pts = pkt_->pts; +info.flags = AVCODEC_BUFFER_FLAGS_CODEC_DATA; +auto buffer = signal_->inBufferQueue_.front(); +if (inputFile_->eof()){ + info.size = 0; + info.flags = AVCODEC_BUFFER_FLAGS_EOS; +}else{ + inputFile_->read((char *)OH_AVMemory_GetAddr(buffer), INPUT_FRAME_BYTES); +} +uint32_t index = signal_->inQueue_.front(); +// 送入解码输入队列进行解码, index为对应队列下标 +int32_t ret = OH_AudioDecoder_PushInputData(audioDec, index, info); +if (ret != AV_ERR_OK) { + // 异常处理 +} +``` +7. 调用OH_AudioDecoder_FreeOutputData(),输出解码后的PCM码流 + +```c++ +OH_AVCodecBufferAttr attr = signal_->attrQueue_.front(); +OH_AVMemory *data = signal_->outBufferQueue_.front(); +uint32_t index = signal_->outQueue_.front(); +// 将解码完成数据data写入到对应输出文件中 +outFile_->write(reinterpret_cast(OH_AVMemory_GetAddr(data)), attr.size); +// buffer 模式, 释放已完成写入的数据 +ret = OH_AudioDecoder_FreeOutputData(audioDec, index); +if (ret != AV_ERR_OK) { + // 异常处理 +} +``` +8. (可选)调用OH_AudioDecoder_Flush()刷新解码器。 + 调用OH_AudioDecoder_Flush()后,解码器仍处于运行态,但会将当前队列清空,将已解码的数据释放。 + 此时需要调用OH_AudioDecoder_Start()重新开始解码。 + 使用情况: + * 在文件EOS之后,需要调用刷新 + * 在执行过程中遇到可继续执行的错误时(即OH_AudioDecoder_IsValid 为true)调用 +```c++ +// 刷新解码器 audioDec +ret = OH_AudioDecoder_Flush(audioDec); +if (ret != AV_ERR_OK) { + // 异常处理 +} +// 重新开始解码 +ret = OH_AudioDecoder_Start(audioDec); +if (ret != AV_ERR_OK) { + // 异常处理 +} +``` +9. (可选)调用OH_AudioDecoder_Reset()重置解码器。 +调用OH_AudioDecoder_Reset()后,解码器回到初始化的状态,需要调用OH_AudioDecoder_Configure()重新配置,然后调用OH_AudioDecoder_Start()重新开始解码。 +```c++ +// 重置解码器 audioDec +ret = OH_AudioDecoder_Reset(audioDec); +if (ret != AV_ERR_OK) { + // 异常处理 +} +// 重新配置解码器参数 +ret = OH_AudioDecoder_Configure(audioDec, format); +if (ret != AV_ERR_OK) { + // 异常处理 +} +``` +10. 调用OH_AudioDecoder_Stop()停止解码器。 + +```c++ +// 终止解码器 audioDec +ret = OH_AudioDecoder_Stop(audioDec); +if (ret != AV_ERR_OK) { + // 异常处理 +} +return ret; +``` +11. 调用OH_AudioDecoder_Destroy()销毁解码器实例,释放资源。 + **注意**:不要重复销毁解码器 + +```c++ +// 调用OH_AudioDecoder_Destroy, 注销解码器 +ret = OH_AudioDecoder_Destroy(audioDec); +if (ret != AV_ERR_OK) { + // 异常处理 +} else { + audioDec = NULL; //不可重复destroy +} +return ret; +``` diff --git a/zh-cn/application-dev/media/audio-encoding.md b/zh-cn/application-dev/media/audio-encoding.md new file mode 100644 index 0000000000000000000000000000000000000000..858f766f97cb8879a988ca40e463fa2de92e6842 --- /dev/null +++ b/zh-cn/application-dev/media/audio-encoding.md @@ -0,0 +1,304 @@ +# 音频编码 + +开发者可以调用本模块的Native API接口,完成音频编码,即将音频PCM编码压缩成不同的格式。 + +接口不限制PCM数据的来源,开发者可以调用麦克风录制获取、也可以导入编辑后的PCM数据,通过音频编码,输出对应格式的码流,最后封装为目标格式文件。 + +当前支持的编码能力如下: + +| 容器规格 | 音频编码类型 | +| -------- | :----------- | +| mp4 | AAC、Flac | +| m4a | AAC | +| flac | Flac | +| aac | AAC | + +**适用场景** + +- 音频录制 + + 通过录制传入PCM,然后编码出对应格式的码流,最后封装成想要的格式 +- 音频编辑 + + 编辑PCM后导出音频文件的场景,需要编码成对应音频格式后再封装成文件 + +## 开发步骤 + +详细的API说明请参考[API文档](../reference/native-apis/_audio_encoder.md)。 + +参考以下示例代码,完成音频编码的全流程,包括:创建编码器,设置编码参数(采样率/码率/声道数等),开始,刷新,重置,销毁资源。 + +在应用开发过程中,开发者应按一定顺序调用方法,执行对应操作,否则系统可能会抛出异常或生成其他未定义的行为。具体顺序可参考下列开发步骤及对应说明。 + +完整代码请参考[示例程序](https://gitee.com/openharmony/multimedia_av_codec/blob/master/test/nativedemo/audio_demo/avcodec_audio_aac_encoder_demo.cpp) + +1. 创建编码器实例对象 + + 应用可以通过名称或媒体类型创建编码器。 + +```cpp +//通过 codecname 创建编码器 +OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_AUDIO_AAC, true); +const char *name = OH_AVCapability_GetName(capability); +OH_AVCodec *audioEnc = OH_AudioEncoder_CreateByName(name); +``` + +```cpp +//通过 codecname 创建编码器 +OH_AVCodec *audioEnc = OH_AudioEncoder_CreateByMime(OH_AVCODEC_MIMETYPE_AUDIO_AAC); +``` + +```cpp +// 初始化队列 +class AEncSignal { +public: + std::mutex inMutex_; + std::mutex outMutex_; + std::mutex startMutex_; + std::condition_variable inCond_; + std::condition_variable outCond_; + std::condition_variable startCond_; + std::queue inQueue_; + std::queue outQueue_; + std::queue inBufferQueue_; + std::queue outBufferQueue_; + std::queue attrQueue_; +}; +AEncSignal *signal_ = new AEncSignal(); +``` + +2. 调用OH_AudioEncoder_SetCallback()设置回调函数。 + + 注册回调函数指针集合OH_AVCodecAsyncCallback,包括: + + - 编码器运行错误 + - 码流信息变化,如声道变化等。 + - 运行过程中需要新的输入数据,即编码器已准备好,可以输入PCM数据。 + - 运行过程中产生了新的输出数据,即编码完成。 + + 开发者可以通过处理该回调报告的信息,确保编码器正常运转。 + +```cpp +// 设置 OnError 回调函数 +static void OnError(OH_AVCodec *codec, int32_t errorCode, void *userData) +{ + (void)codec; + (void)errorCode; + (void)userData; +} +// 设置 FormatChange 回调函数 +static void OnOutputFormatChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData) +{ + (void)codec; + (void)format; + (void)userData; +} +// 编码输入PCM送入InputBuffer 队列 +static void OnInputBufferAvailable(OH_AVCodec *codec, uint32_t index, OH_AVMemory *data, void *userData) +{ + (void)codec; + // 编码输入码流送入InputBuffer 队列 + AEncSignal *signal = static_cast(userData); + cout << "OnInputBufferAvailable received, index:" << index << endl; + unique_lock lock(signal->inMutex_); + signal->inQueue_.push(index); + signal->inBufferQueue_.push(data); + signal->inCond_.notify_all(); +} +// 编码完成的码流送入OutputBuffer队列 +static void OnOutputBufferAvailable(OH_AVCodec *codec, uint32_t index, OH_AVMemory *data, OH_AVCodecBufferAttr *attr, + void *userData) +{ + (void)codec; + // 将对应输出buffer的 index 送入OutputQueue_队列 + // 将对应编码完成的数据data送入outBuffer队列 + AEncSignal *signal = static_cast(userData); + unique_lock lock(signal->outMutex_); + signal->outQueue_.push(index); + signal->outBufferQueue_.push(data); + if (attr) { + signal->attrQueue_.push(*attr); + } +} +OH_AVCodecAsyncCallback cb = {&OnError, &OnOutputFormatChanged, &OnInputBufferAvailable, &OnOutputBufferAvailable}; +// 配置异步回调 +int32_t ret = OH_AudioEncoder_SetCallback(audioEnc, cb, userData); +``` + +3. 调用OH_AudioEncoder_Configure设置编码器 + 设置必选项:采样率,码率,以及声道数,声道类型、位深;可选项:最大输入长度 + flac编码: 需要额外标识兼容性级别(Compliance Level)和采样精度 + +```cpp +enum AudioFormatType : int32_t { + TYPE_AAC = 0, + TYPE_FLAC = 1, +}; +int32_t ret; +// 配置音频采样率(必须) +constexpr uint32_t DEFAULT_SMAPLERATE = 44100; +// 配置音频码率(必须) +constexpr uint32_t DEFAULT_BITRATE = 32000; +// 配置音频声道数(必须) +constexpr uint32_t DEFAULT_CHANNEL_COUNT = 2; +// 配置音频声道类型(必须) +constexpr AudioChannelLayout CHANNEL_LAYOUT =AudioChannelLayout::STEREO; +// 配置音频位深(必须) flac 只有SAMPLE_S16LE和SAMPLE_S32LE +constexpr OH_BitsPerSample SAMPLE_FORMAT =OH_BitsPerSample::SAMPLE_S32LE; +// 配置音频位深(必须)aac只有SAMPLE_S32P +constexpr OH_BitsPerSample SAMPLE_AAC_FORMAT =OH_BitsPerSample::SAMPLE_S32P; +// 配置音频compliance level (默认值0,取值范围-2~2) +constexpr int32_t COMPLIANCE_LEVEL = 0; +// 配置音频精度(必须) SAMPLE_S16LE和SAMPLE_S24LE和SAMPLE_S32LE +constexpr BITS_PER_CODED_SAMPLE BITS_PER_CODED_SAMPLE =OH_BitsPerSample::SAMPLE_S24LE; +// 配置最大输入长度(可选) +constexpr uint32_t DEFAULT_MAX_INPUT_SIZE = 1024*2*4;//aac +OH_AVFormat *format = OH_AVFormat_Create(); +// 写入format +OH_AVFormat_SetIntValue(format,MediaDescriptionKey::MD_KEY_SAMPLE_RATE.data(),DEFAULT_SMAPLERATE); +OH_AVFormat_SetIntValue(format,MediaDescriptionKey::MD_KEY_BITRATE.data(), DEFAULT_BITRATE); +OH_AVFormat_SetIntValue(format,MediaDescriptionKey::MD_KEY_CHANNEL_COUNT.data(),DEFAULT_CHANNEL_COUNT); +OH_AVFormat_SetIntValue(format,MediaDescriptionKey::MD_KEY_MAX_INPUT_SIZE.data(),DEFAULT_MAX_INPUT_SIZE); +OH_AVFormat_SetLongValue(format,MediaDescriptionKey::MD_KEY_CHANNEL_LAYOUT.data(),CHANNEL_LAYOUT); +OH_AVFormat_SetIntValue(format,MediaDescriptionKey::MD_KEY_AUDIO_SAMPLE_FORMAT.data(),SAMPLE_FORMAT); +if(audioType == TYPE_AAC){ + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_AUDIO_SAMPLE_FORMAT.data(), SAMPLE_AAC_FORMAT); +} +if (audioType == TYPE_FLAC) { + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_BITS_PER_CODED_SAMPLE.data(), BITS_PER_CODED_SAMPLE); + OH_AVFormat_SetLongValue(format, MediaDescriptionKey::MD_KEY_COMPLIANCE_LEVEL.data(), COMPLIANCE_LEVEL); +} +// 配置编码器 +ret = OH_AudioEncoder_Configure(audioEnc, format); +if (ret != AV_ERR_OK) { + // 异常处理 +} +``` + +4. 调用OH_AudioEncoder_Prepare(),编码器就绪。 + +```c++ +OH_AudioEncoder_Prepare(audioEnc); +``` + +5. 调用OH_AudioEncoder_Start()启动编码器,进入运行态。 + +```c++ +inputFile_ = std::make_unique(); +// 打开待编码二进制文件路径 +inputFile_->open(inputFilePath.data(), std::ios::in |std::ios::binary); +//配置编码文件输出路径 +outFile_ = std::make_unique(); +outFile_->open(outputFilePath.data(), std::ios::out |std::ios::binary); +// 开始编码 +ret = OH_AudioEncoder_Start(audioEnc); +if (ret != AV_ERR_OK) { + // 异常处理 +} +``` + +6. 调用OH_AudioEncoder_PushInputData(),写入待编码器的数据。 + 如果是结束,需要对flag标识成AVCODEC_BUFFER_FLAGS_EOS + +```c++ +constexpr int32_t INPUT_FRAME_BYTES = 2 * 1024 * 4; +// 配置buffer info信息 +OH_AVCodecBufferAttr info; +// 设置输入pkt尺寸、偏移量、时间戳等信息 +info.size = pkt_->size; +info.offset = 0; +info.pts = pkt_->pts; +info.flags = AVCODEC_BUFFER_FLAGS_CODEC_DATA; +auto buffer = signal_->inBufferQueue_.front(); +if (inputFile_->eof()){ + info.size = 0; + info.flags = AVCODEC_BUFFER_FLAGS_EOS; +}else{ + inputFile_->read((char *)OH_AVMemory_GetAddr(buffer), INPUT_FRAME_BYTES); +} +uint32_t index = signal_->inQueue_.front(); +// 送入编码输入队列进行编码, index为对应队列下标 +int32_t ret = OH_AudioEncoder_PushInputData(audioEnc, index,info); +if (ret != AV_ERR_OK) { + // 异常处理 +} +``` + +7. 调用OH_AudioEncoder_FreeOutputData(),输出编码格式码流 + +```c++ +OH_AVCodecBufferAttr attr = signal_->attrQueue_.front(); +OH_AVMemory *data = signal_->outBufferQueue_.front(); +uint32_t index = signal_->outQueue_.front(); +// 将编码完成数据data写入到对应输出文件中 +outFile_->write(reinterpret_cast(OH_AVMemory_GetAdd(data)), attr.size); +// 释放已完成写入的数据 +ret = OH_AudioEncoder_FreeOutputData(audioEnc, index); +if (ret != AV_ERR_OK) { + // 异常处理 +} +if (attr.flags == AVCODEC_BUFFER_FLAGS_EOS) { + cout << "decode eos" << endl; + isRunning_.store(false); + break; +} +``` + +8. (可选)调用OH_AudioEncoder_Flush()刷新编码器。 + 调用OH_AudioEncoder_Flush()后,编码器处于Flush状态,会将当前编码队列清空。 + 此时需要调用OH_AudioEncoder_Start()重新开始编码。 + 使用情况: + * 在文件EOS之后,需要调用刷新 + * 在执行过程中遇到可继续执行的错误时(即OH_AudioEncoder_IsValid 为true)可以调用,然后重新调用OH_AudioEncoder_Start + +```c++ + // 刷新编码器 audioEnc + ret = OH_AudioEncoder_Flush(audioEnc); + if (ret != AV_ERR_OK) { + // 异常处理 + } + // 重新开始编码 + ret = OH_AudioEncoder_Start(audioEnc); + if (ret != AV_ERR_OK) { + // 异常处理 + } +``` + +9. (可选)调用OH_AudioEncoder_Reset()重置编码器。 + 调用OH_AudioEncoder_Reset()后,编码器回到初始化的状态,需要调用OH_AudioEncoder_Configure()重新配置,然后调用OH_AudioEncoder_Start()重新开始编码。。 + + ```c++ + // 重置编码器 audioEnc + ret = OH_AudioEncoder_Reset(audioEnc); + if (ret != AV_ERR_OK) { + // 异常处理 + } + // 重新配置编码器参数 + ret = OH_AudioEncoder_Configure(audioEnc, format); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` +10. 调用OH_AudioEncoder_Stop()停止编码器。 + + ```c++ + // 终止编码器 audioEnc + ret = OH_AudioEncoder_Stop(audioEnc); + if (ret != AV_ERR_OK) { + // 异常处理 + } + return ret; + ``` +11. 调用OH_AudioEncoder_Destroy()销毁编码器实例,释放资源。 + **注意**:资源不能重复销毁 + + ```c++ + // 调用OH_AudioEncoder_Destroy, 注销编码器 + ret = OH_AudioEncoder_Destroy(audioEnc); + if (ret != AV_ERR_OK) { + // 异常处理 + } else { + audioEnc = NULL; //不可重复destroy + } + return ret; + ``` diff --git a/zh-cn/application-dev/media/audio-video-decapsulation.md b/zh-cn/application-dev/media/audio-video-decapsulation.md new file mode 100644 index 0000000000000000000000000000000000000000..5123599f83fc5f0c9729cbcaafce2b3e86b55c05 --- /dev/null +++ b/zh-cn/application-dev/media/audio-video-decapsulation.md @@ -0,0 +1,197 @@ +# 音视频解封装 + +开发者可以调用本模块的Native API接口,完成音视频解封装,即从比特流数据中取出音频、视频等媒体帧数据。 + +当前支持的数据输入类型有:远程连接(http协议)和文件描述符(fd)。 + +支持的解封装格式如下: + +| 媒体格式 | 封装格式 | +| -------- | :----------------------------| +| 视频 | mp4、mpeg-ts | +| 音频 | m4a、aac、mp3、ogg、flac、wav | + + + +**适用场景**: + +- 播放 + + 播放媒体文件时,需要先对音视频流进行解封装,然后使用解封装获取的帧数据进行解码和播放。 + +- 音视频编辑 + + 编辑媒体文件时,需要先对音视频流进行解封装,获取到指定帧进行编辑。 + +- 媒体文件格式转换(转封装) + + 媒体文件格式转换时,需要先对音视频流进行解封装,然后按需将音视频流封装至新的格式文件内。 + +## 开发步骤 +详细的API说明参考[AVDemuxer](../reference/native-apis/_a_v_demuxer.md)和[AVSource](../reference/native-apis/_a_v_source.md) + +> **说明** +> +> - 调用解封装能力解析网络播放路径,需要[申请相关权限](../security/accesstoken-guidelines.md):ohos.permission.INTERNET +> - 调用解封装能力解析本地文件,需要[申请相关权限](../security/accesstoken-guidelines.md):ohos.permission.READ_MEDIA +> - 如果使用ResourceManager.getRawFd打开HAP资源文件描述符,使用方法请参考[ResourceManager API参考](../reference/apis/js-apis-resource-manager.md#getrawfd9) + +1. 创建解封装器实例对象 + + ``` c++ + // 创建文件操作符 fd,打开时对文件句柄必须有读权限 + std::string fileName = "test.mp4"; + int fd = open(fileName.c_str(), O_RDONLY); + struct stat fileStatus {}; + size_t fileSize = 0; + if (stat(fileName.c_str(), &fileStatus) == 0) { + fileSize = static_cast(fileStatus.st_size); + } else { + printf("get stat failed"); + return; + } + // 为 fd 资源文件创建 source 资源对象, 传入 offset 不为文件起始位置 或 size 不为文件大小时,可能会因不能获取完整数据导致 source 创建失败、或后续解封装失败等问题 + OH_AVSource *source = OH_AVSource_CreateWithFD(fd, 0, fileSize); + if (source == nullptr) { + printf("create source failed"); + return; + } + // 为 uri 资源文件创建 source 资源对象(可选) + // OH_AVSource *source = OH_AVSource_CreateWithURI(uri); + ``` + ```c++ + // 为资源对象创建对应的解封装器 + OH_AVDemuxer *demuxer = OH_AVDemuxer_CreateWithSource(source); + if (demuxer == nullptr) { + printf("create demuxer failed"); + return; + } + ``` + + + +2. 获取文件轨道数(可选,若用户已知轨道信息,可跳过此步) + + ``` c++ + // 从文件 source 信息获取文件轨道数 + OH_AVFormat *sourceFormat = OH_AVSource_GetSourceFormat(source); + if (sourceFormat == nullptr) { + printf("get source format failed"); + return; + } + int32_t trackCount = 0; + OH_AVFormat_GetIntValue(sourceFormat, OH_MD_KEY_TRACK_COUNT, &trackCount); + OH_AVFormat_Destroy(sourceFormat); + ``` + + + +3. 获取轨道index及信息(可选,若用户已知轨道信息,可跳过此步) + + ``` c++ + uint32_t audioTrackIndex = 0; + uint32_t videoTrackIndex = 0; + int32_t w = 0; + int32_t h = 0; + int32_t trackType; + for (uint32_t index = 0; index < (static_cast(trackCount)); index++) { + // 获取轨道信息 + OH_AVFormat *trackFormat = OH_AVSource_GetTrackFormat(source, index); + if (trackFormat == nullptr) { + printf("get track format failed"); + return; + } + OH_AVFormat_GetIntValue(trackFormat, OH_MD_KEY_TRACK_TYPE, &trackType); + static_cast(trackType) == OH_MediaType::MEDIA_TYPE_AUD ? audioTrackIndex = index : videoTrackIndex = index; + // 获取视频轨宽高 + if (trackType == OH_MediaType::MEDIA_TYPE_VID) { + OH_AVFormat_GetIntValue(trackFormat, OH_MD_KEY_WIDTH, &w); + OH_AVFormat_GetIntValue(trackFormat, OH_MD_KEY_HEIGHT, &h); + } + OH_AVFormat_Destroy(trackFormat); + } + ``` + + + +4. 添加解封装轨道 + + ``` c++ + if(OH_AVDemuxer_SelectTrackByID(demuxer, audioTrackIndex) != AV_ERR_OK){ + printf("select audio track failed: %d", audioTrackIndex); + return; + } + if(OH_AVDemuxer_SelectTrackByID(demuxer, videoTrackIndex) != AV_ERR_OK){ + printf("select video track failed: %d", videoTrackIndex); + return; + } + // 取消选择轨道(可选) + // OH_AVDemuxer_UnselectTrackByID(demuxer, audioTrackIndex); + ``` + + + +5. 调整轨道到指定时间点(可选) + + ``` c++ + // 调整轨道到指定时间点,后续从该时间点进行解封装 + OH_AVDemuxer_SeekToTime(demuxer, 0, OH_AVSeekMode::SEEK_MODE_CLOSEST_SYNC); + ``` + +6. 开始解封装,循环获取帧数据(以含音频、视频两轨的文件为例) + + ``` c++ + // 创建 buffer,用与保存用户解封装得到的数据 + OH_AVMemory *buffer = OH_AVMemory_Create(w * h * 3 >> 1); + if (buffer == nullptr) { + printf("build buffer failed"); + return; + } + OH_AVCodecBufferAttr info; + bool videoIsEnd = false; + bool audioIsEnd = false; + int32_t ret; + while (!audioIsEnd || !videoIsEnd) { + // 在调用 OH_AVDemuxer_ReadSample 接口获取数据前,需要先调用 OH_AVDemuxer_SelectTrackByID 选中需要获取数据的轨道 + // 获取音频帧数据 + if(!audioIsEnd) { + ret = OH_AVDemuxer_ReadSample(demuxer, audioTrackIndex, buffer, &info); + if (ret == AV_ERR_OK) { + // 可通过 buffer 获取并处理音频帧数据 + printf("audio info.size: %d\n", info.size); + if (info.flags == OH_AVCodecBufferFlags::AVCODEC_BUFFER_FLAGS_EOS) { + audioIsEnd = true; + } + } + } + if(!videoIsEnd) { + ret = OH_AVDemuxer_ReadSample(demuxer, videoTrackIndex, buffer, &info); + if (ret == AV_ERR_OK) { + // 可通过 buffer 获取并处理视频帧数据 + printf("video info.size: %d\n", info.size); + if (info.flags == OH_AVCodecBufferFlags::AVCODEC_BUFFER_FLAGS_EOS) { + videoIsEnd = true; + } + } + } + } + OH_AVMemory_Destroy(buffer); + ``` + + + +7. 销毁解封装实例 + + ``` c++ + // 需要用户调用 OH_AVSource_Destroy 接口成功后,手动将对象置为 NULL,对同一对象重复调用 OH_AVSource_Destroy 会导致程序错误 + if (OH_AVSource_Destroy(source) != AV_ERR_OK) { + printf("destroy source pointer error"); + } + source = NULL; + // 需要用户调用 OH_AVDemuxer_Destroy 接口成功后,手动将对象置为 NULL,对同一对象重复调用 OH_AVDemuxer_Destroy 会导致程序错误 + if (OH_AVDemuxer_Destroy(demuxer) != AV_ERR_OK) { + printf("destroy demuxer pointer error"); + } + demuxer = NULL; + close(fd); + ``` \ No newline at end of file diff --git a/zh-cn/application-dev/media/audio-video-encapsulation.md b/zh-cn/application-dev/media/audio-video-encapsulation.md new file mode 100644 index 0000000000000000000000000000000000000000..56a3b66b44014f7efbaa18d005dbe950c7aa20e0 --- /dev/null +++ b/zh-cn/application-dev/media/audio-video-encapsulation.md @@ -0,0 +1,203 @@ +# 音视频封装 + +开发者可以调用本模块的Native API接口,完成音视频封装,即将音频、视频等编码后的媒体数据,按一定的格式存储到文件里。 + +当前支持的封装能力如下: + +| 封装格式 | 视频编解码类型 | 音频编解码类型 | 封面类型 | +| -------- | --------------------- | ---------------- | -------------- | +| mp4 | MPEG-4、 AVC(H.264) | AAC、MPEG(MP3) | jpeg、png、bmp | +| m4a | MPEG-4、 AVC(H.264) | AAC | jpeg、png、bmp | + +**适用场景** + +- 录像、录音 + + 保存录像、录音文件时,需要先对音视频流进行编码,再封装为文件。 + +- 音视频编辑 + + 完成编辑后的音视频,需要封装为文件。 + +- 音视频转码 + + 转码后,保存文件时需要封装。 + +## 开发步骤 + +详细的API说明请参考[API文档](../reference/native-apis/_a_v_muxer.md)。 + +> **说明:** +> +> 如果调用封装能力写本地文件,需要[申请相关权限](../security/accesstoken-guidelines.md):ohos.permission.READ_MEDIA, ohos.permission.WRITE_MEDIA + +参考以下示例代码,完成音视频封装的全流程。以封装mp4格式的音视频文件为例。 + +1. 调用OH_AVMuxer_Create()创建封装器实例对象。 + + ``` c++ + // 设置封装格式为mp4 + OH_AVOutputFormat format = AV_OUTPUT_FORMAT_MPEG_4; + // 以读写方式创建fd + int32_t fd = open("test.mp4", O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR); + OH_AVMuxer *muxer = OH_AVMuxer_Create(fd, format); + ``` + +2. (可选)调用OH_AVMuxer_SetRotation()设置旋转角度。 + + ``` c++ + // 旋转角度,视频画面需要旋转的时候设置 + OH_AVMuxer_SetRotation(muxer, 0); + ``` + +3. 添加音频轨。 + + **方法一:用OH_AVFormat_Create创建format** + + ``` c++ + int audioTrackId = -1; + OH_AVFormat *formatAudio = OH_AVFormat_Create(); + OH_AVFormat_SetStringValue(formatAudio, OH_MD_KEY_CODEC_MIME, OH_AVCODEC_MIMETYPE_AUDIO_AAC); // 必填 + OH_AVFormat_SetIntValue(formatAudio, OH_MD_KEY_AUD_SAMPLE_RATE, 44100); // 必填 + OH_AVFormat_SetIntValue(formatAudio, OH_MD_KEY_AUD_CHANNEL_COUNT, 2); // 必填 + + int ret = OH_AVMuxer_AddTrack(muxer, &audioTrackId, formatAudio); + if (ret != AV_ERR_OK || audioTrackId < 0) { + // 音频轨添加失败 + } + OH_AVFormat_Destroy(formatAudio); // 销毁 + ``` + + **方法二:用OH_AVFormat_CreateAudioFormat创建format** + + ``` c++ + int audioTrackId = -1; + OH_AVFormat *formatAudio = OH_AVFormat_CreateAudioFormat(OH_AVCODEC_MIMETYPE_AUDIO_AAC, 44100, 2); + + int ret = OH_AVMuxer_AddTrack(muxer, &audioTrackId, formatAudio); + if (ret != AV_ERR_OK || audioTrackId < 0) { + // 音频轨添加失败 + } + OH_AVFormat_Destroy(formatAudio); // 销毁 + ``` + +4. 添加视频轨。 + + **方法一:用OH_AVFormat_Create创建format** + + ``` c++ + int videoTrackId = -1; + char *buffer = ...; // 编码config data,如果没有可以不传 + size_t size = ...; // 编码config data的长度,根据实际情况配置 + OH_AVFormat *formatVideo = OH_AVFormat_Create(); + OH_AVFormat_SetStringValue(formatVideo, OH_MD_KEY_CODEC_MIME, OH_AVCODEC_MIMETYPE_VIDEO_MPEG4); // 必填 + OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_WIDTH, 1280); // 必填 + OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_HEIGHT, 720); // 必填 + OH_AVFormat_SetBuffer(formatVideo, OH_MD_KEY_CODEC_CONFIG, buffer, size); // 非必须 + + int ret = OH_AVMuxer_AddTrack(muxer, &videoTrackId, formatVideo); + if (ret != AV_ERR_OK || videoTrackId < 0) { + // 视频轨添加失败 + } + OH_AVFormat_Destroy(formatVideo); // 销毁 + ``` + + **方法二:用OH_AVFormat_CreateVideoFormat创建format** + + ``` c++ + int videoTrackId = -1; + char *buffer = ...; // 编码config data,如果没有可以不传 + size_t size = ...; // 编码config data的长度,根据实际情况配置 + OH_AVFormat *formatVideo = OH_AVFormat_CreateVideoFormat(OH_AVCODEC_MIMETYPE_VIDEO_MPEG4, 1280, 720); + OH_AVFormat_SetBuffer(formatVideo, OH_MD_KEY_CODEC_CONFIG, buffer, size); // 非必须 + + int ret = OH_AVMuxer_AddTrack(muxer, &videoTrackId, formatVideo); + if (ret != AV_ERR_OK || videoTrackId < 0) { + // 视频轨添加失败 + } + OH_AVFormat_Destroy(formatVideo); // 销毁 + ``` + +5. 添加封面轨。 + + **方法一:用OH_AVFormat_Create创建format** + + ``` c++ + int coverTrackId = -1; + OH_AVFormat *formatCover = OH_AVFormat_Create(); + OH_AVFormat_SetStringValue(formatCover, OH_MD_KEY_CODEC_MIME, OH_AVCODEC_MIMETYPE_IMAGE_JPG); + OH_AVFormat_SetIntValue(formatCover, OH_MD_KEY_WIDTH, 1280); + OH_AVFormat_SetIntValue(formatCover, OH_MD_KEY_HEIGHT, 720); + + int ret = OH_AVMuxer_AddTrack(muxer, &coverTrackId, formatCover); + if (ret != AV_ERR_OK || coverTrackId < 0) { + // 添加封面失败 + } + OH_AVFormat_Destroy(formatCover); // 销毁 + ``` + + **方法二:用OH_AVFormat_CreateVideoFormat创建format** + + ``` c++ + int coverTrackId = -1; + OH_AVFormat *formatCover = OH_AVFormat_CreateVideoFormat(OH_AVCODEC_MIMETYPE_IMAGE_JPG, 1280, 720); + + int ret = OH_AVMuxer_AddTrack(muxer, &coverTrackId, formatCover); + if (ret != AV_ERR_OK || coverTrackId < 0) { + // 添加封面失败 + } + OH_AVFormat_Destroy(formatCover); // 销毁 + ``` + +6. 调用OH_AVMuxer_Start()开始封装。 + + ``` c++ + // 调用start,写封装文件头。start后,不能设置媒体参数、不能添加媒体轨 + if (OH_AVMuxer_Start(muxer) != AV_ERR_OK) { + // 异常处理 + } + ``` + +7. 调用OH_AVMuxer_WriteSample(),写入封装数据。 + + 包括视频、音频、封面数据。 + + ``` c++ + // start后,才能开始写入数据 + int size = ...; + OH_AVMemory *sample = OH_AVMemory_Create(size); // 创建AVMemory + // 往sampleBuffer里写入数据参考OH_AVMemory的使用方法 + // 封装封面,必须一次写完一张图片 + + // 创建buffer info + OH_AVCodecBufferAttr info; + info.pts = ...; // 当前数据的开始播放的时间,单位微秒 + info.size = size; // 当前数据的长度 + info.offset = 0; // 偏移,一般为0 + info.flags |= AVCODEC_BUFFER_FLAGS_SYNC_FRAME; // 当前数据的标志。具体参考OH_AVCodecBufferFlags + int trackId = audioTrackId; // 选择写的媒体轨 + + int ret = OH_AVMuxer_WriteSample(muxer, trackId, sample, info); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +8. 调用OH_AVMuxer_Stop(),停止封装。 + + ``` c++ + // 调用stop,写封装文件尾。stop后不能写入媒体数据 + if (OH_AVMuxer_Stop(muxer) != AV_ERR_OK) { + // 异常处理 + } + ``` + +9. 调用OH_AVMuxer_Destroy()销毁实例,释放资源。 + + ``` c++ + if (OH_AVMuxer_Destroy(muxer) != AV_ERR_OK) { + // 异常处理 + } + muxer = NULL; + close(fd); // 关闭文件描述符 + ``` \ No newline at end of file diff --git a/zh-cn/application-dev/media/obtain-supported-codecs.md b/zh-cn/application-dev/media/obtain-supported-codecs.md new file mode 100644 index 0000000000000000000000000000000000000000..4bd1d22ae36e92643705c9c3bf8755dd50247e76 --- /dev/null +++ b/zh-cn/application-dev/media/obtain-supported-codecs.md @@ -0,0 +1,156 @@ +# 获取支持的编解码能力 + +不同设备支持的编解码能力存在差异,开发者在调用编解码或配置编解码器前,需要先查询当前系统支持的编解码器规格。 + +开发者可以调用本模块的Native API接口,查询相关能力的支持情况。 + +## 开发步骤 + +详细的API说明请参考[API文档](../reference/native-apis/_a_v_capability.md)。 + +1. 获得能力 + + ```c + // 根据mime type、是否编码器获得能力 + OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, false); + + // 根据mime type、是否编码器以及软硬件类别获得能力 + OH_AVCapability *capability = OH_AVCodec_GetCapabilityByCategory(OH_AVCODEC_MIMETYPE_VIDEO_AVC, false, SOFTWARE); + ``` + +2. 查询参数 + ```c + // 查询当前能力是否支持硬件 + bool isHardware = OH_AVCapability_IsHardware(capability); + + // 查询当前能力codec名称 + const char *codecName = OH_AVCapability_GetName(capability); + + // 查询当前能力中,最大支持的实例数 + int32_t maxSupportedInstances = OH_AVCapability_GetMaxSupportedInstances(capability); + + // 查询当前能力中,编码支持的码率范围 + OH_AVRange bitrateRange; + int32_t ret = OH_AVCapability_GetEncoderBitrateRange(capability, &bitrateRange); + if (ret != AV_ERR_OK) { + // 处理异常 + } + + // 查询当前能力中,码控模式是否支持 + bool isEncoderBitrateModeSupported = OH_AVCapability_IsEncoderBitrateModeSupported(capability, &bitrateMode); + + // 查询当前能力中,编码质量范围 + OH_AVRange qualityRange; + int32_t ret = OH_AVCapability_GetEncoderQualityRange(capability, &qualityRange); + if (ret != AV_ERR_OK) { + // 处理异常 + } + + // 查询当前能力中,编码复杂度范围 + OH_AVRange complexityRange; + int32_t ret = OH_AVCapability_GetEncoderComplexityRange(capability, &complexityRange); + if (ret != AV_ERR_OK) { + // 处理异常 + } + + // 查询当前能力中,支持的音频采样率 + const int32_t *sampleRates; + uint32_t sampleRateNum = 0; + int32_t ret = OH_AVCapability_GetAudioSupportedSampleRates(capability, &sampleRates, &sampleRateNum); + if (ret != AV_ERR_OK) { + // 处理异常 + } + + // 查询当前能力中,支持的音频通道数范围 + OH_AVRange channelCountRange; + int32_t ret = OH_AVCapability_GetAudioChannelCountRange(capability, &channelCountRange); + if (ret != AV_ERR_OK) { + // 处理异常 + } + + // 查询当前能力中,宽的对齐值 + int32_t widthAlignment; + int32_t ret = OH_AVCapability_GetVideoWidthAlignment(capability, &widthAlignment); + if (ret != AV_ERR_OK) { + // 处理异常 + } + + // 查询当前能力中,高的对齐值 + int32_t heightAlignment; + int32_t ret = OH_AVCapability_GetVideoHeightAlignment(capability, &heightAlignment); + if (ret != AV_ERR_OK) { + // 处理异常 + } + + // 查询当前能力中,高为1080时,宽的范围 + OH_AVRange widthRange; + int32_t ret = OH_AVCapability_GetVideoWidthRangeForHeight(capability, 1080, &widthRange); + if (ret != AV_ERR_OK) { + // 处理异常 + } + + // 查询当前能力中,宽为1920时,高的范围 + OH_AVRange heightRange; + int32_t ret = OH_AVCapability_GetVideoHeightRangeForWidth(capability, 1920, &heightRange); + if (ret != AV_ERR_OK) { + // 处理异常 + } + + // 查询当前能力中,宽的范围 + OH_AVRange widthRange; + int32_t ret = OH_AVCapability_GetVideoWidthRange(capability, &widthRange); + if (ret != AV_ERR_OK) { + // 处理异常 + } + + // 查询当前能力中,高的范围 + OH_AVRange heightRange; + int32_t ret = OH_AVCapability_GetVideoWidthRange(capability, &heightRange); + if (ret != AV_ERR_OK) { + // 处理异常 + } + + // 校验当前能力是否支持分辨率1080p + bool isVideoSizeSupported = OH_AVCapability_IsVideoSizeSupported(capability, 1920, 1080); + + // 查询当前能力中,视频帧率范围 + OH_AVRange frameRateRange; + int32_t ret = OH_AVCapability_GetVideoFrameRateRange(capability, &frameRateRange); + if (ret != AV_ERR_OK) { + // 处理异常 + } + + // 查询当前能力中,分辨率为1920x1080时视频帧率范围 + OH_AVRange frameRateRange; + int32_t ret = OH_AVCapability_GetVideoFrameRateRangeForSize(capability, 1920, 1080, &frameRateRange); + if (ret != AV_ERR_OK) { + // 处理异常 + } + + // 校验当前能力是否支持分辨率1080p、帧率30的场景 + bool areVideoSizeAndFrameRateSupported = OH_AVCapability_AreVideoSizeAndFrameRateSupported(capability, 1920, 1080, 30); + + // 查询当前能力中,支持的颜色格式以及个数 + const int32_t *pixFormats; + uint32_t pixFormatNum = 0; + int32_t ret = OH_AVCapability_GetVideoSupportedPixelFormats(capability, &pixFormats, &pixFormatNum); + if (ret != AV_ERR_OK) { + // 处理异常 + } + + // 查询当前能力中,支持的模板 + const int32_t *profiles; + uint32_t profileNum = 0; + int32_t ret = OH_AVCapability_GetSupportedProfiles(capability, &profiles, &profileNum); + if (ret != AV_ERR_OK) { + // 处理异常 + } + + // 查询当前能力中,特定模板情况下的等级范围 + const int32_t *levels; + uint32_t levelNum = 0; + int32_t ret = OH_AVCapability_GetSupportedLevelsForProfile(capability, 0, &levels, &levelNum); + + // 校验当前能力是否支持分辨率1080p、帧率30的场景 + bool areVideoSizeAndFrameRateSupported = OH_AVCapability_AreVideoSizeAndFrameRateSupported(capability, 1920, 1080, 30); + ``` \ No newline at end of file diff --git a/zh-cn/application-dev/media/video-decoding.md b/zh-cn/application-dev/media/video-decoding.md new file mode 100644 index 0000000000000000000000000000000000000000..2da59c26b90cfb3c5e398fde5066e35ca955970c --- /dev/null +++ b/zh-cn/application-dev/media/video-decoding.md @@ -0,0 +1,263 @@ +# 视频解码 + +开发者可以调用本模块的Native API接口,完成视频解码,即将媒体数据解码成YUV文件或送显。 + +当前支持的解码能力如下: + +| 容器规格 | 视频硬解类型 | 视频软解类型 | +| -------- | --------------------- | ---------------- | +| mp4 | AVC(H.264)、HEVC(H.265) |AVC(H.264) | + + +视频解码软/硬件解码存在差异,基于MimeType创建解码器时,软解当前仅支持 H264 ("video/avc"),硬解则支持 H264 ("video/avc") 和 H265 ("video/hevc")。 + +## 开发步骤 + +详细的API说明请参考[API文档](../reference/native-apis/_video_decoder.md)。 + +1. 创建编解码器实例对象。 + + 应用可以通过名称或媒体类型创建解码器。 + + ``` c++ + // 通过 codecname 创建解码器 + OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, false); + const char *name = OH_AVCapability_GetName(capability); + OH_AVCodec *videoDec = OH_AudioDecoder_CreateByName(name); // name:"OH.Media.Codec.Decoder.Video.AVC" + ``` + ```c++ + // 通过 mimetype 创建解码器 + // 软/硬解: 创建 H264 解码器 + OH_AVCodec *videoDec = OH_VideoDecoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_AVC); + // 通过 mimetype 创建解码器 + // 硬解: 创建 H265 解码器 + OH_AVCodec *videoDec = OH_VideoDecoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_HEVC); + ``` + ``` c++ + // 初始化队列 + class VDecSignal { + public: + std::mutex inMutex_; + std::mutex outMutex_; + std::condition_variable inCond_; + std::condition_variable outCond_; + std::queue inQueue_; + std::queue outQueue_; + std::queue inBufferQueue_; + std::queue outBufferQueue_; + std::queue attrQueue_; + }; + VDecSignal *signal_; + ``` + +2. 调用OH_VideoDecoder_SetCallback()设置回调函数。 + + 注册回调函数指针集合OH_AVCodecAsyncCallback,包括: + + - 解码器运行错误 + - 码流信息变化,如码流宽、高变化。 + - 运行过程中需要新的输入数据,即解码器已准备好,可以输入数据。 + - 运行过程中产生了新的输出数据,即解码完成。(注:Surface模式data参数为空) + + 开发者可以通过处理该回调报告的信息,确保解码器正常运转。 + + ``` c++ + // 解码异常回调 + static void OnError(OH_AVCodec *codec, int32_t errorCode, void *userData) + { + (void)codec; + (void)errorCode; + (void)userData; + } + // 解码码流变化回调 + static void OnOutputFormatChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData) + { + (void)codec; + (void)format; + (void)userData; + } + // 解码输入回调获取输入帧信息 + static void OnInputBufferAvailable(OH_AVCodec *codec, uint32_t index, OH_AVMemory *data, void *userData) + { + (void)codec; + VDecSignal *signal_ = static_cast(userData); + unique_lock lock(signal_->inMutex_); + // 解码输入帧id送入 inQueue_ + signal_->inQueue_.push(index); + // 解码输入帧数据送入 inBufferQueue_ + signal_->inBufferQueue_.push(data); + signal_->inCond_.notify_all(); + } + // 解码输出回调获取输出帧信息 + static void OnOutputBufferAvailable(OH_AVCodec *codec, uint32_t index, OH_AVMemory *data, OH_AVCodecBufferAttr *attr, + void *userData) + { + (void)codec; + VDecSignal *signal_ = static_cast(userData); + unique_lock lock(signal_->outMutex_); + // 将对应输出 buffer 的 index 送入 outQueue_ + signal_->outQueue_.push(index); + // 将对应解码完成的数据 data 送入 outBufferQueue_ (注: Surface模式下data为空) + signal_->outBufferQueue_.push(data); + signal_->attrQueue_.push(*attr); + signal_->outCond_.notify_all(); + } + OH_AVCodecAsyncCallback cb = {&OnError, &OnOutputFormatChanged, &OnInputBufferAvailable, &OnOutputBufferAvailable}; + // 配置异步回调 + int32_t ret = OH_VideoDecoder_SetCallback(videoDec, cb, signal_); + ``` + +3. 调用OH_VideoDecoder_Configure()配置解码器。 + + 配置必选项:视频帧宽度、视频帧高度、视频颜色格式。 + + ``` c++ + // 配置视频帧宽度(必须) + constexpr uint32_t DEFAULT_WIDTH = 320; + // 配置视频帧高度(必须) + constexpr uint32_t DEFAULT_HEIGHT = 240; + OH_AVFormat *format = OH_AVFormat_Create(); + // 写入 format + OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, DEFAULT_WIDTH); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, DEFAULT_HEIGHT); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, AV_PIXEL_FORMAT_NV21); + // 配置解码器 + int32_t ret = OH_VideoDecoder_Configure(videoDec, format); + OH_AVFormat_Destroy(format); + ``` + +4. (如需使用Surface送显,必须设置)设置Surface。 + + ``` c++ + // 配置送显窗口参数 + sptr window = nullptr; + sptr option = new Rosen::WindowOption(); + option->SetWindowRect({0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT}); + option->SetWindowType(Rosen::WindowType::WINDOW_TYPE_APP_LAUNCHING); + option->SetWindowMode(Rosen::WindowMode::WINDOW_MODE_FLOATING); + window = Rosen::Window::Create("video-decoding", option); + window->Show(); + sptr ps = window->GetSurfaceNode()->GetSurface(); + OHNativeWindow *nativeWindow = CreateNativeWindowFromSurface(&ps); + int32_t ret = OH_VideoDecoder_SetSurface(videoDec, window); + bool isSurfaceMode = true; + ``` + +5. (仅使用Surface时可配置)配置解码器surface参数。 + + ``` c++ + OH_AVFormat *format = OH_AVFormat_Create(); + // 配置显示旋转角度 + OH_AVFormat_SetIntValue(format, OH_MD_KEY_ROTATION, 90); + // 配置视频与显示屏匹配模式(缩放与显示窗口适配, 裁剪与显示窗口适配) + OH_AVFormat_SetIntValue(format, OH_MD_KEY_SCALING_MODE, SCALING_MODE_SCALE_CROP); + int32_t ret = OH_VideoDecoder_SetParameter(videoDec, format); + OH_AVFormat_Destroy(format); + ``` + +6. 调用OH_VideoDecoder_Start()启动解码器。 + + ``` c++ + string_view outputFilePath = "/*yourpath*.yuv"; + std::unique_ptr inputFile = std::make_unique(); + // 打开待解码二进制文件路径 + inputFile->open(inputFilePath.data(), std::ios::in | std::ios::binary); + // buffer 模式下需要配置 + if(!isSurfaceMode) { + // buffer 模式: 配置解码文件输出路径 + std::unique_ptr outFile = std::make_unique(); + outFile->open(outputFilePath.data(), std::ios::out | std::ios::binary); + } + // 开始解码 + int32_t ret = OH_VideoDecoder_Start(videoDec); + ``` + +7. 调用OH_VideoDecoder_PushInputData(),写入解码码流。 + + ``` c++ + // 配置 buffer info 信息 + OH_AVCodecBufferAttr info; + // 调用 Ffmpeg 接口 av_packet_alloc 进行初始化并返回一个容器 pkt + AVPacket pkt = av_packet_alloc(); + // 配置 info 的输入尺寸、偏移量、时间戳等字段信息 + info.size = pkt->size; + info.offset = 0; + info.pts = pkt->pts; + info.flags = AVCODEC_BUFFER_FLAGS_NONE; + // 送入解码输入队列进行解码, index 为对应队列下标 + int32_t ret = OH_VideoDecoder_PushInputData(videoDec, index, info); + ``` + +8. 调用OH_VideoDecoder_FreeOutputData(),输出解码帧。 + + ``` c++ + int32_t ret; + // 将解码完成数据 data 写入到对应输出文件中 + outFile->write(reinterpret_cast(OH_AVMemory_GetAddr(data)), data.size); + // buffer 模式, 释放已完成写入的数据, index 为对应 surface/buffer 队列下标 + if (isSurfaceMode) { + ret = OH_VideoDecoder_RenderOutputData(videoDec, index); + } else { + ret = OH_VideoDecoder_FreeOutputData(videoDec, index); + } + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +9. (可选)调用OH_VideoDecoder_Flush()刷新解码器。 + + 调用OH_VideoDecoder_Flush()后,解码器仍处于运行态,但会将当前队列清空,将已解码的数据释放。 + + 此时需要调用OH_VideoDecoder_Start()重新开始解码。 + + ``` c++ + int32_t ret; + // 刷新解码器 videoDec + ret = OH_VideoDecoder_Flush(videoDec); + if (ret != AV_ERR_OK) { + // 异常处理 + } + // 重新开始解码 + ret = OH_VideoDecoder_Start(videoDec); + ``` + +10. (可选)调用OH_VideoDecoder_Reset()重置解码器。 + + 调用OH_VideoDecoder_Reset()后,解码器回到初始化的状态,需要调用OH_VideoDecoder_Configure()重新配置。 + + ``` c++ + int32_t ret; + // 重置解码器 videoDec + ret = OH_VideoDecoder_Reset(videoDec); + if (ret != AV_ERR_OK) { + // 异常处理 + } + // 重新配置解码器参数 + ret = OH_VideoDecoder_Configure(videoDec, format); + ``` + +11. 调用OH_VideoDecoder_Stop()停止解码器。 + + ``` c++ + int32_t ret; + // 终止解码器 videoDec + ret = OH_VideoDecoder_Stop(videoDec); + if (ret != AV_ERR_OK) { + // 异常处理 + } + return AV_ERR_OK; + ``` + +12. 调用OH_VideoDecoder_Destroy()销毁解码器实例,释放资源。 + + ``` c++ + int32_t ret; + // 调用 OH_VideoDecoder_Destroy, 注销解码器 + ret = OH_VideoDecoder_Destroy(videoDec); + if (ret != AV_ERR_OK) { + // 异常处理 + } + return AV_ERR_OK; + ``` + diff --git a/zh-cn/application-dev/media/video-encoding.md b/zh-cn/application-dev/media/video-encoding.md new file mode 100644 index 0000000000000000000000000000000000000000..eccceda896bbe65d624c161dee944212b11706b8 --- /dev/null +++ b/zh-cn/application-dev/media/video-encoding.md @@ -0,0 +1,564 @@ +# 视频编码 + +开发者可以调用本模块的Native API接口,完成视频编码,即将未压缩的音视频数据压缩成音视频码流。 + +当前支持的编码能力如下: + +| 容器规格 | 视频编码类型 | 音频编码类型 | +| -------- | --------------------- | ---------------- | +| mp4 | HEVC(H.265)、 AVC(H.264) | AAC、MPEG(MP3) | +| m4a | HEVC(H.265)、 AVC(H.264) | AAC | + +## Surface输入与Buffer输入 + +两者的数据来源不同。 + +Surface输入包含了像素数据、像素格式等信息,如相机模块直接传入的录制视频流等。更适用于实时采集等场景。 + +Buffer输入是指一块内存区域,一般为字节数组或指向内存的指针。更适用于从文件中读取音视频数据,或是实时流式传输等场景。 + +在接口调用的过程中,两种方式的接口调用方式基本一致,但存在以下差异点: + +1. Buffer模式下,应用调用OH_VideoEncoder_PushInputData()输入数据;Surface模式下,应用应在编码器启动前调用OH_VideoEncoder_GetSurface(),获取Surface用于传递视频数据。 + +2. Buffer模式下,应用调用OH_VideoEncoder_PushInputData()传入结束flag,编码器读取到尾帧后,停止编码;Surface模式下,需要调用OH_VideoEncoder_NotifyEndOfStream()通知编码器输入流结束。 + +两种模式的开发步骤详细说明请参考:[Buffer模式](#buffer模式)和[Surface模式](#surface模式)。 + +## 开发步骤 + +详细的API说明请参考[API文档](../reference/native-apis/_video_encoder.md)。 + +### Buffer模式 + +参考以下示例代码,开发者可以完成Buffer输入模式下,视频编码的全流程。此处以YUV文件输入,编码成H.264格式为例。 +本模块目前仅支持异步模式的数据轮转。 + +1. 创建编码器实例对象。 + + 应用可以通过名称或媒体类型创建解码器。 + + ``` c++ + // 通过 MIME TYPE 创建编码器,系统会根据MIME创建最合适的编码器。 + OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByMime(CodecMimeType::VIDEO_AVC.data()); + ``` + + ```c++ + // 通过codec name创建编码器,应用有特殊需求,比如选择支持某种分辨率规格的编码器,可先查询capability,再根据codec name创建编码器。 + OH_AVCapability *capability = OH_AVCodec_GetCapability(CodecMimeType::VIDEO_AVC.data(), true); + const char *codecName = OH_AVCapability_GetName(capability); + OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByName(codecName); + ``` + +2. 调用OH_VideoEncoder_SetCallback()设置回调函数。 + + > **注意:** + > + > 在回调函数中,对数据队列进行操作时,需要注意多线程冲突的问题。 + + 注册回调函数指针集合OH_AVCodecAsyncCallback,包括: + + - 编码器运行错误 + - 码流信息变化,如声道变化等。 + - 运行过程中需要新的输入数据,即编码器已准备好,可以输入PCM数据。 + - 运行过程中产生了新的输出数据,即编码完成。 + + 开发者可以通过处理该回调报告的信息,确保编码器正常运转。 + + ``` c++ + // 设置 OnError 回调函数 + static void OnError(OH_AVCodec *codec, int32_t errorCode, void *userData) + { + (void)codec; + (void)errorCode; + (void)userData; + } + + // 设置 OnStreamChanged 回调函数 + static void OnStreamChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData) + { + (void)codec; + (void)format; + (void)userData; + } + + // 设置 OnNeedInputData 回调函数,编码输入帧送入数据队列 + static void OnNeedInputData(OH_AVCodec *codec, uint32_t index, OH_AVMemory *mem, void *userData) + { + (void)userData; + // 输入帧buffer对应的index,送入InIndexQueue队列 + // 输入帧的数据mem送入InBufferQueue队列 + // 数据处理,请参考: + // 7. 写入编码码流 + // 8. 通知编码器码流结束 + } + + // 设置 OnNeedOutputData 回调函数,编码完成帧送入输出队列 + static void OnNeedOutputData(OH_AVCodec *codec, uint32_t index, OH_AVMemory *mem, + OH_AVCodecBufferAttr *attr, void *userData) + { + (void)userData; + // 完成帧buffer对应的index,送入outIndexQueue队列 + // 完成帧的数据mem送入outBufferQueue队列 + // 完成帧的数据格式送入outAttrQueue队列 + // 数据处理,请参考: + // 9. 输出编码帧 + } + + // 配置异步回调,调用 OH_VideoEncoder_SetCallback 接口 + OH_AVCodecAsyncCallback cb = {&OnError, &OnStreamChanged, &OnNeedInputData, &OnNeedOutputData}; + int32_t ret = OH_VideoEncoder_SetCallback(videoEnc, cb, userData); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +3. 调用OH_VideoEncoder_Configure()配置编码器。 + + 目前支持的所有格式都必须配置以下选项:视频帧宽度、视频帧高度、视频像素格式。 + 示例中的变量如下: + - DEFAULT_WIDTH:320像素宽度; + - DEFAULT_HEIGHT:240像素高度; + - DEFAULT_PIXELFORMAT: 像素格式,因为示例使用YUV的文件保存的像素格式是YUV420P,所以设置为VideoPixelFormat::YUV420P。 + + ``` c++ + // 配置视频帧宽度(必须) + constexpr uint32_t DEFAULT_WIDTH = 320; + // 配置视频帧高度(必须) + constexpr uint32_t DEFAULT_HEIGHT = 240; + // 配置视频像素格式(必须) + constexpr VideoPixelFormat DEFAULT_PIXELFORMAT = VideoPixelFormat::YUV420P; + OH_AVFormat *format = OH_AVFormat_Create(); + // 写入format + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_WIDTH.data(), DEFAULT_WIDTH); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_HEIGHT.data(), DEFAULT_HEIGHT); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_PIXEL_FORMAT.data(), DEFAULT_PIXELFORMAT); + // 配置编码器 + int32_t ret = OH_VideoEncoder_Configure(videoEnc, format); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +4. 调用OH_VideoEncoder_Prepare(),编码器就绪。 + + 该接口将在编码器运行前进行一些数据的准备工作。 + + ``` c++ + ret = OH_VideoEncoder_Prepare(videoEnc); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +5. 调用OH_VideoEncoder_Start()启动编码器,进入运行态。 + + 启动编码器后,回调函数将开始响应事件。所以,需要先配置输入文件、输出文件。 + + ``` c++ + // 配置待编码文件路径 + string_view inputFilePath = "/*yourpath*.yuv"; + string_view outputFilePath = "/*yourpath*.h264"; + std::unique_ptr inputFile = std::make_unique(); + std::unique_ptr outputFile = std::make_unique(); + inputFile->open(inputFilePath.data(), std::ios::in | std::ios::binary); + outputFile->open(outputFilePath.data(), std::ios::out | std::ios::binary | std::ios::ate); + // 启动编码器,开始编码 + int32_t ret = OH_VideoEncoder_Start(videoEnc); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +6. (可选)动态配置编码器实例。 + + ``` c++ + OH_AVFormat *format = OH_AVFormat_Create(); + // 配置视频帧速率 + double frameRate = 30.0; + // 配置视频YUV值范围标志 + bool rangeFlag = false; + // 配置视频原色 + int32_t primary = static_cast(OH_ColorPrimary::COLOR_PRIMARY_BT709); + // 配置传输特性 + int32_t transfer = static_cast(OH_TransferCharacteristic::TRANSFER_CHARACTERISTIC_BT709); + // 配置最大矩阵系数 + int32_t matrix = static_cast(OH_MaxtrixCoefficient::MATRIX_COFFICIENT_IDENTITY); + // 配置编码Profile + int32_t profile = static_cast(AVCProfile::AVC_PROFILE_BASELINE); + // 配置编码比特率模式 + int32_t rateMode = static_cast(VideoEncodeBitrateMode::CBR); + // 配置关键帧的间隔,单位为毫秒 + int32_t iFrameInterval = 23000; + // 配置所需的编码质量。只有在恒定质量模式下配置的编码器才支持此配置 + int32_t quality = 0; + // 配置比特率 + int64_t bitRate = 3000000; + // 写入format + OH_AVFormat_SetDoubleValue(format, MediaDescriptionKey::MD_KEY_FRAME_RATE, frameRate); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_RANGE_FLAG, rangeFlag); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_COLOR_PRIMARIES, primary); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_TRANSFER_CHARACTERISTICS, transfer); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_MATRIX_COEFFICIENTS, matrix); + + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_I_FRAME_INTERVAL, iFrameInterval); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_PROFILE, profile); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_VIDEO_ENCODE_BITRATE_MODE, rateMode); + OH_AVFormat_SetLongValue(format, MediaDescriptionKey::MD_KEY_BITRATE, bitRate); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_QUALITY, quality); + + int32_t ret = OH_VideoEncoder_SetParameter(videoEnc, format); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +7. 调用OH_VideoEncoder_PushInputData(),写入编码码流。 + + 送入输入队列进行编码,以下示例中: + - GetOneFrameSize():计算yuv文件帧长度的函数,具体的计算过程请参阅YUV相关资料。 + - mem:回调函数OnNeedInputData传入的参数,可以通过OH_AVMemory_GetAddr接口得到共享内存地址的指针。 + - index:回调函数OnNeedInputData传入的参数,数据队列的索引。 + + ``` c++ + // 处理文件流得到帧的长度,再将需要编码的数据写入到对应index的mem中 + int32_t frameSize = GetOneFrameSize(); + inputFile->read(reinterpret_cast(OH_AVMemory_GetAddr(mem)), frameSize); + // 配置buffer info信息 + OH_AVCodecBufferAttr info; + info.size = frameSize; + info.offset = 0; + info.pts = 0; + info.flags = AVCODEC_BUFFER_FLAGS_CODEC_DATA; + // 送入编码输入队列进行编码,index为对应输入队列的下标 + int32_t ret = OH_VideoEncoder_PushInputData(videoEnc, index, info); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +8. 通知编码器码流结束。 + + 以下示例中: + index:回调函数OnNeedInputData传入的参数,数据队列的索引。 + 与“步骤6.写入编码码流一样”,使用同一个接口OH_VideoEncoder_PushInputData,通知编码器输入结束,需要对flag标识成AVCODEC_BUFFER_FLAGS_EOS + + ``` c++ + int32_t ret; + OH_AVCodecBufferAttr info; + info.size = 0; + info.offset = 0; + info.pts = 0; + info.flags = AVCODEC_BUFFER_FLAG_EOS; + ret = OH_VideoEncoder_PushInputData(videoEnc, index, info); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +9. 调用OH_VideoEncoder_FreeOutputData(),输出编码帧。 + + 以下示例中: + - index:回调函数OnNeedOutputData传入的参数,数据队列的索引。 + - attr:回调函数OnNeedOutputData传入的参数,输出数据的Buffer信息。 + - mem: 回调函数OnNeedOutputData传入的参数,可以通过OH_AVMemory_GetAddr接口得到共享内存地址的指针。 + + ``` c++ + // 将编码完成帧数据mem写入到对应输出文件中 + outputFile->write(reinterpret_cast(OH_AVMemory_GetAddr(mem)), attr->size); + // 释放已完成写入的数据,index为对应输出队列的下标 + int32_t ret = OH_VideoEncoder_FreeOutputData(videoEnc, index); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +10. (可选)调用OH_VideoEncoder_Flush()刷新编码器。 + + 调用OH_VideoEncoder_Flush()后,编码器仍处于运行态,但会将当前队列清空,将已编码的数据释放。 + + 此时需要调用OH_AudioEncoder_Start()重新开始编码。 + + ``` c++ + int32_t ret; + // 刷新编码器videoEnc + ret = OH_VideoEncoder_Flush(videoEnc); + if (ret != AV_ERR_OK) { + // 异常处理 + } + // 重新开始编码 + ret = OH_VideoEncoder_Start(videoEnc); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +11. (可选)调用OH_VideoEncoder_Reset()重置编码器。 + + 调用OH_VideoEncoder_Reset()后,编码器回到初始化的状态,需要调用OH_VideoEncoder_Configure()重新配置。 + + ``` c++ + int32_t ret; + // 重置编码器videoEnc + ret = OH_VideoEncoder_Reset(videoEnc); + if (ret != AV_ERR_OK) { + // 异常处理 + } + // 重新配置编码器参数 + ret = OH_VideoEncoder_Configure(videoEnc, format); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +12. 调用OH_VideoEncoder_Stop()停止编码器。 + + ``` c++ + int32_t ret; + // 终止编码器videoEnc + ret = OH_VideoEncoder_Stop(videoEnc); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +13. 调用OH_VideoEncoder_Destroy()销毁编码器实例,释放资源。 + + > **注意:** + > + > 执行该步骤之后,需要开发者将videoEnc指向nullptr,防止野指针导致程序错误。 + + ``` c++ + int32_t ret; + // 调用OH_VideoEncoder_Destroy,注销编码器 + ret = OH_VideoEncoder_Destroy(videoEnc); + videoEnc = nullptr; + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +### Surface模式 + +参考以下示例代码,开发者可以完成Surface输入模式下,视频编码的全流程。此处以视频数据输入,编码成H.264格式为例。 +本模块目前仅支持异步模式的数据轮转。 + +1. 创建编码器实例对象。 + + 应用可以通过名称或媒体类型创建解码器。 + + ``` c++ + // 通过 MIME TYPE 创建编码器,系统会根据MIME创建最合适的编码器。 + OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByMime(CodecMimeType::VIDEO_AVC.data()); + ``` + + ```c++ + // 通过codec name创建编码器,应用有特殊需求,比如选择支持某种分辨率规格的编码器,可先查询capability,再根据codec name创建编码器。 + OH_AVCapability *capability = OH_AVCodec_GetCapability(CodecMimeType::VIDEO_AVC.data(), true); + const char *codecName = OH_AVCapability_GetName(capability); + OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByName(codecName); + ``` + +2. 调用OH_VideoEncoder_SetCallback()设置回调函数。 + + > **注意:** + > + > 在回调函数中,对数据队列进行操作时,需要注意多线程冲突的问题。 + + 注册回调函数指针集合OH_AVCodecAsyncCallback,包括: + + - 编码器运行错误 + - 码流信息变化,如声道变化等。 + - 运行过程中需要新的输入数据,即编码器已准备好,可以输入PCM数据。 + - 运行过程中产生了新的输出数据,即编码完成。 + + ``` c++ + // 设置 OnError 回调函数 + static void OnError(OH_AVCodec *codec, int32_t errorCode, void *userData) + { + (void)codec; + (void)errorCode; + (void)userData; + } + + // 设置 OnStreamChanged 回调函数 + static void OnStreamChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData) + { + (void)codec; + (void)format; + (void)userData; + } + + // 设置 OnNeedInputData 回调函数,编码输入帧送入数据队列 + static void OnNeedInputData(OH_AVCodec *codec, uint32_t index, OH_AVMemory *mem, void *userData) + { + (void)userData; + (void)index; + (void)mem; + // surface模式下,该回调函数无作用,用户通过配置的surface输入数据 + } + + // 设置 OnNeedOutputData 回调函数,编码完成帧送入输出队列 + static void OnNeedOutputData(OH_AVCodec *codec, uint32_t index, OH_AVMemory *mem, + OH_AVCodecBufferAttr *attr, void *userData) + { + (void)userData; + // 完成帧buffer对应的index,送入outIndexQueue队列 + // 完成帧的数据mem送入outBufferQueue队列 + // 完成帧的数据格式送入outAttrQueue队列 + // 数据处理,请参考: + // 10. 输出编码帧 + } + + // 配置异步回调,调用 OH_VideoEncoder_SetCallback 接口 + OH_AVCodecAsyncCallback cb = {&OnError, &OnStreamChanged, &OnNeedInputData, &OnNeedOutputData}; + int32_t ret = OH_VideoEncoder_SetCallback(videoEnc, cb, userData); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +3. 调用OH_VideoEncoder_Configure()配置编码器。 + + 目前支持的所有格式都必须配置以下选项:视频帧宽度、视频帧高度、视频像素格式。 + 示例中的变量如下: + - DEFAULT_WIDTH:320像素宽度; + - DEFAULT_HEIGHT:240像素高度; + - DEFAULT_PIXELFORMAT: 像素格式,因为示例使用YUV的文件保存的像素格式是YUV420P,所以设置为VideoPixelFormat::YUV420P。 + + ``` c++ + // 配置视频帧宽度(必须) + constexpr uint32_t DEFAULT_WIDTH = 320; + // 配置视频帧高度(必须) + constexpr uint32_t DEFAULT_HEIGHT = 240; + // 配置视频像素格式(必须) + constexpr VideoPixelFormat DEFAULT_PIXELFORMAT = VideoPixelFormat::YUV420P; + OH_AVFormat *format = OH_AVFormat_Create(); + // 写入format + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_WIDTH.data(), DEFAULT_WIDTH); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_HEIGHT.data(), DEFAULT_HEIGHT); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_PIXEL_FORMAT.data(), DEFAULT_PIXELFORMAT); + // 配置编码器 + int32_t ret = OH_VideoEncoder_Configure(videoEnc, format); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +4. 调用OH_VideoEncoder_Prepare(),编码器就绪。 + + 该接口将在编码器运行前进行一些数据的准备工作。 + + ``` c++ + ret = OH_VideoEncoder_Prepare(videoEnc); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +5. 获取Surface。 + + 获取编码器Surface模式的OHNativeWindow输入,获取Surface需要在启动编码器之前完成。 + + ``` c++ + int32_t ret; + // 获取需要输入的Surface,以进行编码 + OHNativeWindow *nativeWindow; + ret = OH_VideoEncoder_GetSurface(videoEnc, &nativeWindow); + if (ret != AV_ERR_OK) { + // 异常处理 + } + // 通过OHNativeWindow*变量类型,配置输入数据的Surface + ``` + + OHNativeWindow*变量类型的使用方法请参考图形子系统/foundation/graphic/graphic_2d + +6. 调用OH_VideoEncoder_Start()启动编码器。 + + ``` c++ + int32_t ret; + // 启动编码器,开始编码 + ret = OH_VideoEncoder_Start(videoEnc); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +7. (可选)动态配置编码器实例。 + + ``` c++ + OH_AVFormat *format = OH_AVFormat_Create(); + // 配置视频帧速率 + double frameRate = 30.0; + // 配置视频YUV值范围标志 + bool rangeFlag = false; + // 配置视频原色 + int32_t primary = static_cast(OH_ColorPrimary::COLOR_PRIMARY_BT709); + // 配置传输特性 + int32_t transfer = static_cast(OH_TransferCharacteristic::TRANSFER_CHARACTERISTIC_BT709); + // 配置最大矩阵系数 + int32_t matrix = static_cast(OH_MaxtrixCoefficient::MATRIX_COFFICIENT_IDENTITY); + // 配置编码Profile + int32_t profile = static_cast(AVCProfile::AVC_PROFILE_BASELINE); + // 配置编码比特率模式 + int32_t rateMode = static_cast(VideoEncodeBitrateMode::CBR); + // 配置关键帧的间隔,单位为毫秒 + int32_t iFrameInterval = 23000; + // 配置所需的编码质量。只有在恒定质量模式下配置的编码器才支持此配置 + int32_t quality = 0; + // 配置比特率 + int64_t bitRate = 3000000; + // 写入format + OH_AVFormat_SetDoubleValue(format, MediaDescriptionKey::MD_KEY_FRAME_RATE, frameRate); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_RANGE_FLAG, rangeFlag); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_COLOR_PRIMARIES, primary); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_TRANSFER_CHARACTERISTICS, transfer); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_MATRIX_COEFFICIENTS, matrix); + + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_I_FRAME_INTERVAL, iFrameInterval); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_PROFILE, profile); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_VIDEO_ENCODE_BITRATE_MODE, rateMode); + OH_AVFormat_SetLongValue(format, MediaDescriptionKey::MD_KEY_BITRATE, bitRate); + OH_AVFormat_SetIntValue(format, MediaDescriptionKey::MD_KEY_QUALITY, quality); + + int32_t ret = OH_VideoEncoder_SetParameter(videoEnc, format); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +8. 写入编码码流。 + + 在之前的第5步中,开发者已经对OH_VideoEncoder_GetSurface接口返回的OHNativeWindow*类型变量进行配置。 + 因为编码所需的数据,由配置的Surface进行持续地输入,所以开发者无需对OnNeedInputData回调函数进行处理,也无需使用OH_VideoEncoder_PushInputData接口输入数据。 + +9. 调用OH_VideoEncoder_NotifyEndOfStream()通知编码器码流结束。 + + ``` c++ + int32_t ret; + // surface模式:通知视频编码器输入流已结束,只能使用此接口进行通知 + // 不能像buffer模式中将flag设为AVCODEC_BUFFER_FLAGS_EOS,再调用OH_VideoEncoder_PushInputData接口通知编码器输入结束 + ret = OH_VideoEncoder_NotifyEndOfStream(videoEnc); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +10. 调用OH_VideoEncoder_FreeOutputData(),输出编码帧。 + + 以下示例中: + - index:回调函数OnNeedOutputData传入的参数,数据队列的索引。 + - attr:回调函数OnNeedOutputData传入的参数,输出数据的Buffer信息。 + - mem: 回调函数OnNeedOutputData传入的参数,可以通过OH_AVMemory_GetAddr接口得到共享内存地址的指针。 + + ``` c++ + // 将编码完成帧数据mem写入到对应输出文件中 + outputFile->write(reinterpret_cast(OH_AVMemory_GetAddr(mem)), attr->size); + // 释放已完成写入的数据,index为对应输出队列下标 + int32_t ret = OH_VideoEncoder_FreeOutputData(videoEnc, index); + if (ret != AV_ERR_OK) { + // 异常处理 + } + ``` + +后续流程(包括刷新编码器、重置编码器、停止编码器、销毁编码器)与Buffer模式一致,请参考[Buffer模式](#buffer模式)的步骤9-12。