OpenHarmony实战之视频编解码HDI的使用 原创 精华

发布于 2022-3-24 13:54
浏览
6收藏

作者:祁金全

简介

Codec HDI 提供了基于OpenMax(简称OMX)的一整套编解码接口,程序通过简单的几步,即可实现基于OMX的硬件编解码。本文主要介绍基于Codec HDI 开发的编解码功能,包括初始化,Buffer流转以及释放等。

CODEC HDI框架介绍

OpenHarmony实战之视频编解码HDI的使用-开源基础软件社区

Codec HDI Interface (简称HDI)提供基于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 进行对应的数据处理。

目录

./drivers/peripheral/codec
|-- hal									#HDI框架代码目录
|   |-- include							
|   |-- src
|-- hdi_service						    #原HDI框架代码目录
|   |-- codec_proxy
|   |-- codec_service_stub
|   |-- codec_test
|-- interfaces							#codec接口头文件
|   |-- include
|-- test								#unittest 和Demo目录

Codec HDI编解码实例

组件状态流转

OpenHarmony实战之视频编解码HDI的使用-开源基础软件社区
1.通过调用CreateComponent函数,可以打开组件,并让组件进入OMX_StateLoaded 或OMX_StateWaitForResources 状态;
2.通过调用DestoryComponent函数,可以销毁处于OMX_StateLoaded状态的组件实例;
3.其它的状态变化,可以通过SendCommand(OMX_CommandStateSet, <OMX_STATETYPE>)使组件进入相应的状态。

<table><thread align=“center”>
<tr>
<th align=“center”><p>状态</p></th>
<th align=“center”><p>描述</p></th>
</tr>
</thread>
<tbody>
<tr>
<td align=“center” ><p>OMX_StateInvalid</p></td>
<td align=“center”><p>组件已损坏或者发生一个不能恢复的错误</p></td>
</tr>
<tr>
<td align=“center”><p>OMX_StateLoaded</p></td>
<td align=“center”><p>组件已经加载,但是资源未申请</p></td>
</tr>
<tr>
<td align=“center”><p>OMX_StateIdle</p></td>
<td align=“center”><p>组件已经申请到所有资源,但不会轮转任何buffer</p></td>
</tr>
<tr>
<td align=“center”><p>OMX_StateExecuting</p></td>
<td align=“center”><p>组件正在处理有效的数据 </p></td>
</tr>
<tr>
<td align=“center”><p>OMX_StatePause</p></td>
<td align=“center”><p>组件暂停处理数据,可以通过设置组件状态唤醒重新处理数据</p></td>
</tr>
<tr>
<td align=“center”><p>OMX_StateWaitForResources</p></td>
<td align=“center”><p>组件正在等待所需要的</p></td>
</tr>
</tbody>
</table>

编解码流程简介

OpenHarmony实战之视频编解码HDI的使用-开源基础软件社区

主要包含以下几步:

  1. 接口及回调初始化
  2. 组件初始化
  3. 设置编解码参数
  4. 申请输入输出Buffer
  5. 编解码
  6. 组件回调处理
  7. 组件释放

codec HDI编解码主要流程如上图显示,下面我们以解码流程来讲解。

接口及回调初始化

本步骤主要初始化了使用HDI接口必须使用的结构及初始化对应的回调函数。

//初始化HDI 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_
auto err = omxMgr_->CreateComponent(&client_, "OMX.rk.video_decoder.avc", 0, 0, callback_);
if (err != HDF_SUCCESS) {
    HDF_LOGE("failed to CreateComponent");
    FUNC_EXIT_ERR();
    return false;
}
//获取组件版本号
char                  name[MAX_LEN] = {0};
union OMX_VERSIONTYPE omxVer;
union OMX_VERSIONTYPE omxVerspec;
uint8_t               uuid[MAX_LEN] = {0};
uint32_t              uuidLen   = MAX_LEN; // MAX_LEN = 128
err = client_->GetComponentVersion(client_, name, &omxVer, &omxVerspec, uuid, uuidLen);
if (err != HDF_SUCCESS) {
    HDF_LOGE("failed to CreateComponent");
    FUNC_EXIT_ERR();
    return false;
}

设置编解码参数

设置参数时会使用上一步获取到的版本信息,注意填写。

1.设置输入的宽和高

OMX_PARAM_PORTDEFINITIONTYPE param;
InitParam(param);
param.nPortIndex = (uint32_t)PortIndex::kPortIndexInput;
auto err         = client_->GetParameter(client_, OMX_IndexParamPortDefinition, (int8_t *)&param, sizeof(param));
if (err != HDF_SUCCESS) {
    HDF_LOGE("failed to GetParameter with PortIndex::kPortIndexInput, index is "
             "OMX_IndexParamPortDefinition");

    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 *)&param, sizeof(param));
if (err != HDF_SUCCESS) {
    HDF_LOGE("failed to SetParameter with PortIndex::kPortIndexInput, index is "
             "OMX_IndexParamPortDefinition");

    return false;
}

2.设置输出视频的宽和高以及格式

InitParam(param);
param.nPortIndex = (uint32_t)PortIndex::kPortIndexOutput;
err              = client_->GetParameter(client_, OMX_IndexParamPortDefinition, (int8_t *)&param, sizeof(param));
if (err != HDF_SUCCESS) {
    HDF_LOGE("failed to GetParameter with PortIndex::kPortIndexOutput, index is "
             "OMX_IndexParamPortDefinition");

    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 *)&param, sizeof(param));
if (err != HDF_SUCCESS) {
    HDF_LOGE("failed to SetParameter with PortIndex::kPortIndexOutput, index is "
             "OMX_IndexParamPortDefinition");

    return false;
}

3.设置输入的格式和帧率

OMX_VIDEO_PARAM_PORTFORMATTYPE param;
InitParam(param);
param.nPortIndex = (uint32_t)PortIndex::kPortIndexInput;
auto err         = client_->GetParameter(client_, OMX_IndexParamVideoPortFormat, (int8_t *)&param, sizeof(param));
if (err != HDF_SUCCESS) {
    HDF_LOGE("failed to GetParameter with PortIndex::kPortIndexInput, index is "
             "OMX_IndexParamVideoPortFormat");
    return false;
}
HDF_LOGI("set Format PortIndex::kPortIndexInput eCompressionFormat = %{public}d, "
         "eColorFormat=%{public}d",
         param.eCompressionFormat, param.eColorFormat);
param.xFramerate         = FRAME;                // 30fps,Q16 format
param.eCompressionFormat = OMX_VIDEO_CodingAVC;  // H264
err = client_->SetParameter(client_, OMX_IndexParamVideoPortFormat, (int8_t *)&param, sizeof(param));
if (err != HDF_SUCCESS) {
    HDF_LOGE("failed to SetParameter with PortIndex::kPortIndexInput, index is "
             "OMX_IndexParamVideoPortFormat");

    return false;
}

申请输入输出Buffer

参数设置完成后,我们需要设置输入输出端口的Buffer,需要调用UseBuffer 方法,注意该方法只有在组件处于OMX_StateLoaded 或OMX_StateWaitForResources 或者端口处于Disabled 状态才能使用。实例代码中的portIndex可以是输入端口PortIndex::kPortIndexInput, 也可以是输出端口PortIndex::kPortIndexOutput,两个都需要设置。

1.获取端口需要的buffer大小和数量以及enable状态

OMX_PARAM_PORTDEFINITIONTYPE param;
InitParam(param);
param.nPortIndex = (OMX_U32)portIndex;
auto err         = client_->GetParameter(client_, OMX_IndexParamPortDefinition, (int8_t *)&param, sizeof(param));
if (err != HDF_SUCCESS) {
    HDF_LOGE("failed to GetParameter with OMX_IndexParamPortDefinition : "
             "portIndex[%{public}d]",
             portIndex);

    return false;
}

bufferSize  = param.nBufferSize;
bufferCount = param.nBufferCountActual;
bPortEnable = param.bEnabled;

2.初始化多个Buffer并调用UseBuffer方法,注意返回的bufferId,后续操作都可以用bufferId来标识OmxCodecBuffer

for (int i = 0; i < bufferCount; i++) {
	//初始化OmxCodecBuffer, 目前只支持共享内存的方式
    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<Ashmem> spSharedMem     = make_shared<Ashmem>(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;  // 对输入端口,服务是只读的
        spSharedMem->MapReadAndWriteAshmem();
    } else {
        omxBuffer->type = READ_WRITE_TYPE; //对输出端口,服务是可写入的
        spSharedMem->MapReadOnlyAshmem();
    }
    auto err = client_->UseBuffer(client_, (uint32_t)portIndex, omxBuffer);
    if (err != HDF_SUCCESS) {
        HDF_LOGE("failed to UseBuffer with  portIndex[%{public}d]", portIndex);

        spSharedMem->UnmapAshmem();
        spSharedMem->CloseAshmem();
        spSharedMem = nullptr;
        return false;
    }

    omxBuffer->bufferLen = 0;
    HDF_LOGI("UseBuffer returned bufferID [%{public}d]", omxBuffer->bufferId);
	//保存omxBuffer 以及共享内存映射地址
    BufferInfo *bufferInfo  = new BufferInfo;
    bufferInfo->omxBuffer   = omxBuffer;
    bufferInfo->avSharedPtr = spSharedMem;
    bufferInfo->portIndex   = portIndex;
    omxBuffers_.insert(std::make_pair<int, BufferInfo *>(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);
}

3.调用完UseBuffer之后,我们需要判断端口是否处于enable状态,如果是非使能状态,需要发送命令使端口处于使能状态

// set port enable
if (!bPortEnable) {
    auto err = client_->SendCommand(client_, OMX_CommandPortEnable, portIndex, NULL, 0);
    if (err != HDF_SUCCESS) {
        HDF_LOGE("SendCommand OMX_CommandPortEnable::kPortIndexInput error");
        FUNC_EXIT_ERR();
        return false;
    }
}

4.Buffer 设置完成后,需要使组件进入OMX_StateIdle状态

HDF_LOGI("...command to IDLE....");
auto err = client_->SendCommand(client_, OMX_CommandStateSet, OMX_StateIdle, NULL, 0);
if (err != HDF_SUCCESS) {
    HDF_LOGE("failed to SendCommand with OMX_CommandStateSet:OMX_StateIdle");
    FUNC_EXIT_ERR();
    return false;
}

HDF_LOGI("Wait for OMX_StateIdle status");
this->WaitForStatusChanged();

编解码

组件进入IDLE状态之后,我们要先让组件进入OMX_StateExecuting 状态,组件即可正常编解码了。
1.发送命令,使组件进入OMX_StateExecuting 状态

HDF_LOGI("...command to OMX_StateExecuting....");
err = client_->SendCommand(client_, OMX_CommandStateSet, OMX_StateExecuting, NULL, 0);
if (err != HDF_SUCCESS) {
    HDF_LOGE("failed to SendCommand with OMX_CommandStateSet:OMX_StateIdle");
    FUNC_EXIT_ERR();
    return false;
}

HDF_LOGI("Wait for OMX_StateExecuting status");
this->WaitForStatusChanged();

2.先将输出Buffer传给组件,让组件填充这些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->omxBuffer);
        if (err != HDF_SUCCESS) {
            HDF_LOGE("FillThisBuffer error");
            return;
        }
    }
}

3.读取H264 或 H265文件中的数据,让组件消费,这里请注意下,组件只支持已分帧的数据,我们读取文件时,需要按照start code(0x000001 或者 0x00000001)进行分帧

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_.get(), (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("EmptyThisBuffer error");

        return;
    }
}

4.解码完成后,需要将组件设置为OMX_StateIdle 状态

// command to IDLE
client_->SendCommand(client_, OMX_CommandStateSet, OMX_StateIdle, NULL, 0);

组件回调处理

codec HDI 提供了3大回调,分别对应了omx组件的3个回调函数。
1.EventHandler, 包含状态变化,错误通知等等,具体参数含义请见下表格
<table><thread align=“center”>
<tr>
<th align=“center”><p>eEvent</p></th>
<th align=“center”><p>data1</p></th>
<th align=“center”><p>data2</p></th>
<th align=“center”><p>eventData</p></th>
</tr>
</thread>
<tbody>
<tr>
<td align=“center” rowspan=“5”><p>OMX_EventCmdComplete</p></td>
<td align=“center”><p>OMX_CommandStateSet</p></td>
<td align=“center”><p>OMX组件状态</p></td>
<td align=“center”><p>NULL</p></td>
</tr>
<tr>
<td align=“center”><p>OMX_CommandFlush</p></td>
<td align=“center”><p>端口</p></td>
<td align=“center”><p>NULL</p></td>
</tr>
<tr>
<td align=“center”><p>OMX_CommandPortDisable</p></td>
<td align=“center”><p>端口</p></td>
<td align=“center”><p>NULL</p></td>
</tr>
<tr>
<td align=“center”><p>OMX_CommandPortEnable</p></td>
<td align=“center”><p>端口</p></td>
<td align=“center”><p>NULL</p></td>
</tr>
<tr>
<td align=“center”><p>OMX_CommandMarkBuffer</p></td>
<td align=“center”><p>端口</p></td>
<td align=“center”><p>NULL</p></td>
</tr>
<tr>
<td align=“center”><p>OMX_EventError</p></td>
<td align=“center”><p>Error Code</p></td>
<td align=“center”><p>0</p></td>
<td align=“center”><p>NULL</p></td>
</tr>
<tr>
<td align=“center”><p>OMX_EventMark</p></td>
<td align=“center”><p>0</p></td>
<td align=“center”><p>0</p></td>
<td align=“center”><p>make buffer</p></td>
</tr>
<tr>
<td align=“center”><p>OMX_EventPortSettingsChanged</p></td>
<td align=“center”><p>端口</p></td>
<td align=“center”><p>0</p></td>
<td align=“center”><p>NULL</p></td>
</tr>
<tr>
<td align=“center”><p>OMX_EventBufferFlag</p></td>
<td align=“center”><p>端口</p></td>
<td align=“center”><p>nFlags</p></td>
<td align=“center”><p>NULL</p></td>
</tr>
<tr>
<td align=“center”><p>OMX_EventResourcesAcquired</p></td>
<td align=“center”><p>0</p></td>
<td align=“center”><p>0</p></td>
<td align=“center”><p>NULL</p></td>
</tr>
<tr>
<td align=“center”><p>OMX_EventDynamicResourcesAvailable</p></td>
<td align=“center”><p>0</p></td>
<td align=“center”><p>0</p></td>
<td align=“center”><p>NULL</p></td>
</tr>
</tbody>
</table>

Demo 中逻辑处理比较简单,只是对OMX_EventCmdComplete 做了简单处理

int32_t CodecHdiDecode::OnEvent(struct CodecCallbackType *self, int8_t *pAppData, uint32_t pAppDataLen,
                            enum OMX_EVENTTYPE eEvent, uint32_t nData1, uint32_t nData2, int8_t *pEventData,
                            uint32_t pEventDataLen)
{
   	 HDF_LOGI("onEvent: pAppData[0x%{public}p], eEvent [%{public}d], "
             "nData1[%{public}d]",
             pAppData, eEvent, nData1);
    switch (eEvent) {
        case OMX_EventCmdComplete: {
            OMX_COMMANDTYPE cmd = (OMX_COMMANDTYPE)nData1;
            if (OMX_CommandStateSet == cmd) {
                HDF_LOGI("OMX_CommandStateSet reached, status is %{public}d", nData2);
                g_core->onStatusChanged();
            }
            break;
        }

        default:
            break;
    }
    return HDF_SUCCESS;
}

2.EmptyBufferDone, 输入buffer处理完毕通知
接收到此通知后,根据bufferId找到具体被消耗的OmxCodecBuffer,然后可以继续将待解码数据写入该buffer,通过调用EmptyThisBuffer将buffer传递给组件处理其数据。

//回调处理函数
int32_t CodecHdiDecode::OnEmptyBufferDone(struct CodecCallbackType *self, int8_t *pAppData, uint32_t pAppDataLen,
                                      const struct OmxCodecBuffer *buffer)
{
    HDF_LOGI("onEmptyBufferDone: pBuffer.bufferID [%{public}d]", pBuffer->bufferId);
    g_core->OnEmptyBufferDone(buffer);

    return HDF_SUCCESS;
}

int32_t CodecHdiDecode::OnEmptyBufferDone(const struct OmxCodecBuffer *buffer)
{
    unique_lock<mutex> ulk(lockInputBuffers_);
    unUsedInBuffers_.push_back(buffer->bufferId);
    return HDF_SUCCESS;
}

3.FillBufferDone, 输出buffer填充完毕通知
接收到此通知后,根据bufferId找到具体被填充的OmxCodecBuffer,然后取出解码数据(如果需要的话),再调用FillThisBuffer将buffer传递给组件,组件解码成功一帧数据后,会将解码数据填充到此buffer,并再次出发该回调。

//回调处理函数
int32_t CodecHdiDecode::OnFillBufferDone(struct CodecCallbackType *self, int8_t *pAppData, uint32_t pAppDataLen,
                                     struct OmxCodecBuffer *buffer)
{
    HDF_LOGI("onFillBufferDone: pBuffer.bufferID [%{public}d]", buffer->bufferId);
    g_core->OnFillBufferDone(buffer);
    return HDF_SUCCESS;
}

int32_t CodecHdiDecode::OnFillBufferDone(struct OmxCodecBuffer *buffer)
{
    if (exit_) {
        return HDF_SUCCESS;
    }

    auto iter = omxBuffers_.find(buffer->bufferId);
    if (iter == omxBuffers_.end() || !iter->second) {
        return HDF_SUCCESS;
    }
    // read buffer
    BufferInfo *bufferInfo = iter->second;
    const void *addr        = pBufferInfo->avSharedPtr->ReadFromAshmem(buffer->filledLen, buffer->offset);
    // save to file
    (void)fwrite(addr, 1, buffer->filledLen, fpOut_.get());
    (void)fflush(fpOut_.get());
    // reset buffer
    buffer->offset    = 0;
    buffer->filledLen = 0;
    if (buffer->flag == OMX_BUFFERFLAG_EOS) {
        // 解码结束
        exit_ = true;
        HDF_LOGI("OnFillBufferDone the END coming");
        return HDF_SUCCESS;
    }
    // call fillthisbuffer again
    auto err = client_->FillThisBuffer(client_, bufferInfo->omxBuffer);
    if (err != HDF_SUCCESS) {
        HDF_LOGE("FillThisBuffer error");
        return HDF_SUCCESS;
    }
    return HDF_SUCCESS;
}

组件释放

编解码完成后,我们需要对使用的buffer进行销毁,同时,需要销毁接口相关的结构。
1.先发送命令让组件进入OMX_StateLoaded状态

// command to loaded
client_->SendCommand(client_, OMX_CommandStateSet, OMX_StateLoaded, nullptr, 0);

2.释放所有使用的Buffer

 // release all the buffers
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();

3.释放所有buffer之后,等待组件真正进入OMX_StateLoaded 状态
enum OMX_STATETYPE status;
client_->GetState(client_, &status);
// wait loaded
if (status != OMX_StateLoaded) {
HDF_LOGI(“Wait for OMX_StateLoaded status”);
this->WaitForStatusChanged();
} else {
HDF_LOGI(" status is %{public}d", status);
}

4.组件进入Loaded状态,可以释放组件及接口相关实例

// 关闭组件
omxMgr_->DestoryComponent(client_); // 这里已经释放了client_
client_ = nullptr;
// 释放omxMgr_
CodecComponentManagerRelease();

总结

本文只是简单的介绍了下视频编解码Codec HDI的解码过程,需要在正确的组件状态下调用相应的接口。

更多原创内容请关注:深开鸿技术团队

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-3-24 13:54:50修改
8
收藏 6
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐