HDR Vivid视频播放
开发者可以调用本模块的Native API接口,实现在视频播放中支持HDR Vivid标准。
视频播放的主要流程,是将视频文件“解封装 > 解码 > 送显/播放”。
HDR Vivid视频解析
从视频文件中,可以解析出其是否为HDR Vivid视频,如果视频源为HDR Vivid视频,可以解析相关的信息,如元数据、颜色信息(Color)等。
在 CMake 脚本中链接动态库
target_link_libraries(sample PUBLIC libnative_media_codecbase.so)
target_link_libraries(sample PUBLIC libnative_media_avdemuxer.so)
target_link_libraries(sample PUBLIC libnative_media_avsource.so)
target_link_libraries(sample PUBLIC libnative_media_core.so)
上述'sample'字样仅为示例,此处由开发者根据实际工程目录自定义。
开发步骤
-
添加头文件。
#include <multimedia/player_framework/native_avdemuxer.h>#include <multimedia/player_framework/native_avsource.h>#include <multimedia/player_framework/native_avcodec_base.h>#include <multimedia/player_framework/native_avformat.h>#include <multimedia/player_framework/native_avbuffer.h>#include <fcntl.h>#include <sys/stat.h>#include <string> -
文件解析器。
// 创建文件操作符 fd,打开时对文件实例必须有读权限(filePath 为待解封装文件路径,需预置文件,保证路径指向的文件存在)。std::string filePath = "test.mp4";int fd = open(filePath.c_str(), O_RDONLY);struct stat fileStatus {};// 获取fileSize。size_t fileSize = 0;if (stat(filePath.c_str(), &fileStatus) == 0) {fileSize = static_cast<size_t>(fileStatus.st_size);} else {printf("get stat failed");return;}// 为 fd 资源文件创建 source 资源实例。OH_AVSource *source = OH_AVSource_CreateWithFD(fd, 0, fileSize);if (source == nullptr) {printf("create source failed");return;} -
获取视频轨道信息,查询文件HDR类型。
int32_t trackCount = 0;uint32_t audioTrackIndex = 0;uint32_t videoTrackIndex = 0;int32_t trackType;// 从文件 source 信息获取文件轨道数。OH_AVFormat *sourceFormat = OH_AVSource_GetSourceFormat(source);if (sourceFormat == nullptr) {printf("get source format failed");return;}bool getTrackRet = OH_AVFormat_GetIntValue(sourceFormat, OH_MD_KEY_TRACK_COUNT, &trackCount);if (!getTrackRet) {// 异常处理。}OH_AVFormat_Destroy(sourceFormat);for (uint32_t index = 0; index < (static_cast<int32_t>(trackCount)); index++) {// 获取轨道信息。OH_AVFormat *format = OH_AVSource_GetTrackFormat(source, index);if (format == nullptr) {printf("get track format failed");return;}// 判断轨道类型。static_cast<OH_MediaType>(trackType) == OH_MediaType::MEDIA_TYPE_AUD ? audioTrackIndex = index : videoTrackIndex = index;// 查询文件HDR类型,是否为HDR Vivid视频。int32_t isHDRVivid = 0;bool getHdrRet = OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_IS_HDR_VIVID, &isHDRVivid);if (getHdrRet == false || isHDRVivid == 0) {printf("is not HDRVivid ");return;}OH_AVFormat_Destroy(format); // 销毁。}
HDR Vivid视频解码
应用创建H.265解码器,并配置宽、高、format信息。解码器解析码流,生成对应的视频帧数据以及元数据。
当前支持surface输出与buffer输出两种类型,差异如下:
在接口调用的过程中,两种方式的接口调用方式基本一致,但存在以下差异点:
- Surface模式下,应用在解码器就绪前,必须调用OH_VideoDecoder_SetSurface接口设置OHNativeWindow。
- Buffer模式下,可以通过调用OH_AVBuffer_GetNativeBuffer接口将buffer转换为nativebuffer,再通过调用OH_NativeBuffer_GetMetadataValue接口获取元数据。
在 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_vdec.so)
上述'sample'字样仅为示例,此处由开发者根据实际工程目录自定义。
定义基础结构
本部分示例代码按照C++17标准编写,仅作参考。开发者可以参考此部分,定义自己的buffer对象。
-
添加头文件。
#include <condition_variable>#include <memory>#include <mutex>#include <queue>#include <shared_mutex>#include <multimedia/player_framework/native_avcodec_videodecoder.h>#include <multimedia/player_framework/native_avcapability.h>#include <multimedia/player_framework/native_avcodec_base.h>#include <multimedia/player_framework/native_avformat.h>#include <multimedia/player_framework/native_avbuffer.h>#include <fstream> -
解码器回调buffer的信息。
struct CodecBufferInfo {CodecBufferInfo(uint32_t index, OH_AVBuffer *buffer): index(index), buffer(buffer), isValid(true) {}// 回调buffer。OH_AVBuffer *buffer = nullptr;// 回调buffer对应的index。uint32_t index = 0;// 判断当前buffer信息是否有效。bool isValid = true;}; -
解码输入输出队列。
class CodecBufferQueue {public:// 将回调buffer的信息传入队列。void Enqueue(const std::shared_ptr<CodecBufferInfo> bufferInfo){std::unique_lock<std::mutex> lock(mutex_);bufferQueue_.push(bufferInfo);cond_.notify_all();}// 获取回调buffer的信息。std::shared_ptr<CodecBufferInfo> Dequeue(int32_t timeoutMs = 1000){std::unique_lock<std::mutex> lock(mutex_);(void)cond_.wait_for(lock, std::chrono::milliseconds(timeoutMs), [this]() { return !bufferQueue_.empty(); });if (bufferQueue_.empty()) {return nullptr;}std::shared_ptr<CodecBufferInfo> bufferInfo = bufferQueue_.front();bufferQueue_.pop();return bufferInfo;}// 清空队列,之前的回调buffer设置为不可用。void Flush(){std::unique_lock<std::mutex> lock(mutex_);while (!bufferQueue_.empty()) {std::shared_ptr<CodecBufferInfo> bufferInfo = bufferQueue_.front();// Flush、Stop、Reset、Destroy操作之后,之前回调的buffer信息设置为无效。bufferInfo->isValid = false;bufferQueue_.pop();}}private:std::mutex mutex_;std::condition_variable cond_;std::queue<std::shared_ptr<CodecBufferInfo>> bufferQueue_;}; -
全局变量。
仅作参考,可以根据实际情况将其封装到对象中。
// 解码器实例指针。OH_AVCodec *videoDec = nullptr;// 解码器同步锁。std::shared_mutex codecMutex;// 解码器输入队列。CodecBufferQueue inQueue;// 解码器输出队列。CodecBufferQueue outQueue;
开发步骤
Surface模式
-
创建H.265解码器实例。
应用可以通过名称或媒体类型创建解码器。示例中的变量说明如下:
- videoDec:视频解码器实例的指针。
- OH_AVCODEC_MIMETYPE_VIDEO_HEVC:HEVC格式视频编解码器。
// 通过mimetype创建H.265解码器实例。OH_AVCodec *videoDec = OH_VideoDecoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_HEVC); -
配置异步回调函数。
// 解码输入回调OH_AVCodecOnNeedInputBuffer实现。static void OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData){// 输入帧的数据buffer和对应的index送入inQueue队列。(void)codec;(void)userData;inQueue.Enqueue(std::make_shared<CodecBufferInfo>(index, buffer));}具体可参考:视频解码Surface模式中的“步骤-3:调用OH_VideoDecoder_RegisterCallback()设置回调函数”。
-
配置解码器。
具体可参考:视频解码Surface模式中的“步骤-5:调用OH_VideoDecoder_Configure()配置解码器”。
-
设置surface。
具体可参考:视频解码Surface模式中的“步骤-6:设置surface”。
-
调用OH_VideoDecoder_Start()启动解码器。
具体可参考:视频解码Surface模式中的“步骤-8:调用OH_VideoDecoder_Start()启动解码器”。
Buffer模式
-
创建H.265解码器实例。
应用可以通过名称或媒体类型创建解码器。示例中的变量说明如下:
- videoDec:视频解码器实例的指针。
- OH_AVCODEC_MIMETYPE_VIDEO_HEVC:HEVC格式视频编解码器。
// 通过mimetype创建H.265解码器实例。OH_AVCodec *videoDec = OH_VideoDecoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_HEVC); -
配置异步回调函数。
// 解码输入回调OH_AVCodecOnNeedInputBuffer实现。static void OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData){// 输入帧的数据buffer和对应的index送入inQueue队列。(void)codec;(void)userData;inQueue.Enqueue(std::make_shared<CodecBufferInfo>(index, buffer));}// 解码输出回调OH_AVCodecOnNewOutputBuffer实现。static void OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData){// 完成帧的数据buffer和对应的index送入outQueue队列。(void)userData;outQueue.Enqueue(std::make_shared<CodecBufferInfo>(index, buffer));}具体可参考:视频解码Buffer模式中的“步骤-3:调用OH_VideoDecoder_RegisterCallback()设置回调函数”。
-
配置解码器。
具体可参考:视频解码Buffer模式中的“步骤-5:调用OH_VideoDecoder_Configure()配置解码器”。
-
调用OH_VideoDecoder_Start()启动解码器。
具体可参考:视频解码Buffer模式中的“步骤-7:调用OH_VideoDecoder_Start()启动解码器”。
-
获取元数据。
在 CMake 脚本中链接动态库。
target_link_libraries(sample PUBLIC libnative_buffer.so)添加头文件。
#include <string.h>#include <native_buffer/native_buffer.h>示例代码如下:
// 元数据的大小。int32_t size = 0;// 元数据实例指针。uint8_t *metadata = nullptr;// 存储元数据的容器。std::vector<uint8_t> meta;// 取出回调函数OnNewOutputBuffer存到输出队列的帧buffer。std::shared_ptr<CodecBufferInfo> bufferInfo = outQueue.Dequeue();std::shared_lock<std::shared_mutex> lock(codecMutex);if (bufferInfo == nullptr || !bufferInfo->isValid) {// 异常处理。}// 获取OH_NativeBuffer指针实例。OH_NativeBuffer *nativeBuffer = OH_AVBuffer_GetNativeBuffer(bufferInfo.buffer);if (nativeBuffer != nullptr){// 获取static元数据。if (OH_NativeBuffer_GetMetadataValue(nativeBuffer, OH_HDR_STATIC_METADATA, &size, &metadata) != 0){// 异常处理。} else {meta.resize(size);memcpy(&meta[0], metadata, size);delete[] metadata;metadata = nullptr;}// 获取dynamic元数据。if (OH_NativeBuffer_GetMetadataValue(nativeBuffer, OH_HDR_DYNAMIC_METADATA, &size, &metadata) != 0){// 异常处理。} else {meta.resize(size);memcpy(&meta[0], metadata, size);delete[] metadata;metadata = nullptr;}}//销毁nativebuffer。if (nativeBuffer != nullptr) {OH_NativeBuffer_Unreference(nativeBuffer);nativeBuffer = nullptr;}
处理视频帧数据
-
解封装,循环获取帧数据。
bool videoIsEnd = false;// 为资源实例创建对应的解封装器。OH_AVDemuxer *demuxer = OH_AVDemuxer_CreateWithSource(source);// 取出回调函数OnNeedInputBuffer存到输入队列的帧buffer。std::shared_ptr<CodecBufferInfo> bufferInfo = inQueue.Dequeue();std::shared_lock<std::shared_mutex> lock(codecMutex);if (bufferInfo == nullptr || !bufferInfo->isValid) {// 异常处理。}// 解封装帧数据。int32_t ret = OH_AVDemuxer_ReadSampleBuffer(demuxer, videoTrackIndex, bufferInfo->buffer);if (ret == AV_ERR_OK) {// 可通过buffer获取并处理视频帧数据。OH_AVCodecBufferAttr info;OH_AVErrCode getBufferRet = OH_AVBuffer_GetBufferAttr(bufferInfo->buffer, &info);if (getBufferRet != AV_ERR_OK) {// 异常处理。}if (info.flags == OH_AVCodecBufferFlags::AVCODEC_BUFFER_FLAGS_EOS) {videoIsEnd = true;}} -
将解封装后的视频帧数据送入解码输入队列。
// 送入解码输入队列进行解码,index为对应队列下标。ret = OH_VideoDecoder_PushInputBuffer(videoDec, bufferInfo->index);if (ret != AV_ERR_OK) {// 异常处理。}后续步骤具体可参考:视频解码。