diff --git a/zh-cn/application-dev/media/avcodec/Readme-CN.md b/zh-cn/application-dev/media/avcodec/Readme-CN.md index 85fff2ad0633dca68c06e361e0b8420cebe598c6..172459d040f98fcd95324d513f5221e95c796cc1 100644 --- a/zh-cn/application-dev/media/avcodec/Readme-CN.md +++ b/zh-cn/application-dev/media/avcodec/Readme-CN.md @@ -7,6 +7,7 @@ - [音频编码](audio-encoding.md) - [音频解码](audio-decoding.md) - [视频编码](video-encoding.md) + - [视频编码同步模式](synchronous-video-encoding.md) - [时域可分层视频编码](video-encoding-temporal-scalability.md) - [典型场景的视频编码配置](video-encoding-configuration-typical-scenarios.md) - [ROI视频编码](video-encoding-ROI.md) diff --git a/zh-cn/application-dev/media/avcodec/figures/synchronous-video-decode.png b/zh-cn/application-dev/media/avcodec/figures/synchronous-video-decode.png index 84faac8d53e25bc3663c53d1c266ff043116bda1..b3f1e6e0693af511c28f7209997063c7dc098b02 100644 Binary files a/zh-cn/application-dev/media/avcodec/figures/synchronous-video-decode.png and b/zh-cn/application-dev/media/avcodec/figures/synchronous-video-decode.png differ diff --git a/zh-cn/application-dev/media/avcodec/figures/synchronous-video-encode.png b/zh-cn/application-dev/media/avcodec/figures/synchronous-video-encode.png new file mode 100644 index 0000000000000000000000000000000000000000..9dabbf0db053beb3b107a2e5df492cb21e80a5b8 Binary files /dev/null and b/zh-cn/application-dev/media/avcodec/figures/synchronous-video-encode.png differ diff --git a/zh-cn/application-dev/media/avcodec/synchronous-video-decoding.md b/zh-cn/application-dev/media/avcodec/synchronous-video-decoding.md index 31cbde076a82beecd751058af10179e2535b44a8..cd0c8cf6a7d26a77b226622c815b7d368042717f 100644 --- a/zh-cn/application-dev/media/avcodec/synchronous-video-decoding.md +++ b/zh-cn/application-dev/media/avcodec/synchronous-video-decoding.md @@ -315,7 +315,7 @@ target_link_libraries(sample PUBLIC libnative_media_vdec.so) if (!inputDone) { result = DecoderInput(videoDec, timeoutUs); } - if (!outputDone ) { + if (!outputDone) { result = DecoderOutput(videoDec, timeoutUs); } } @@ -621,7 +621,7 @@ target_link_libraries(sample PUBLIC libnative_media_vdec.so) if (!inputDone) { result = DecoderInput(videoDec, timeoutUs); } - if (!outputDone ) { + if (!outputDone) { result = DecoderOutput(videoDec, timeoutUs); } } diff --git a/zh-cn/application-dev/media/avcodec/synchronous-video-encoding.md b/zh-cn/application-dev/media/avcodec/synchronous-video-encoding.md new file mode 100644 index 0000000000000000000000000000000000000000..505edba0b74dae35c38d1d91f7aa31f363400a6b --- /dev/null +++ b/zh-cn/application-dev/media/avcodec/synchronous-video-encoding.md @@ -0,0 +1,550 @@ +# 视频编码同步模式 + + +开发者可以调用本模块的Native API接口,完成同步模式的视频编码。 + +当前支持的编码能力请参考[AVCodec支持的格式](avcodec-support-formats.md#视频编码)。 + +视频编码的限制约束、支持的能力、状态机调用关系请参考[视频编码](video-encoding.md)。 + +## 适用场景 + +通常情况下,推荐使用异步模式。若需要主动请求buffer去送帧,则可以采用同步模式。 + + +## 开发指导 + +详细的API说明请参考[VideoEncoder](../../reference/apis-avcodec-kit/_video_encoder.md)。 + +- 虚线表示可选。 + +- 实线表示必选。 + +![Invoking relationship of video encode stream](figures/synchronous-video-encode.png) + +### 在CMake脚本中链接动态库 + +``` cmake +target_link_libraries(sample PUBLIC libnative_media_codecbase.so) +target_link_libraries(sample PUBLIC libnative_media_core.so) +target_link_libraries(sample PUBLIC libnative_media_venc.so) +``` + +> **说明:** +> +> 上述'sample'字样仅为示例,此处由开发者根据实际工程目录自定义。 +> + +### 定义基础结构 + +本部分示例代码按照C++17标准编写,仅作参考。 + +1. 添加头文件。 + + ```c++ + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + ``` + +2. 全局变量(仅做参考,可以根据实际情况将其封装到对象中)。 + + ```c++ + // 视频帧宽度。 + int32_t width = 320; + // 视频帧高度。 + int32_t height = 240; + // 视频像素格式。 + OH_AVPixelFormat pixelFormat = AV_PIXEL_FORMAT_NV12; + // 编码器同步锁。 + std::shared_mutex codecMutex; + // 编码器实例指针。 + OH_AVCodec *videoEnc = nullptr; + // 编码输出。 + bool outputDone = false; + // 编码输入。 + bool inputDone = false; + std::unique_ptr inFile_; + ``` + +### Surface模式 + +参考以下示例代码,开发者可以完成Surface模式下视频编码的全流程,实现同步模式的数据轮转。此处以surface数据输入,编码成H.264格式为例。 + + +1. 创建编码器实例。 + + 通过名称创建编码器。示例中的变量说明如下: + + - videoEnc:视频编码器实例的指针。 + - capability:编码器能力查询实例的指针。 + - OH_AVCODEC_MIMETYPE_VIDEO_AVC:AVC格式视频编解码器。 + + ```c++ + // 创建硬件编码器实例。 + OH_AVCapability *capability= OH_AVCodec_GetCapabilityByCategory(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true, HARDWARE); + const char *name = OH_AVCapability_GetName(capability); + OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByName(name); + if (videoEnc == nullptr) { + printf("create videoEnc failed"); + return; + } + ``` + +2. 调用OH_VideoEncoder_Configure()配置编码器。 + + - 详细可配置选项的说明请参考[媒体数据键值对](../../reference/apis-avcodec-kit/_codec_base.md#媒体数据键值对)。 + - 参数校验规则请参考[OH_VideoEncoder_Configure()](../../reference/apis-avcodec-kit/_video_encoder.md#oh_videoencoder_configure)。 + - 参数取值范围可以通过能力查询接口获取,具体示例请参考[获取支持的编解码能力](obtain-supported-codecs.md)。 + + 目前支持的所有格式都必须配置以下选项:视频帧宽度、视频帧高度、视频像素格式。 + + ```c++ + + OH_AVFormat *format = OH_AVFormat_Create(); + // 写入format。 + OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, width); // 必须配置。 + OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, height); // 必须配置。 + OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, pixelFormat);// 必须配置。 + OH_AVFormat_SetIntValue(format, OH_MD_KEY_ENABLE_SYNC_MODE, 1); // 同步模式配置。 + // 配置编码器。 + int32_t ret = OH_VideoEncoder_Configure(videoEnc, format); + if (ret != AV_ERR_OK) { + // 异常处理。 + } + OH_AVFormat_Destroy(format); + ``` + + > **注意:** + > + > 1. 使能视频编码同步模式,必须要配置OH_MD_KEY_ENABLE_SYNC_MODE为1。 + > 2. 同步模式在调用OH_VideoEncoder_Configure接口前不能调用OH_VideoEncoder_RegisterCallback或OH_VideoEncoder_RegisterParameterCallback接口,否则为异步模式。 + > 3. 不支持Surface模式的随帧通路的同步模式。 + > + +3. 设置surface。 + + 示例中的变量说明如下: + - nativeWindow:获取方式请参考[视频编码Surface模式](video-encoding.md#surface模式)的“步骤-6:设置surface”。 + + ```c++ + // 获取需要输入的surface,以进行编码。 + int32_t ret = OH_VideoEncoder_GetSurface(videoEnc, &nativeWindow); + if (ret != AV_ERR_OK) { + // 异常处理。 + } + ``` + +4. 调用OH_VideoEncoder_Prepare()编码器就绪。 + + 该接口将在编码器运行前进行一些数据的准备工作。 + + ```c++ + int32_t ret = OH_VideoEncoder_Prepare(videoEnc); + if (ret != AV_ERR_OK) { + // 异常处理。 + } + ``` + +5. 调用调用OH_VideoEncoder_Start()启动编码器。 + + ```c++ + // 配置待编码文件路径。 + std::string_view outputFilePath = "/*yourpath*.h264"; + std::unique_ptr outputFile = std::make_unique(); + 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. 调用OH_VideoEncoder_FreeOutputBuffer()释放编码帧 + + ```c++ + bool EncoderOutput(OH_AVCodec *videoEnc, int64_t timeoutUs) + { + uint32_t index; + std::shared_lock lock(codecMutex); + + OH_AVErrCode ret = OH_VideoEncoder_QueryOutputBuffer(videoEnc, &index, timeoutUs); + switch (ret) { + case AV_ERR_OK: { + OH_AVBuffer *buffer = OH_VideoEncoder_GetOutputBuffer(videoEnc, index); + if (buffer == nullptr) { + // 异常处理。 + return false; + } + + // 获取编码后信息。 + OH_AVCodecBufferAttr info; + int32_t ret = OH_AVBuffer_GetBufferAttr(buffer, &info); + if (ret != AV_ERR_OK) { + // 异常处理。 + return false; + } + if (info.flags & AVCODEC_BUFFER_FLAGS_EOS) { + outputDone = 1; + break; + } + + // 将编码完成帧数据buffer写入到对应输出文件中。 + uint8_t *addr = OH_AVBuffer_GetAddr(buffer); + if (addr == nullptr) { + // 异常处理 + return false; + } + outputFile->write(reinterpret_cast(addr), info.size); + // 释放已完成写入的数据,index为对应输出队列下标 + ret = OH_VideoEncoder_FreeOutputBuffer(videoEnc, index); + if (ret != AV_ERR_OK) { + // 异常处理。 + return false; + } + break; + } + case AV_ERR_TRY_AGAIN_LATER: { + break; + } + case AV_ERR_STREAM_CHANGED: { + OH_AVFormat *format = OH_VideoEncoder_GetOutputDescription(videoEnc); + // 获取新宽高。 + OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_WIDTH, &width); + OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_HEIGHT, &height); + OH_AVFormat_Destroy(format); + break; + } + default: { + // 异常处理。 + return false; + } + } + return true; + } + ``` + +7. 编码输出。 + + ```c++ + bool result = true; + int64_t timeoutUs = 0; // 单位:微秒(us),负值:无限等待;0:立即退出;正值:指定时间timeout后退出。 + + while (!outputDone && result) { + if (!outputDone ) { + result = EncoderOutput(videoEnc, timeoutUs); + } + } + ``` + +8. (可选)调用OH_VideoEncoder_Flush()刷新编码器。 + + 调用OH_VideoEncoder_Flush接口后,编码器仍处于运行态,但会清除编码器中缓存的输入和输出数据及参数集如H.264格式的PPS/SPS。 + 此时需要调用[OH_VideoEncoder_Start](../../reference/apis-avcodec-kit/_video_encoder.md#oh_videoencoder_start)接口重新开始编码。 + + ```c++ + // 通过codecMutex来避免调用Flush接口,状态切换后,编码线程还在跑会退出循环的问题。 + std::unique_lock lock(codecMutex); + // 刷新编码器videoEnc。 + int32_t ret = OH_VideoEncoder_Flush(videoEnc); + if (ret != AV_ERR_OK) { + // 异常处理。 + } + + // 重新开始编码。 + ret = OH_VideoEncoder_Start(videoEnc); + if (ret != AV_ERR_OK) { + // 异常处理。 + } + ``` + +9. (可选)调用OH_VideoEncoder_Reset()重置编码器。 + + 调用OH_VideoEncoder_Reset接口后,编码器回到初始化的状态,需要调用接口[OH_VideoEncoder_Configure](../../reference/apis-avcodec-kit/_video_encoder.md#oh_videoencoder_configure)和[OH_VideoEncoder_Prepare](../../reference/apis-avcodec-kit/_video_encoder.md#oh_videoencoder_prepare)重新配置。 + + ```c++ + // 重置编码器videoEnc。 + std::unique_lock lock(codecMutex); + int32_t ret = OH_VideoEncoder_Reset(videoEnc); + if (ret != AV_ERR_OK) { + // 异常处理。 + } + + // 重新配置编码器参数。 + OH_AVFormat *format = OH_AVFormat_Create(); + ret = OH_VideoEncoder_Configure(videoEnc, format); + if (ret != AV_ERR_OK) { + // 异常处理。 + } + OH_AVFormat_Destroy(format); + + // 编码器重新就绪。 + ret = OH_VideoEncoder_Prepare(videoEnc); + if (ret != AV_ERR_OK) { + // 异常处理。 + } + ``` + + > **注意:** + > + > 编码器回到初始化的状态,调用OH_VideoEncoder_Configure接口重新配置编码器参数时,同步模式需要重新配置OH_MD_KEY_ENABLE_SYNC_MODE为1,否则为异步模式。 + > + +10. (可选)调用OH_VideoEncoder_Stop()停止编码器。 + + 调用OH_VideoEncoder_Stop接口后,编码器保留了编码实例,释放输入输出buffer。开发者可以直接调用OH_VideoEncoder_Start接口继续编码,输入的第一个buffer需要携带参数集,从IDR帧开始送入。 + + ```c++ + // 终止编码器videoEnc。 + std::unique_lock lock(codecMutex); + int32_t ret = OH_VideoEncoder_Stop(videoEnc); + if (ret != AV_ERR_OK) { + // 异常处理。 + } + ``` + +11. 调用OH_VideoEncoder_Destroy()销毁编码器实例,释放资源。 + + ```c++ + // 注销编码器。 + std::unique_lock lock(codecMutex); + int32_t ret = AV_ERR_OK; + if (videoEnc != nullptr) { + ret = OH_VideoEncoder_Destroy(videoEnc); + videoEnc = nullptr; + } + if (ret != AV_ERR_OK) { + // 异常处理。 + } + ``` + + > **说明:** + > + > 执行该步骤之后,需要开发者将videoEnc指向nullptr,防止野指针导致程序错误。 + > + +### Buffer模式 + +参考以下示例代码,开发者可以完成Buffer模式下视频编码的全流程,实现同步模式的数据轮转。此处以YUV文件输入,编码成H.264格式为例。 + +1. 创建编码器实例。 + + 与Surface模式相同,此处不再赘述。 + + ```c++ + // 通过codecname创建编码器,应用有特殊需求,比如选择支持某种分辨率规格的编码器,可先查询capability,再根据codec name创建编码器。 + OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true); + const char *name = OH_AVCapability_GetName(capability); + OH_AVCodec *videoEnc = OH_videoEncoder_CreateByName(name); + if (videoEnc == nullptr) { + printf("create videoEnc failed"); + return; + } + ``` + +2. 调用OH_VideoEncoder_Configure()配置编码器。 + + 与Surface模式相同,此处不再赘述。 + + ```c++ + OH_AVFormat *format = OH_AVFormat_Create(); + // 写入format。 + OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, width); // 必须配置。 + OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, height); // 必须配置。 + OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, pixelFormat);// 必须配置。 + OH_AVFormat_SetIntValue(format, OH_MD_KEY_ENABLE_SYNC_MODE, 1); // 同步模式配置。 + // 配置编码器。 + int32_t ret = OH_VideoEncoder_Configure(videoEnc, format); + if (ret != AV_ERR_OK) { + // 异常处理。 + } + OH_AVFormat_Destroy(format); + ``` + + > **注意:** + > + > 1. 使能视频编码同步模式,必须要配置OH_MD_KEY_ENABLE_SYNC_MODE为1。 + > 2. 同步模式在调用OH_VideoEncoder_Configure接口前不能调用OH_VideoEncoder_RegisterCallback或OH_VideoEncoder_RegisterParameterCallback接口,否则为异步模式。 + > + +3. 调用OH_VideoEncoder_Prepare()编码器就绪。 + + 该接口将在编码器运行前进行一些数据的准备工作。 + + ```c++ + ret = OH_VideoEncoder_Prepare(videoEnc); + if (ret != AV_ERR_OK) { + // 异常处理。 + } + ``` + +4. 调用OH_VideoEncoder_Start()启动编码器。 + + 配置输入文件、输出文件。 + + ```c++ + // 配置待编码文件路径。 + std::string_view inputFilePath = "/*yourpath*.yuv"; + std::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) { + // 异常处理。 + } + ``` + +5. 调用OH_VideoEncoder_PushInputBuffer()写入编码码流。 + + 与Surface模式相同,此处不再赘述。 + + ```c++ + bool EncoderInput(OH_AVCodec *videoEnc, int64_t timeoutUs) + { + uint32_t index; + std::shared_lock lock(codecMutex); + + OH_AVErrCode ret = OH_VideoEncoder_QueryInputBuffer(videoEnc, &index, timeoutUs); + switch (ret) { + case AV_ERR_OK: { + OH_AVBuffer *buffer = OH_VideoEncoder_GetInputBuffer(videoEnc, index); + if (buffer == nullptr) { + // 异常处理。 + return false; + } + + // 写入图像数据。 + int32_t frameSize = 0; + if (widthStride == width && heightStride == height) { + frameSize = width * height * 3 / 2; // NV12像素格式下,每帧数据大小的计算公式。 + // 处理文件流得到帧的长度,再将需要编码的数据写入到对应index的buffer中。 + uint8_t *addr = OH_AVBuffer_GetAddr(buffer); + if (addr == nullptr) { + // 异常处理 + return false; + } + inputFile->read(reinterpret_cast(addr), frameSize); + } else { + // 如果跨距不等于宽,需要开发者按照跨距进行偏移,视频编码Buffer“步骤-8. 写入编码图像”。 + } + + // 配置buffer info信息。 + OH_AVCodecBufferAttr info; + info.size = frameSize; + info.offset = 0; + info.pts = 0; + int32_t ret = OH_AVBuffer_SetBufferAttr(buffer, &info); + if (ret != AV_ERR_OK) { + // 异常处理。 + return false; + } + // 送入编码输入队列进行编码,index为对应输入队列的下标。 + ret = OH_VideoEncoder_PushInputBuffer(videoEnc, index); + if (ret != AV_ERR_OK) { + // 异常处理。 + return false; + } + if (inFile_->eof()) { + inputDone = 1; + break; + } + break; + } + case AV_ERR_TRY_AGAIN_LATER: { + break; + } + default: { + // 异常处理。 + return false; + } + } + return true; + } + ``` + +6. 调用OH_VideoEncoder_FreeOutputBuffer()释放编码帧。 + + ```c++ + bool EncoderOutput(OH_AVCodec *videoEnc, int64_t timeoutUs) + { + uint32_t index; + std::shared_lock lock(codecMutex); + + OH_AVErrCode ret = OH_VideoEncoder_QueryOutputBuffer(videoEnc, &index, timeoutUs); + switch (ret) { + case AV_ERR_OK: { + OH_AVBuffer *buffer = OH_VideoEncoder_GetOutputBuffer(videoEnc, index); + if (buffer == nullptr) { + // 异常处理。 + return false; + } + + // 获取编码后信息。 + OH_AVCodecBufferAttr info; + int32_t ret = OH_AVBuffer_GetBufferAttr(buffer, &info); + if (ret != AV_ERR_OK) { + // 异常处理。 + return false; + } + // 将编码完成帧数据buffer写入到对应输出文件中。 + uint8_t *addr = OH_AVBuffer_GetAddr(buffer); + if (addr == nullptr) { + // 异常处理 + return false; + } + outputFile->write(reinterpret_cast(addr), info.size); + if (info.flags & AVCODEC_BUFFER_FLAGS_EOS) { + outputDone = 1; + break; + } + // 释放已完成处理的信息,index为对应buffer队列的下标。 + ret = OH_VideoEncoder_FreeOutputBuffer(videoEnc, index); + if (ret != AV_ERR_OK) { + // 异常处理。 + return false; + } + break; + } + case AV_ERR_TRY_AGAIN_LATER: { + break; + } + case AV_ERR_STREAM_CHANGED: { + break; + } + default: { + // 异常处理。 + return false; + } + } + return true; + } + ``` + +7. 编码输入输出。 + + ```c++ + bool result = true; + int64_t timeoutUs = 0; // 单位:微秒(us),负值:无限等待;0:立即退出;正值:指定时间timeout后退出。 + + while (!outputDone && result) { + if (!inputDone) { + result = EncoderInput(videoEnc, timeoutUs); + } + if (!outputDone) { + result = EncoderOutput(videoEnc, timeoutUs); + } + } + ``` + +后续流程(包括刷新编码器、重置编码器、停止编码器、销毁编码器)与Surface模式基本一致,请参考[Surface模式](#surface模式)的步骤8-11。 \ No newline at end of file diff --git a/zh-cn/application-dev/media/avcodec/video-encoding.md b/zh-cn/application-dev/media/avcodec/video-encoding.md index 423e67c4085bc28a6a1c673ad6763571e24179ae..00b337a7e9d931ad0e90495f8f14b57fd2de5787 100644 --- a/zh-cn/application-dev/media/avcodec/video-encoding.md +++ b/zh-cn/application-dev/media/avcodec/video-encoding.md @@ -195,8 +195,7 @@ target_link_libraries(sample PUBLIC libnative_media_venc.so) ### Surface模式 -参考以下示例代码,开发者可以完成Surface模式下视频编码的全流程。此处以surface数据输入,编码成H.264格式为例。 -本模块目前仅支持异步模式的数据轮转。 +参考以下示例代码,开发者可以完成Surface模式下视频编码的全流程,实现异步模式的数据轮转。此处以surface数据输入,编码成H.264格式为例。 1. 添加头文件。 @@ -626,8 +625,7 @@ target_link_libraries(sample PUBLIC libnative_media_venc.so) ### Buffer模式 -参考以下示例代码,开发者可以完成Buffer模式下视频编码的全流程。此处以YUV文件输入,编码成H.264格式为例。 -本模块目前仅支持异步模式的数据轮转。 +参考以下示例代码,开发者可以完成Buffer模式下视频编码的全流程,实现异步模式的数据轮转。此处以YUV文件输入,编码成H.264格式为例。 1. 添加头文件。 diff --git a/zh-cn/application-dev/website.md b/zh-cn/application-dev/website.md index 5b779614e86d17649dbb4c9886d161c2f917b630..776803b4fa4a69ed2220f36833fff34f9b413640 100644 --- a/zh-cn/application-dev/website.md +++ b/zh-cn/application-dev/website.md @@ -1539,6 +1539,7 @@ - [音频编码](media/avcodec/audio-encoding.md) - [音频解码](media/avcodec/audio-decoding.md) - [视频编码](media/avcodec/video-encoding.md) + - [视频编码同步模式](media/avcodec/synchronous-video-encoding.md) - [时域可分层视频编码](media/avcodec/video-encoding-temporal-scalability.md) - [典型场景的视频编码配置](media/avcodec/video-encoding-configuration-typical-scenarios.md) - [ROI视频编码](media/avcodec/video-encoding-ROI.md)