From 54ce0aad192860681e75aeb1566aa1af634bd1ff Mon Sep 17 00:00:00 2001 From: chenhaiwen Date: Wed, 31 Aug 2022 15:05:39 +0800 Subject: [PATCH] Add HDI demo design Signed-off-by: chenhaiwen --- design/codec_hdi/hdi2.0_usage_demo.md | 566 ++++++++++++++++++++++++++ 1 file changed, 566 insertions(+) create mode 100644 design/codec_hdi/hdi2.0_usage_demo.md diff --git a/design/codec_hdi/hdi2.0_usage_demo.md b/design/codec_hdi/hdi2.0_usage_demo.md new file mode 100644 index 0000000..378d851 --- /dev/null +++ b/design/codec_hdi/hdi2.0_usage_demo.md @@ -0,0 +1,566 @@ +# CODEC HDI 2.0 + +- **[CODEC HDI 2.0概述](#section1000)** + +- **[CODEC HDI 2.0框架介绍](#section2000)** + +- **[CODEC HDI 2.0开发](#section3000)** + +- **[CODEC HDI 2.0开发实例](#section4000)** + - [接口及回调初始化](#section4100) + - [组件初始化](#section4200) + - [设置编解码参数和配置信息](#section4300) + - [申请输入输出Buffer](#section4400) + - [编解码](#section4500) + - [组件回调处理](#section4600) + - [组件释放](#section4700) + +- **[总结](#section9999)** + +# CODEC HDI 2.0概述 + +Codec HDI2.0 提供了基于OMX的一整套编解码接口,程序通过简单的几步,即可实现基于OMX的硬件编解码。 + +本文主要介绍基于Codec HDI2.0 开发的编解码功能,包括初始化,编解码以及释放等。 + + + +# CODEC HDI 2.0框架介绍 + +Codec HDI 2.0 驱动框架基于[HDF驱动框架](https://device.harmonyos.com/cn/docs/documentation/guide/driver-hdf-overview-0000001051715456)实现。Codec HDI 2.0 驱动架构组成: +![](Codec框架图.png) + + +Codec HDI Interface 2.0(简称HDI2.0)提供基于OpenMax 的标准接口,Media Service 调用这套接口实现硬件编解码能力。HDI 另外提供了实现Callback 机制的接口,Media Service 可以通过HDI 接口创建Callback 信息接收服务端(Codec HDI Callback Remote Service),并且通过SetCallback 方法将对应的客户端代理(Codec HDI Callback Client Proxy)传给OpenMax 实现层,OpenMax 在数据Buffer 处理过程中会通过此代理调用回调方法,告知Media Service 进行对应的数据处理。 + + + + +# CODEC HDI 2.0开发实例 + +代码路径: + + drivers\peripheral\codec\test\demo\codec_hdi_decode.cpp(解码Demo,本文以解码为例) + drivers\peripheral\codec\test\demo\codec_hdi_encode.cpp(编码Demo) + +下面以rk3568为例,介绍codec HDI 2.0开发步骤。 + +CODEC HDI 2.0开发主要包含如下几个重要步骤: + +1. 接口及回调初始化 +2. 组件初始化 +3. 设置编解码参数和配置信息 +4. 申请输入输出Buffer +5. 编解码 +6. 组件回调处理 +7. 组件释放 + + + + +## 接口及回调初始化 + +Codec HDI2.0 接口及回调初始化包括一下主要操作: + + + //初始化HDI2.0 ComponentManager实例,用于打开OMX组件 + omxMgr_ = GetCodecComponentManager(); + + //初始化回调 + callback_ = CodecCallbackTypeStubGetInstance(); + if (!omxMgr_ || !callback_) { + FUNC_EXIT_ERR(); + return false; + } + //设置回调函数指针 + callback_->EventHandler = &OMXCore::OnEvent; + callback_->EmptyBufferDone = &OMXCore::OnEmptyBufferDone; + callback_->FillBufferDone = &OMXCore::OnFillBufferDone; + + +## 组件初始化 + +组件初始化包括打开组件并获取组件的版本号。 + + + //新建组件实例,组件实例为 client_ 后续接口中需要使用 client_ + uint32_t err = HDF_SUCCESS; + if (codec == codecMime::AVC) { + err = omxMgr_->CreateComponent(&client_, "OMX.rk.video_decoder.avc", 0, 0, callback_); + } else { + err = omxMgr_->CreateComponent(&client_, "OMX.rk.video_decoder.hevc", 0, 0, callback_); + } + + //获取组件版本号 + struct CompVerInfo verInfo; + memset(&verInfo, 0, sizeof(verInfo)); + err = client_->GetComponentVersion(client_, &verInfo); + if (err != HDF_SUCCESS) { + HDF_LOGE("%{public}s failed to CreateComponent", __func__); + return false; + } + + + +## 设置编解码参数和配置信息 + +codec HDI2.0 编解码参数配置,包括输入输出数据的宽和高,输入数据格式和输出数据格式 + + + //设置输入端口图片的宽高 + OMX_PARAM_PORTDEFINITIONTYPE param; + InitParam(param); + param.nPortIndex = (uint32_t)PortIndex::kPortIndexInput; + auto err = client_->GetParameter(client_, OMX_IndexParamPortDefinition, (int8_t *)¶m, sizeof(param)); + if (err != HDF_SUCCESS) { + HDF_LOGE("%{public}s failed PortIndex::kPortIndexInput, index is OMX_IndexParamPortDefinition", __func__); + return false; + } + HDF_LOGI("PortIndex::kPortIndexInput: eCompressionFormat = %{public}d, eColorFormat = %{public}d ", + param.format.video.eCompressionFormat, param.format.video.eColorFormat); + param.format.video.nFrameWidth = width_; + param.format.video.nFrameHeight = height_; + param.format.video.nStride = width_; + param.format.video.nSliceHeight = height_; + err = client_->SetParameter(client_, OMX_IndexParamPortDefinition, (int8_t *)¶m, sizeof(param)); + if (err != HDF_SUCCESS) { + HDF_LOGE("%{public}s failed with PortIndex::kPortIndexInput, index is OMX_IndexParamPortDefinition", __func__); + return false; + } + + + + // 输出宽和高,格式设置 + InitParam(param); + param.nPortIndex = (uint32_t)PortIndex::kPortIndexOutput; + err = client_->GetParameter(client_, OMX_IndexParamPortDefinition, (int8_t *)¶m, sizeof(param)); + if (err != HDF_SUCCESS) { + HDF_LOGE("%{public}s failed with PortIndex::kPortIndexOutput, index is OMX_IndexParamPortDefinition", __func__); + return false; + } + HDF_LOGI("PortIndex::kPortIndexOutput eCompressionFormat = %{public}d, eColorFormat=%{public}d", + param.format.video.eCompressionFormat, param.format.video.eColorFormat); + param.format.video.nFrameWidth = width_; + param.format.video.nFrameHeight = height_; + param.format.video.nStride = width_; + param.format.video.nSliceHeight = height_; + param.format.video.eColorFormat = AV_COLOR_FORMAT; // 输出数据格式设置,YUV420SP + err = client_->SetParameter(client_, OMX_IndexParamPortDefinition, (int8_t *)¶m, sizeof(param)); + if (err != HDF_SUCCESS) { + HDF_LOGE("%{public}s failed with PortIndex::kPortIndexOutput, index is OMX_IndexParamPortDefinition", + __func__); + return false; + } + + // 设置输入数据为H264编码或H265编码 + OMX_VIDEO_PARAM_PORTFORMATTYPE param; + InitParam(param); + param.nPortIndex = (uint32_t)PortIndex::kPortIndexInput; + auto err = client_->GetParameter(client_, OMX_IndexParamVideoPortFormat, (int8_t *)¶m, sizeof(param)); + if (err != HDF_SUCCESS) { + HDF_LOGE("%{public}s failed with PortIndex::kPortIndexInput", __func__); + return false; + } + HDF_LOGI("set Format PortIndex::kPortIndexInput eCompressionFormat = %{public}d, eColorFormat=%{public}d", + param.eCompressionFormat, param.eColorFormat); + param.xFramerate = FRAME; // 30fps,Q16 format + if (codecMime_ == codecMime::AVC) { + param.eCompressionFormat = OMX_VIDEO_CodingAVC; // H264 + } else { + param.eCompressionFormat = (OMX_VIDEO_CODINGTYPE)OMX_VIDEO_CodingHEVC; // H265 + } + + err = client_->SetParameter(client_, OMX_IndexParamVideoPortFormat, (int8_t *)¶m, sizeof(param)); + if (err != HDF_SUCCESS) { + HDF_LOGE("%{public}s failed with PortIndex::kPortIndexInput", __func__); + return false; + } + + +## 申请输入输出Buffer + +此处讲解输入输出buffer的申请过程,参考codec_hdi_test.cpp 中的UseBuffers 函数,申请了输入和输出buffer,本例中讲解了共享内存的buffer实现,填充了OmxCodecBuffer, 根据返回的bufferID 记录OmxCodecBuffer 与之对应关系,后续直接通过bufferID 操作即可。同时,我们在调用完UseBuffer 之后,需要判断下端口是否可用,如果不是可用状态,需要使当前端口可用。输入输出Buffer 设置完后,端口bufffer申请完成后,需要设置组件状态为OMX_StateIdle,并等待通知结果。 + + // 输入端口buffer申请 + auto ret = UseBufferOnPort(PortIndex::kPortIndexInput); + if (!ret) { + HDF_LOGE("%{public}s UseBufferOnPort PortIndex::kPortIndexInput error", __func__); + return false; + } + + // 输出端口buffer申请 + ret = UseBufferOnPort(PortIndex::kPortIndexOutput); + if (!ret) { + HDF_LOGE("%{public}s UseBufferOnPort PortIndex::kPortIndexOutput error", __func__); + return false; + } + + // 发送命令,使组件进入OMX_StateIdle状态 + HDF_LOGI("...command to IDLE...."); + auto err = client_->SendCommand(client_, OMX_CommandStateSet, OMX_StateIdle, NULL, 0); + if (err != HDF_SUCCESS) { + HDF_LOGE("%{public}s failed to SendCommand with OMX_CommandStateSet:OMX_StateIdle", __func__); + + return false; + } + HDF_LOGI("Wait for OMX_StateIdle status"); + this->WaitForStatusChanged(); + +UseBufferOnPort 实现如下: + + bool CodecHdiDecode::UseBufferOnPort(enum PortIndex portIndex) + { + HDF_LOGI("%{public}s enter, portIndex = %{public}d", __func__, portIndex); + int bufferSize = 0; + int bufferCount = 0; + bool bPortEnable = false; + // 获取端口buffer参数 + OMX_PARAM_PORTDEFINITIONTYPE param; + InitParam(param); + param.nPortIndex = (OMX_U32)portIndex; + auto err = client_->GetParameter(client_, OMX_IndexParamPortDefinition, (int8_t *)¶m, sizeof(param)); + if (err != HDF_SUCCESS) { + HDF_LOGE("%{public}s failed to GetParameter with OMX_IndexParamPortDefinition : portIndex[%{public}d]", + __func__, portIndex); + return false; + } + + bufferSize = param.nBufferSize; + bufferCount = param.nBufferCountActual; + bPortEnable = param.bEnabled; + HDF_LOGI("buffer index [%{public}d], buffer size [%{public}d], " + "buffer count [%{public}d], portEnable[%{public}d], err [%{public}d]", + portIndex, bufferSize, bufferCount, bPortEnable, err); + + { + OMX_PARAM_BUFFERSUPPLIERTYPE param; + InitParam(param); + param.nPortIndex = (uint32_t)portIndex; + auto err = client_->GetParameter(client_, OMX_IndexParamCompBufferSupplier, (int8_t *)¶m, sizeof(param)); + HDF_LOGI("param.eBufferSupplier[%{public}d] isSupply [%{public}d], err [%{public}d]", param.eBufferSupplier, + this->isSupply_, err); + } + + // 设置端口buffer + UseBufferOnPort(portIndex, bufferCount, bufferSize); + // set port enable + if (!bPortEnable) { + auto err = client_->SendCommand(client_, OMX_CommandPortEnable, (uint32_t)portIndex, NULL, 0); + if (err != HDF_SUCCESS) { + HDF_LOGE("%{public}s SendCommand OMX_CommandPortEnable::PortIndex::kPortIndexInput error", __func__); + return false; + } + } + return true; + } + + bool CodecHdiDecode::UseBufferOnPort(enum PortIndex portIndex, int bufferCount, int bufferSize) + { + for (int i = 0; i < bufferCount; i++) { + OmxCodecBuffer *omxBuffer = new OmxCodecBuffer(); + memset_s(omxBuffer, sizeof(OmxCodecBuffer), 0, sizeof(OmxCodecBuffer)); + omxBuffer->size = sizeof(OmxCodecBuffer); + omxBuffer->version.s.nVersionMajor = 1; + omxBuffer->bufferType = BUFFER_TYPE_AVSHARE_MEM_FD; + int fd = AshmemCreate(0, bufferSize); + shared_ptr sharedMem = make_shared(fd, bufferSize); + omxBuffer->bufferLen = FD_SIZE; + omxBuffer->buffer = (uint8_t *)(unsigned long)fd; + omxBuffer->allocLen = bufferSize; + omxBuffer->fenceFd = -1; + + if (portIndex == PortIndex::kPortIndexInput) { + omxBuffer->type = READ_ONLY_TYPE; // ReadOnly + sharedMem->MapReadAndWriteAshmem(); + } else { + omxBuffer->type = READ_WRITE_TYPE; + sharedMem->MapReadOnlyAshmem(); + } + auto err = client_->UseBuffer(client_, (uint32_t)portIndex, omxBuffer); + if (err != HDF_SUCCESS) { + HDF_LOGE("%{public}s failed to UseBuffer with portIndex[%{public}d]", __func__, portIndex); + sharedMem->UnmapAshmem(); + sharedMem->CloseAshmem(); + sharedMem = nullptr; + return false; + } + omxBuffer->bufferLen = 0; + HDF_LOGI("UseBuffer returned bufferID [%{public}d]", omxBuffer->bufferId); + + BufferInfo *bufferInfo = new BufferInfo; + bufferInfo->omxBuffer = omxBuffer; + bufferInfo->avSharedPtr = sharedMem; + bufferInfo->portIndex = portIndex; + omxBuffers_.insert(std::make_pair(omxBuffer->bufferId, std::move(bufferInfo))); + if (portIndex == PortIndex::kPortIndexInput) { + unUsedInBuffers_.push_back(omxBuffer->bufferId); + } else { + unUsedOutBuffers_.push_back(omxBuffer->bufferId); + } + int fdret = (int)omxBuffer->buffer; + HDF_LOGI("{bufferID = %{public}d, srcfd = %{public}d, retfd = %{public}d}", omxBuffer->bufferId, fd, fdret); + } + + return true; + } + + +## 编解码 + +编解码流程比较简单,需要先将组件设置为Executing状态,然后填充输入buffer, 读取输出buffer,进行buffer的轮转。 + + // 设置组件进入OMX_StateExecuting状态,开始buffer的轮转 + HDF_LOGI("...command to OMX_StateExecuting...."); + auto err = client_->SendCommand(client_, OMX_CommandStateSet, OMX_StateExecuting, NULL, 0); + if (err != HDF_SUCCESS) { + HDF_LOGE("%{public}s failed to SendCommand with OMX_CommandStateSet:OMX_StateIdle", __func__); + return; + } + + //设置输出buffer填充 + for (auto bufferId : unUsedOutBuffers_) { + HDF_LOGI("fill bufferid [%{public}d]", bufferId); + auto iter = omxBuffers_.find(bufferId); + if (iter != omxBuffers_.end()) { + BufferInfo *bufferInfo = iter->second; + auto err = client_->FillThisBuffer(client_, bufferInfo->pOmxBuffer); + if (err != HDF_SUCCESS) { + HDF_LOGE("FillThisBuffer error"); + FUNC_EXIT_ERR(); + return; + } + } + } + + + //填充输入buffer + bool bEndOfFile = false; + while (!bEndOfFile) { + HDF_LOGI(" inputput run"); + int bufferID = GetFreeBufferId(); + if (this->exit_) { + break; + } + if (bufferID < 0) { + usleep(10000); + continue; + } + auto iter = omxBuffers_.find(bufferID); + if (iter == omxBuffers_.end()) { + continue; + } + BufferInfo *bufferInfo = iter->second; + void *sharedAddr = (void *)bufferInfo->avSharedPtr->ReadFromAshmem(0, 0); + bool bEOS = (size_t)this->ReadOnePacket(fpIn_, (char *)sharedAddr, bufferInfo->omxBuffer->filledLen); + HDF_LOGI("read data size is %{public}d", bufferInfo->omxBuffer->filledLen); + bufferInfo->omxBuffer->offset = 0; + if (bEOS) { + bufferInfo->omxBuffer->flag = OMX_BUFFERFLAG_EOS; + bEndOfFile = true; + } + auto err = client_->EmptyThisBuffer(client_, bufferInfo->omxBuffer); + if (err != HDF_SUCCESS) { + HDF_LOGE("%{public}s EmptyThisBuffer error", __func__); + return; + } + } + // wait + while (!this->exit_) { + usleep(10000); + continue; + } + // 解码完成后,使组件进入OMX_StateIdle状态 + client_->SendCommand(client_, OMX_CommandStateSet, OMX_StateIdle, NULL, 0); + +对rk OMX解码时,不支持数据的分帧,所以需要我进手动分帧,目前简单实现按照开始码0x000001或0x00000001分帧发送到服务端处理。分帧代码如下: + + //文件分帧读取实现 + bool OMXCore::ReadOnePacket(FILE* fp, char* buf, size_t& nFilled) + { + //先读取4个字节 + size_t t = fread(buf, 1, 4, fp); + if (t < 4) { + //文件读取结束 + return true; + } + size_t filled = 0; + filled = 4; + + bool bRet = true; + while (!feof(fp)) { + fread(buf + filled, 1, 1, fp); + // HDF_LOGI("enter ReadFile filled= %{public}d", filled); + if (buf[filled] == 1) { + // check start code + if ((buf[filled - 1] == 0) && + (buf[filled - 2] == 0) && + (buf[filled - 3] == 0)) { + fseek(fp, -4, SEEK_CUR); + //读文件的时候,最后一次未加1,因此减3 + filled -= 3; + bRet = false; + break; + } else if ((buf[filled - 1] == 0) && + (buf[filled - 2] == 0)) { + fseek(fp, -3, SEEK_CUR); + filled -= 2; + bRet = false; + break; + } + } + filled++; + } + nFilled = filled; + return bRet; + } + + +## 组件回调处理 + +codec HDI2.0 提供3中回调函数,EventHandler, EmptyBufferDone 和 FillBufferDone。 + +-EventHandler: 主要命令完成后的通知,例如:IDLE转为Executing 的命令执行成功通知等。 +-EmptyBufferDone: 输入数据消费完毕,客户端需要重新填入待编解码数据,再次调用EmptyThisBuffer +-FillBufferDone: 输出数据填充完毕,客户端需要读取已编码/解码数据,再次调用FillThisBuffer + +####EmptyBufferDone 示例 + + //EmptyBufferDone 回调处理示例 + + int32_t OMXCore::OnEmptyBufferDone(struct CodecCallbackType *self, int8_t *pAppData, uint32_t pAppDataLen, + const struct OmxCodecBuffer *pBuffer) + { + FUNC_ENTER(); + HDF_LOGI("onEmptyBufferDone: pBuffer.bufferID [%{public}d]", pBuffer->bufferId); + g_core->OnEmptyBufferDone(pBuffer); + FUNC_EXIT(); + return HDF_SUCCESS; + } + + + int32_t OMXCore::OnEmptyBufferDone(const struct OmxCodecBuffer *pBuffer) + { + unique_lock ulk(mLockInputBuffers_); + unUsedInBuffers_.push_back(pBuffer->bufferId); + return HDF_SUCCESS; + } + + + +####FillBufferDone 示例 + + //FillBufferDone 回调处理示例 + int32_t OMXCore::OnFillBufferDone(struct CodecCallbackType *self, int8_t *pAppData, uint32_t pAppDataLen, + struct OmxCodecBuffer *pBuffer) + { + FUNC_ENTER(); + HDF_LOGI("onFillBufferDone: pBuffer.bufferID [%{public}d]", pBuffer->bufferId); + g_core->OnFillBufferDone(pBuffer); + FUNC_EXIT(); + return HDF_SUCCESS; + } + + + int32_t OMXCore::onFillBufferDone(struct OmxCodecBuffer* pBuffer) { + //根据bufferID, 找到ptr + if (bExit_) { + return HDF_SUCCESS; + } + + auto iter = omxBuffers_.find(pBuffer->bufferId); + if (iter == omxBuffers_.end() || !iter->second) { + return HDF_SUCCESS; + } + //取出输出的数据 + BufferInfo *pBufferInfo = iter->second; + const void *addr = pBufferInfo->avSharedPtr->ReadFromAshmem(pBuffer->filledLen, pBuffer->offset); + //(void)addr; + //解码数据保存到文件 + fwrite(addr, 1, pBuffer->filledLen, fpOut_.get()); + fflush(fpOut_.get()); + //重置buffer数据 + pBuffer->offset = 0; + pBuffer->filledLen = 0; + if (pBuffer->flag == OMX_BUFFERFLAG_EOS) { + //结束 + bExit_ = true; + HDF_LOGI("OnFillBufferDone the END coming"); + return HDF_SUCCESS; + } + //再次调用FillThisBuffer + auto err = client_->FillThisBuffer(client_, pBufferInfo->pOmxBuffer); + if (err != HDF_SUCCESS) { + HDF_LOGE("FillThisBuffer error"); + return HDF_SUCCESS; + } + return HDF_SUCCESS; + } + +####EventHandler示例 + + int32_t CodecHdiDecode::OnEvent(struct CodecCallbackType *self, enum OMX_EVENTTYPE event, struct EventInfo *info) + { + HDF_LOGI("onEvent: appData[0x%{public}p], eEvent [%{public}d], " + "nData1[%{public}d]", + info->appData, event, info->data1); + switch (event) { + case OMX_EventCmdComplete: { + OMX_COMMANDTYPE cmd = (OMX_COMMANDTYPE)info->data1; + if (OMX_CommandStateSet == cmd) { + HDF_LOGI("OMX_CommandStateSet reached, status is %{public}d", info->data2); + g_core->onStatusChanged(); + } + break; + } + default: + break; + } + return HDF_SUCCESS; + } + + +## 组件释放 + +组件释放前,需要将组件状态修改为IDLE,然后开始释放输入输出Buffer, 再讲组件状态修改为OMX_StateLoaded, 最后再调用ComponentDeInit 和 DestoryComponent。 + +####Buffer释放示例 + + // 发送命令,使组件进入OMX_StateLoaded状态 + client_->SendCommand(client_, OMX_CommandStateSet, OMX_StateLoaded, nullptr, 0); + + // 释放所有申请的buffer + auto iter = omxBuffers_.begin(); + while (iter != omxBuffers_.end()) { + BufferInfo *bufferInfo = iter->second; + client_->FreeBuffer(client_, (uint32_t)bufferInfo->portIndex, bufferInfo->omxBuffer); + delete bufferInfo; + iter++; + } + omxBuffers_.clear(); + unUsedInBuffers_.clear(); + unUsedOutBuffers_.clear(); + + enum OMX_STATETYPE status; + client_->GetState(client_, &status); + // buffer释放后,组件即进入OMX_StateLoaded状态 + if (status != OMX_StateLoaded) { + HDF_LOGI("Wait for OMX_StateLoaded status"); + this->WaitForStatusChanged(); + } else { + HDF_LOGI(" status is %{public}d", status); + } + + +####组件实例释放示例 + //组件实例释放 + void OMXCore::Release() { + omxMgr_->DestoryComponent(client_); + client_ = nullptr; + + CodecComponentManagerRelease(); + } + + +# 总结 + +以上就是基于Codec HDI2.0 编解码开发过程中,所涉及的所有关键点,重点介绍了 Codec HDI2.0 编解码的过程及相关的示例代码。希望通过本次的文档,您能初步掌握基于Codec HDI2.0 编解码的开发。 \ No newline at end of file -- Gitee