Harmony鸿蒙南向外设驱动开发-Codec

功能简介

OpenHarmony Codec HDI(Hardware Device Interface)驱动框架基于OpenMax实现了视频硬件编解码驱动,提供Codec基础能力接口给上层媒体服务调用,包括获取组件编解码能力、创建组件、参数设置、数据的轮转和控制、以及销毁组件等功能,实现对视频数据的编解码处理(可以将YUV/RGB等格式的视频数据编码成H264/H265等编码格式,也可以将H264/H265等裸流数据解码成YUV/RGB等格式数据)。本文主要介绍基于HDF(Hardware Driver Foundation)驱动框架开发的Codec编解码功能。

Codec HDI驱动框架基于HDF驱动框架实现。Codec HDI驱动架构组成:

图 1 Codec HDI驱动框架

image

  • Codec HDI Callback Remote Service:匿名Callback服务,通过该服务,可以处理回调。
  • Codec HDI Interface:提供了基于OpenMax的标准接口,上层可通过这些接口来实现硬件的编解码。
  • Codec HDI Adapter:HDI 实现层,实现了HDI Interface接口,并与OpenMax IL 对接。
  • OpenMax IL Interface:OpenMax IL接口,Codec HDI驱动直接对接OpenMax IL层。
  • Vendor Impl:厂商适配层,各大厂商适配的OpenMax 实现层。
  • Codec Hardware:硬件解码设备。

基本概念

在进行开发前,开发者应了解一下基本概念:

  • 采样率

    采样率就是每秒从连续信号中提取并组成离散信号的采样个数,用赫兹(Hz)来表示。

  • OpenMax IL

    OpenMax IL定义了硬件或软件编解码的标准,使得应用程序和媒体框架能够以统一的方式与多媒体编解码器和支持的组件进行交互。

  • 帧率

    帧率就是每秒内传输的图片的帧数,也可以理解为图形处理器每秒能够刷新几次。帧率越大,画面越流畅;帧率越小,画面越有跳动感。

  • 码率

    视频的码率是指在单位时间内传输的视频数据数量,一般用kbps作为单位。码率越高,视频就越清晰,反之则画面粗糙而且多马赛克。

  • 组件

    组件就是指的OpenMax IL 组件,是对视频流中模块的抽象,本文中的组件指的是编解码组件,专门处理视频的编解码。

约束与限制

Codec HDI只针对标准系统,其它系统暂不支持。

接口约束和限制参考OpenMax IL标准。

开发指导

场景介绍

Codec模块主要完成对视频数据的硬件编解码,将H264等裸流数据转化成图形支持的YUV或者RGB数据,也支持将图形的YUV或RGB数据编码成H264等数据格式。

接口说明

  • icodec_component_manager.h

    接口名称功能描述
    int32_t CreateComponent(sptr& component, uint32_t& componentId,
    const std::string& compName, int64_t appData, const sptr& callbacks)
    创建Codec组件实例
    int32_t DestoryComponent(uint32_t componentId)销毁Codec组件实例
  • icodec_component.h

    接口名称功能描述
    int32_t SendCommand(CodecCommandType cmd, uint32_t param, const std::vector<int8_t>& cmdData)发送命令给组件
    int32_t GetParameter(uint32_t index, const std::vector<int8_t>& inParamStruct, std::vector<int8_t>& outParamStruct)获取组件参数设置
    int32_t SetParameter(uint32_t index, const std::vector<int8_t>& paramStruct)设置组件需要的参数
    int32_t GetState(CodecStateType& state)获取组件的状态
    int32_t UseBuffer(uint32_t portIndex, const OmxCodecBuffer& inBuffer, OmxCodecBuffer& outBuffer)指定组件端口的buffer
    int32_t FreeBuffer(uint32_t portIndex, const OmxCodecBuffer& buffer)释放buffer
    int32_t EmptyThisBuffer(const OmxCodecBuffer& buffer)编解码输入待处理buffer
    int32_t FillThisBuffer(const OmxCodecBuffer& buffer)编解码输出填充buffer
  • icodec_callback.h

    接口名称功能描述
    int32_t EventHandler(CodecEventType event, const EventInfo& info)事件上报
    int32_t EmptyBufferDone(int64_t appData, const OmxCodecBuffer& buffer)上报输入buffer编码或者解码处理完毕
    int32_t FillBufferDone(int64_t appData, const OmxCodecBuffer& buffer)上报输出buffer填充完毕

更多接口请参考Codec驱动仓。

开发步骤

Codec HDI驱动的开发过程主要包含以下步骤:

Driver的注册及初始化

定义Codec HDI的HdfDriverEntry结构体,该结构体中定义了Driver初始化的方法,填充g_codeccomponentmanagerDriverEntry结构体,实现Bind、Init、Release函数指针。

static struct HdfDriverEntry g_codeccomponentmanagerDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "codec_component_manager_service",
    .Bind = HdfCodecComponentManagerDriverBind,
    .Init = HdfCodecComponentManagerDriverInit,
    .Release = HdfCodecComponentManagerDriverRelease,
}; // 将Codec HDI的HdfDriverEntry结构体注册到HDF上
  • HdfCodecComponentManagerDriverBind:将HDF中device绑定到HdfCodecComponentManagerHost,将codec service注册到HDF框架。

    static int HdfCodecComponentManagerDriverBind(struct HdfDeviceObject *deviceObject)
    {
        CODEC_LOGI("HdfCodecComponentManagerDriverBind enter");
    
        auto *hdfCodecComponentManagerHost = new (std::nothrow) HdfCodecComponentManagerHost;
        if (hdfCodecComponentManagerHost == nullptr) {
            CODEC_LOGE("failed to create create HdfCodecComponentManagerHost object");
            return HDF_FAILURE;
        }
    
        hdfCodecComponentManagerHost->ioService.Dispatch = CodecComponentManagerDriverDispatch;
        hdfCodecComponentManagerHost->ioService.Open = NULL;
        hdfCodecComponentManagerHost->ioService.Release = NULL;
    
        auto serviceImpl = ICodecComponentManager::Get(true);
        if (serviceImpl == nullptr) {
            CODEC_LOGE("failed to get of implement service");
            delete hdfCodecComponentManagerHost;
            return HDF_FAILURE;
        }
    
        hdfCodecComponentManagerHost->stub =
            OHOS::HDI::ObjectCollector::GetInstance().GetOrNewObject(serviceImpl, ICodecComponentManager::GetDescriptor());
        if (hdfCodecComponentManagerHost->stub == nullptr) {
            CODEC_LOGE("failed to get stub object");
            delete hdfCodecComponentManagerHost;
            return HDF_FAILURE;
        }
    
        deviceObject->service = &hdfCodecComponentManagerHost->ioService;
        return HDF_SUCCESS;
    }
  • HdfCodecComponentManagerDriverInit:加载HCS(HDF Configuration Source)中的属性配置。

    static int HdfCodecComponentManagerDriverInit(struct HdfDeviceObject *deviceObject)
    {
        CODEC_LOGI("HdfCodecComponentManagerDriverInit enter");
        if (DevHostRegisterDumpHost(CodecDfxService::DevCodecHostDump) != HDF_SUCCESS) {
            CODEC_LOGE("DevHostRegisterDumpHost error!");
        }
        return HDF_SUCCESS;
    }
  • HdfCodecComponentTypeDriverRelease:释放驱动实例。

    static void HdfCodecComponentManagerDriverRelease(struct HdfDeviceObject *deviceObject)
    {
        CODEC_LOGI("HdfCodecComponentManagerDriverRelease enter");
        if (deviceObject->service == nullptr) {
            CODEC_LOGE("HdfCodecComponentManagerDriverRelease not initted");
            return;
        }
    
        auto *hdfCodecComponentManagerHost =
            CONTAINER_OF(deviceObject->service, struct HdfCodecComponentManagerHost, ioService);
        delete hdfCodecComponentManagerHost;
    }
Driver的HCS配置

HCS配置包括两部分:

  • device相关配置。
  • 支持的组件相关配置。

HCS配置内容包括:驱动节点、加载顺序、服务名称等。HCS语法可参考配置管理。

以RK3568开发板为例,标准系统配置文件路径(其它系统暂不涉及): vendor/hihope/rk3568/hdf_config/uhdf/

  1. device相关配置

    在device_info.hcs的codec_host中增加codec_component_manager_service配置,具体配置如下:

    codec :: host {
        hostName = "codec_host";
        priority = 50;
        gid = ["codec_host", "uhdf_driver", "vendor_mpp_driver"];
        codec_omx_idl_device :: device {
            device0 :: deviceNode {
                policy = 2;                                       // 自动加载,非延迟加载
                priority = 100;                                   // 优先级
                moduleName = "libcodec_driver.z.so";              // 驱动的动态库
                serviceName = "codec_component_manager_service";  // 配置驱动的服务名
                deviceMatchAttr = "media_codec_capabilities";     // 属性配置
            }
        }
    }
  2. 支持的组件相关配置

    在media_codec\media_codec_capabilities.hcs中增加组件配置,具体配置如下:

    /* node name explanation -- HDF_video_hw_enc_avc_rk:
    **
    **    HDF____________video__________________hw____________________enc____________avc_______rk
    **     |               |                    |                      |              |        |
    ** HDF or OMX    video or audio    hardware or software    encoder or decoder    mime    vendor
    */
    HDF_video_hw_enc_avc_rk {
        role = 1;                                           // AvCodecRole配置
        type = 1;                                           // CodecType配置
        name = "OMX.rk.video_encoder.avc";                  // 组件名配置
        supportProfiles = [1, 32768, 2, 32768, 8, 32768];   // 支持的profile配置
        maxInst = 4;                                        // 最大实例数量配置
        isSoftwareCodec = false;                            // 硬件/软件
        processModeMask = [];                               // CodecProcessMode配置
        capsMask = [0x01];                                  // CodecCapsMask配置
        minBitRate = 1;                                     // 最小比特率
        maxBitRate = 40000000;                              // 最大比特率
        minWidth = 176;                                     // 视频最小宽
        minHeight = 144;                                    // 视频最小高
        maxWidth = 1920;                                    // 视频最大宽
        maxHeight = 1088;                                   // 视频最大高
        widthAlignment = 16;                                // 水平对齐
        heightAlignment = 8;                                // 垂直对齐
        minBlockCount = 0xFFFFFFFF;
        maxBlockCount = 0xFFFFFFFF;
        minBlocksPerSecond = 0xFFFFFFFF;
        maxBlocksPerSecond = 0xFFFFFFFF;
        blockSizeWidth = 0xFFFFFFFF;
        blockSizeHeight = 0xFFFFFFFF;
        supportPixelFmts = [28, 24, 20, 12];                // 支持的颜色列表,Display支持的颜色列表
        measuredFrameRate = [320, 240, 165, 165, 720, 480, 149, 149, 1280, 720, 73, 73, 1920, 1080, 18, 18];
        bitRateMode = [1, 2];                               // 比特率模式,BitRateMode
        minFrameRate = 0;                                   // 帧率配置
        maxFrameRate = 0;
    }

使用实例

在按照开发步骤进行相关操作后,Codec模块完成了基本的驱动适配,用户可使用Codec模块提供的HDI接口进行下一步的开发。Codec HDI核心功能如下:

  1. 提供Codec HDI接口供北向视频服务调用,实现视频服务的基本编解码。
  2. 作为标准南向接口,保证南向OEM产商实现HDI-adapter的规范性,保证生态良性演进。

用户开发步骤如下所示:

  1. 初始化,包括接口实例、回调的初始化和对应的组件的初始化;
  2. 设置编解码参数和配置信息,如视频宽、高和码率等;
  3. 输入输出Buffer申请;
  4. 编解码Buffer流转,使组件进入CODEC_STATE_EXECUTING状态,并处理相应的回调;
  5. 接口去初始化,销毁buffer,关闭组件并释放所有的接口对象;
初始化

初始化过程包括接口的初始化,回调的初始化以及组件的创建。

// 初始化Codec HDI ComponentManager实例
omxMgr_ = ICodecComponentManager::Get(false);
if ((omxMgr_ == nullptr)) {
    HDF_LOGE("%{public}s omxMgr_ is null", __func__);
    return false;
}
// 初始化回调
callback_ = new CodecHdiCallback(shared_from_this());
if ((callback_ == nullptr)) {
    HDF_LOGE("%{public}s callback_ is null", __func__);
    return false;
}
// 新建组件实例
err = omxMgr_->CreateComponent(client_, componentId_, compName, reinterpret_cast<int64_t>(this), callback_);
if (err != HDF_SUCCESS) {
    HDF_LOGE("%{public}s failed to CreateComponent", __func__);
    return false;
}
设置编解码参数和配置信息

Codec HDI编解码参数配置,包括输入输出数据的宽和高,输入数据格式和输出数据格式。

// 设置输入端口图片的宽高
OMX_PARAM_PORTDEFINITIONTYPE param;
if (util_->InitParam(param) != HDF_SUCCESS) {
    return HDF_FAILURE;
}
param.nPortIndex = static_cast<uint32_t>(PortIndex::PORT_INDEX_INPUT);

std::vector<int8_t> inVec, outVec;
util_->ObjectToVector(param, inVec);
auto err = client_->GetParameter(OMX_IndexParamPortDefinition, inVec, outVec);
if (err != HDF_SUCCESS) {
    HDF_LOGE("%{public}s failed  PortIndex::PORT_INDEX_INPUT, index is OMX_IndexParamPortDefinition", __func__);
    return err;
}
util_->VectorToObject(outVec, param);

HDF_LOGI("PortIndex::PORT_INDEX_INPUT: eCompressionFormat = %{public}d, eColorFormat = %{public}d ",
         param.format.video.eCompressionFormat, param.format.video.eColorFormat);
util_->setParmValue(param, width_, height_, stride_);
util_->ObjectToVector(param, inVec);
err = client_->SetParameter(OMX_IndexParamPortDefinition, inVec);
if (err != HDF_SUCCESS) {
    HDF_LOGE("%{public}s failed with PortIndex::PORT_INDEX_INPUT, index is OMX_IndexParamPortDefinition", __func__);
    return err;
}
// 输出宽、高和格式设置
if (util_->InitParam(param) != HDF_SUCCESS) {
    return HDF_FAILURE;
}
param.nPortIndex = static_cast<uint32_t>(PortIndex::PORT_INDEX_OUTPUT);
util_->ObjectToVector(param, inVec);
err = client_->GetParameter(OMX_IndexParamPortDefinition, inVec, outVec);
if (err != HDF_SUCCESS) {
    HDF_LOGE("%{public}s failed with PortIndex::PORT_INDEX_OUTPUT, index is OMX_IndexParamPortDefinition",
             __func__);
    return err;
}
util_->VectorToObject(outVec, param);

HDF_LOGI("PortIndex::PORT_INDEX_OUTPUT eCompressionFormat = %{public}d, eColorFormat=%{public}d",
         param.format.video.eCompressionFormat, param.format.video.eColorFormat);
util_->setParmValue(param, width_, height_, stride_);
param.format.video.eColorFormat = AV_COLOR_FORMAT;  // 输出数据格式设置为YUV420SP
err = client_->SetParameter(OMX_IndexParamPortDefinition, inVec);
if (err != HDF_SUCCESS) {
    HDF_LOGE("%{public}s failed  with PortIndex::PORT_INDEX_OUTPUT, index is OMX_IndexParamPortDefinition",
             __func__);
    return err;
}
// 设置输入数据为H264/H265格式数据
OMX_VIDEO_PARAM_PORTFORMATTYPE param;
if (util_->InitParam(param) != HDF_SUCCESS) {
    return false;
}
param.nPortIndex = (uint32_t)PortIndex::PORT_INDEX_INPUT;
std::vector<int8_t> inVec, outVec;
util_->ObjectToVector(param, inVec);
auto err = client_->GetParameter(OMX_IndexParamVideoPortFormat, inVec, outVec);
if (err != HDF_SUCCESS) {
    HDF_LOGE("%{public}s failed with PortIndex::PORT_INDEX_INPUT", __func__);
    return false;
}
util_->VectorToObject(outVec, param);

HDF_LOGI("set Format PortIndex::PORT_INDEX_INPUT eCompressionFormat = %{public}d, eColorFormat=%{public}d",
         param.eCompressionFormat, param.eColorFormat);
param.xFramerate = FRAME  // 30帧
if (codecMime_ == codecMime::AVC) {
    param.eCompressionFormat = OMX_VIDEO_CodingAVC;  // H264
} else {
    param.eCompressionFormat = (OMX_VIDEO_CODINGTYPE)CODEC_OMX_VIDEO_CodingHEVC;  // H265
}

util_->ObjectToVector(param, inVec);
err = client_->SetParameter(OMX_IndexParamVideoPortFormat, inVec);
if (err != HDF_SUCCESS) {
    HDF_LOGE("%{public}s failed  with PortIndex::PORT_INDEX_INPUT", __func__);
    return false;
}
申请输入输出Buffer

此处讲解输入输出buffer的申请的整个过程,我们需要按照下面的步骤依次执行:

  1. 用户通过UseBuffer申请输入输出Buffer,并保存bufferId,后续buffer轮转可以直接通过bufferId来操作。
  2. 用户需要判断对应的端口是否是使能状态,如果不是,需要先将对应的端口设置为使能状态。
  3. 用户通过SendCommand将组件的状态为修改为CODEC_STATE_IDLE,需要等待其结果通知。
// 输入端口buffer申请
auto err = UseBufferOnPort(PortIndex::PORT_INDEX_INPUT);
if (err != HDF_SUCCESS) {
    HDF_LOGE("%{public}s UseBufferOnPort PortIndex::PORT_INDEX_INPUT error", __func__);
    return false;
}
// 输出端口buffer申请
err = UseBufferOnPort(PortIndex::PORT_INDEX_OUTPUT);
if (err != HDF_SUCCESS) {
    HDF_LOGE("%{public}s UseBufferOnPort PortIndex::PORT_INDEX_OUTPUT error", __func__);
    return false;
}
// 发送命令使组件进入OMX_StateIdle状态
std::vector<int8_t> cmdData;
auto err = client_->SendCommand(CODEC_COMMAND_STATE_SET, CODEC_STATE_IDLE, cmdData);
if (err != HDF_SUCCESS) {
    HDF_LOGE("%{public}s failed to SendCommand with CODEC_COMMAND_STATE_SET:CODEC_STATE_IDLE", __func__);
    return false;
}

UseBufferOnPort实现如下:

int32_t CodecHdiDecode::UseBufferOnPort(PortIndex portIndex)
{
    HDF_LOGI("%{public}s enter, portIndex = %{public}d", __func__, portIndex);
    int bufferSize = 0;
    int bufferCount = 0;
    bool PortEnable = false;
    // 获取端口buffer参数
    OMX_PARAM_PORTDEFINITIONTYPE param;
    if (util_->InitParam(param) != HDF_SUCCESS) {
        return HDF_FAILURE;
    }
    param.nPortIndex = static_cast<OMX_U32>(portIndex);

    std::vector<int8_t> inVec, outVec;
    util_->ObjectToVector(param, inVec);
    auto err = client_->GetParameter(OMX_IndexParamPortDefinition, inVec, outVec);
    if (err != HDF_SUCCESS) {
        HDF_LOGE("%{public}s failed to GetParameter with OMX_IndexParamPortDefinition : portIndex[%{public}d]",
                 __func__, portIndex);
        return err;
    }
    util_->VectorToObject(outVec, param);

    bufferSize = param.nBufferSize;
    bufferCount = param.nBufferCountActual;
    portEnable = param.bEnabled;
    HDF_LOGI("buffer index [%{public}d], buffer size [%{public}d], "
             "buffer count [%{public}d], portEnable[%{public}d], ret [%{public}d]",
             portIndex, bufferSize, bufferCount, portEnable, err);
    // 设置端口buffer
    if (useBufferHandle_ && portIndex == PortIndex::PORT_INDEX_OUTPUT) {
        err = UseBufferHandle(bufferCount, bufferSize);
    } else {
        err = UseBufferOnPort(portIndex, bufferCount, bufferSize);
    }
    // 检查端口是否可用状态
    if (!portEnable) {
        err = client_->SendCommand(CODEC_COMMAND_PORT_ENABLE, static_cast<uint32_t>(portIndex), {});
        if (err != HDF_SUCCESS) {
            HDF_LOGE("%{public}s SendCommand OMX_CommandPortEnable::PortIndex::PORT_INDEX_INPUT error", __func__);
            return err;
        }
    }
    return HDF_SUCCESS;
}

int32_t CodecHdiDecode::UseBufferOnPort(PortIndex portIndex, int bufferCount, int bufferSize)
{
    if (bufferCount <= 0 || bufferSize <= 0) {
        HDF_LOGE("UseBufferOnPort bufferCount <= 0 or bufferSize <= 0");
        return HDF_ERR_INVALID_PARAM;
    }
    for (int i = 0; i < bufferCount; i++) {
        std::shared_ptr<OmxCodecBuffer> omxBuffer = std::make_shared<OmxCodecBuffer>();
        omxBuffer->size = sizeof(OmxCodecBuffer);
        omxBuffer->version.s.nVersionMajor = 1;
        omxBuffer->bufferType = CODEC_BUFFER_TYPE_AVSHARE_MEM_FD;
        int fd = AshmemCreate(0, bufferSize);
        shared_ptr<Ashmem> sharedMem = make_shared<Ashmem>(fd, bufferSize);
        omxBuffer->fd = fd;
        omxBuffer->bufferhandle = nullptr;
        omxBuffer->allocLen = bufferSize;
        omxBuffer->fenceFd = -1;
        omxBuffer->pts = 0;
        omxBuffer->flag = 0;

        if (portIndex == PortIndex::PORT_INDEX_INPUT) {
            omxBuffer->type = READ_ONLY_TYPE;  // ReadOnly
            sharedMem->MapReadAndWriteAshmem();
        } else {
            omxBuffer->type = READ_WRITE_TYPE;
            sharedMem->MapReadOnlyAshmem();
        }
        OmxCodecBuffer outBuffer;
        auto err = client_->UseBuffer((uint32_t)portIndex, *omxBuffer.get(), outBuffer);
        if (err != HDF_SUCCESS) {
            HDF_LOGE("%{public}s failed to UseBuffer with  portIndex[%{public}d]", __func__, portIndex);
            sharedMem->UnmapAshmem();
            sharedMem->CloseAshmem();
            sharedMem = nullptr;
            return err;
        }
        omxBuffer->bufferId = outBuffer.bufferId;
        omxBuffer->fd = -1;
        HDF_LOGI("UseBuffer returned bufferID [%{public}d]", omxBuffer->bufferId);

        std::shared_ptr<BufferInfo> bufferInfo = std::make_shared<BufferInfo>();
        bufferInfo->omxBuffer = omxBuffer;
        bufferInfo->avSharedPtr = sharedMem;
        bufferInfo->portIndex = portIndex;
        omxBuffers_.emplace(std::make_pair(omxBuffer->bufferId, bufferInfo));
        if (portIndex == PortIndex::PORT_INDEX_INPUT) {
            unUsedInBuffers_.push_back(omxBuffer->bufferId);
        } else {
            unUsedOutBuffers_.push_back(omxBuffer->bufferId);
        }
    }

    return HDF_SUCCESS;
}
编解码Buffer流转

用户需要先将组件设置为CODEC_STATE_EXECUTING状态,然后填充输入buffer,读取输出buffer,进行buffer的轮转。

// 设置组件进入OMX_StateExecuting状态并开始buffer的轮转
HDF_LOGI("...command to CODEC_STATE_EXECUTING....");
auto err = client_->SendCommand(CODEC_COMMAND_STATE_SET, CODEC_STATE_EXECUTING, {});
if (err != HDF_SUCCESS) {
    HDF_LOGE("%{public}s failed to SendCommand with CODEC_COMMAND_STATE_SET:CODEC_STATE_IDLE", __func__);
    return;
}
// 设置输出buffer填充
if (!FillAllTheBuffer()) {
    HDF_LOGE("%{public}s FillAllTheBuffer error", __func__);
    return;
}
// 填充输入buffer
auto t1 = std::chrono::system_clock::now();
bool eosFlag = false;
while (!eosFlag) {
    if (this->exit_) {
        break;
    }
    int bufferID = GetFreeBufferId();
    if (bufferID < 0) {
        usleep(10000);  // 10000 for wait 10ms
        continue;
    }
    auto iter = omxBuffers_.find(bufferID);
    if (iter == omxBuffers_.end()) {
        continue;
    }
    auto bufferInfo = iter->second;
    void *sharedAddr = const_cast<void *>(bufferInfo->avSharedPtr->ReadFromAshmem(0, 0));
    eosFlag = this->ReadOnePacket(fpIn_, static_cast<char *>(sharedAddr), bufferInfo->omxBuffer->filledLen);
    bufferInfo->omxBuffer->offset = 0;
    if (eosFlag) {
        bufferInfo->omxBuffer->flag = OMX_BUFFERFLAG_EOS;
    }
    err = client_->EmptyThisBuffer(*bufferInfo->omxBuffer.get());
    if (err != HDF_SUCCESS) {
        HDF_LOGE("%{public}s EmptyThisBuffer error", __func__);
        return;
    }
}
// wait
while (!this->exit_) {
    usleep(10000);  // 10000 for wait 10ms
}
// 解码完成后使组件进入OMX_StateIdle状态
auto t2 = std::chrono::system_clock::now();
std::chrono::duration<double> diff = t2 - t1;
HDF_LOGI("cost %{public}f, count=%{public}d", diff.count(), count_);
(void)client_->SendCommand(CODEC_COMMAND_STATE_SET, CODEC_STATE_IDLE, {});
return;
}

当在rk开发板上进行解码时,由于其OMX的实现不支持数据的分帧,所以需要手动分帧,目前简单实现按照起始码0x000001或0x00000001分帧发送到服务端处理。分帧代码如下:

// 文件分帧读取实现
bool CodecHdiDecode::ReadOnePacket(FILE *fp, char *buf, uint32_t &filledCount)
{
    // 读取起始码
    size_t t = fread(buf, 1, START_CODE_SIZE_FRAME, fp);
    if (t < START_CODE_SIZE_FRAME) {
        return true;
    }
    char *temp = buf;
    temp += START_CODE_SIZE_FRAME;
    bool ret = true;
    while (!feof(fp)) {
        (void)fread(temp, 1, 1, fp);
        if (*temp != START_CODE) {
            temp++;
            continue;
        }
        // 检查起始码
        if ((temp[START_CODE_OFFSET_ONE] == 0) && (temp[START_CODE_OFFSET_SEC] == 0) &&
            (temp[START_CODE_OFFSET_THIRD] == 0)) {
            fseek(fp, -START_CODE_SIZE_FRAME, SEEK_CUR);
            temp -= (START_CODE_SIZE_FRAME - 1);
            ret = false;
            break;
            }
        if ((temp[START_CODE_OFFSET_ONE] == 0) && (temp[START_CODE_OFFSET_SEC] == 0)) {
            fseek(fp, -START_CODE_SIZE_SLICE, SEEK_CUR);
            temp -= (START_CODE_SIZE_SLICE - 1);
            ret = false;
            break;
        }
        temp++;
    }
    filledCount = (temp - buf);
    return ret;
}

Codec HDI提供3个回调函数:EventHandler,EmptyBufferDone和FillBufferDone。

  • EventHandler:主要命令完成后的通知,例如:CODEC_STATE_IDLE转为CODEC_STATE_EXECUTING的命令执行成功通知等。
  • EmptyBufferDone:输入数据消费完毕,客户端需要重新填入待编解码数据,再次调用EmptyThisBuffer。
  • FillBufferDone:输出数据填充完毕,客户端需要读取已编码/解码数据,再次调用FillThisBuffer。
// EmptyBufferDone回调处理示例
int32_t CodecHdiDecode::OnEmptyBufferDone(const struct OmxCodecBuffer &buffer)
{
    HDF_LOGI("OnEmptyBufferDone, bufferId [%{public}d]", buffer.bufferId);
    unique_lock<mutex> ulk(lockInputBuffers_);
    unUsedInBuffers_.push_back(buffer.bufferId);
    return HDF_SUCCESS;
}
// FillBufferDone回调处理示例
int32_t CodecHdiDecode::OnFillBufferDone(const struct OmxCodecBuffer &buffer)
{
    HDF_LOGI("OnFillBufferDone, bufferId [%{public}d]", buffer.bufferId);
    if (exit_) {
        return HDF_SUCCESS;
    }

    auto iter = omxBuffers_.find(buffer.bufferId);
    if ((iter == omxBuffers_.end()) || (iter->second == nullptr)) {
        return HDF_SUCCESS;
    }
    count_++;
    // read buffer
    auto bufferInfo = iter->second;
    if (bufferInfo->avSharedPtr != nullptr) {
        const void *addr = bufferInfo->avSharedPtr->ReadFromAshmem(buffer.filledLen, buffer.offset);
        (void)fwrite(addr, 1, buffer.filledLen, fpOut_);
    } else if (bufferInfo->bufferHandle != nullptr && gralloc_ != nullptr) {
        gralloc_->Mmap(*bufferInfo->bufferHandle);
        (void)fwrite(bufferInfo->bufferHandle->virAddr, 1, buffer.filledLen, fpOut_);
        gralloc_->Unmap(*bufferInfo->bufferHandle);
    }

    (void)fflush(fpOut_);
    if (buffer.flag == OMX_BUFFERFLAG_EOS) {
        // end
        exit_ = true;
        HDF_LOGI("OnFillBufferDone the END coming");
        return HDF_SUCCESS;
    }
    // call fillthisbuffer again
    auto err = client_->FillThisBuffer(*bufferInfo->omxBuffer.get());
    if (err != HDF_SUCCESS) {
        HDF_LOGE("%{public}s FillThisBuffer error", __func__);
        return HDF_SUCCESS;
    }
    return HDF_SUCCESS;
}
// EventHandler示例
int32_t CodecHdiDecode::EventHandler(CodecEventType event, const EventInfo &info)
{
    switch (event) {
        case CODEC_EVENT_CMD_COMPLETE: {
            CodecCommandType cmd = (CodecCommandType)info.data1;
            if (CODEC_COMMAND_STATE_SET == cmd) {
                HDF_LOGI("CODEC_COMMAND_STATE_SET reached, status is %{public}d", info.data2);
                this->OnStatusChanged();
            }
            break;
        }
        case OMX_EventPortSettingsChanged: {
            HDF_LOGI("OMX_EventPortSeetingsChanged reached");
            this->HandleEventPortSettingsChanged(info.data1, info.data2);
        }

        default:
            break;
    }

    return HDF_SUCCESS;
}
接口去初始化

组件关闭前,需要将组件状态修改为CODEC_STATE_IDLE,然后开始释放输入输出Buffer,再将组件状态修改为CODEC_STATE_LOADED,最后再调用DestoryComponent去关闭组件。

Buffer释放示例
// 发送命令使组件进入OMX_StateLoaded状态
client_->SendCommand(CODEC_COMMAND_STATE_SET, CODEC_STATE_LOADED, {});

// 释放所有申请的buffer
auto iter = omxBuffers_.begin();
while (iter != omxBuffers_.end()) {
    auto bufferInfo = iter->second;
    iter = omxBuffers_.erase(iter);
    (void)client_->FreeBuffer((uint32_t)bufferInfo->portIndex, *bufferInfo->omxBuffer.get());
    bufferInfo = nullptr;
}

unUsedInBuffers_.clear();
unUsedOutBuffers_.clear();

// buffer释放后组件即进入OMX_StateLoaded状态
CodecStateType status = CODEC_STATE_INVALID;
int32_t err = HDF_SUCCESS;
int32_t tryCount = 3;
do {
    err = client_->GetState(status);
    if (err != HDF_SUCCESS) {
        HDF_LOGE("%s GetState error [%{public}x]", __func__, err);
        break;
    }
    if (status != CODEC_STATE_LOADED) {
        HDF_LOGI("Wait for OMX_StateLoaded status");
        this->WaitForStatusChanged();
    }
    tryCount--;
} while ((status != CODEC_STATE_LOADED) && (tryCount > 0));
组件实例释放示例
// 组件实例释放
void CodecHdiDecode::Release()
{
    omxMgr_->DestoryComponent(componentId_);
    client_ = nullptr;
    callback_ = nullptr;
    omxMgr_ = nullptr;
}

常见问题

解码过程中部分绿屏

现象描述

解码过程中,开始能正常解码,后续绿屏比较多。

可能原因

OpenMax不支持分帧。

解决办法

上层在调用EmptyThisBuffer时,需要按照每次一帧的方式传入。

解码过程中全是绿屏

现象描述

解码过程中,解码失败全部播放不了。

可能原因

OpenMax对AVCC格式的数据处理,第一帧一定要是extra_data,可能没有正常输入extra_data导致AVCC格式解码失败。

解决办法

将sps和pps按照extrea_data格式写入buffer,并设置好buffer的flag为OMX_BUFFERFLAG_EXTRADATA。

编码输出播放不了

现象描述

编码输出视频不正确,将生成的视频流(如H264流)写入文件后,通过ffplay工具播放不了。

可能原因

  1. 输出端口的xFramerate参数未正常设置。
  2. 如果设置了参数OMX_VIDEO_PARAM_AVCTYPE,请检查此参数是否正确。

解决办法

请看编码时codec_host的日志,搜索“encode params init settings”,确认是否出现异常的参数。如果是framerate为0,则是原因1,需要将正常的framerate左移16位;如果是其它参数异常,可能是原因2,需要检查其相应的参数。

最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。 

这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。

希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

鸿蒙(HarmonyOS NEXT)最新学习路线

  •  HarmonOS基础技能

  • HarmonOS就业必备技能 
  •  HarmonOS多媒体技术

  • 鸿蒙NaPi组件进阶

  • HarmonOS高级技能

  • 初识HarmonOS内核 
  • 实战就业级设备开发

有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

《鸿蒙 (OpenHarmony)开发入门教学视频》

《鸿蒙生态应用开发V2.0白皮书》

图片

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

图片

 《鸿蒙开发基础》

  • ArkTS语言
  • 安装DevEco Studio
  • 运用你的第一个ArkTS应用
  • ArkUI声明式UI开发
  • .……

图片

 《鸿蒙开发进阶》

  • Stage模型入门
  • 网络管理
  • 数据管理
  • 电话服务
  • 分布式应用开发
  • 通知与窗口管理
  • 多媒体技术
  • 安全技能
  • 任务管理
  • WebGL
  • 国际化开发
  • 应用测试
  • DFX面向未来设计
  • 鸿蒙系统移植和裁剪定制
  • ……

图片

《鸿蒙进阶实战》

  • ArkTS实践
  • UIAbility应用
  • 网络案例
  • ……

图片

 获取以上完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料

总结

总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/538816.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【题目】【信息安全管理与评估】2022年国赛高职组“信息安全管理与评估”赛项样题1

【题目】【信息安全管理与评估】2022年国赛高职组“信息安全管理与评估”赛项样题1 信息安全管理与评估 网络系统管理 网络搭建与应用 云计算 软件测试 移动应用开发 任务书&#xff0c;赛题&#xff0c;解析等资料&#xff0c;知识点培训服务 添加博主wx&#xff1a;liuliu548…

【微信小程序】canvas开发笔记

【微信小程序】canvasToTempFilePath:fail fail canvas is empty 看说明书 最好是先看一下官方文档点此前往 如果是canvas 2d 写canvas: this.canvas,&#xff0c;如果是旧版写canvasId: ***, 解决问题 修改对应的代码&#xff0c;如下所示&#xff0c;然后再试试运行&#x…

Java 基于微信小程序的汽车4S店客户管理小程序,附源码

博主介绍&#xff1a;✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3…

Python——详细解析目标检测xml格式标注转换为txt格式

本文简述了目标检测xml格式标注的内容&#xff0c;以及yolo系列模型所需的txt格式标注的内容。并提供了一个简单的&#xff0c;可以将xml格式标注文件转换为txt格式标注文件的python脚本。 1. xml格式文件内容 <size>标签下为图片信息&#xff0c;包括 <width> …

排序算法之选择排序

目录 一、简介二、代码实现三、应用场景 一、简介 算法平均时间复杂度最好时间复杂度最坏时间复杂度空间复杂度排序方式稳定性选择排序O(n^2 )O(n^2)O(n^2)O(1)In-place不稳定 稳定&#xff1a;如果A原本在B前面&#xff0c;而AB&#xff0c;排序之后A仍然在B的前面&#xff1…

RREA论文阅读

Relational Reflection Entity Alignment 关系反射实体对齐 ABSTRACT 实体对齐旨在识别来自不同知识图谱(KG)的等效实体对&#xff0c;这对于集成多源知识图谱至关重要。最近&#xff0c;随着 GNN 在实体对齐中的引入&#xff0c;近期模型的架构变得越来越复杂。我们甚至在这…

低频电磁仿真 | 新能源汽车性能提升的利器

永磁同步电机 新能源汽车的心脏 近年来&#xff0c;全球变暖的趋势日益加剧&#xff0c;极端天气事件层出不穷&#xff0c;这些现象都反映出当前气候形势的严峻性。为了应对这一全球性挑战&#xff0c;各国纷纷采取行动&#xff0c;制定了一系列降碳、减碳的措施。中国在2020年…

PostgreSQL15 + PostGis + QGIS安装教程

目录 下载1、PostgreSQL安装1.1、环境变量配置 2、PostGIS安装2.1、安装插件 3、QGIS下载3.1、安装3.2、测试 下载 PostgreSQL15安装&#xff1a;下载地址 PostGIS安装&#xff1a;下载地址&#xff08;倒数第二个&#xff09; 1、PostgreSQL安装 下载安装包之后一直点下一步…

MySQL中的SQL高级语句[二]

使用语言 MySQL 使用工具 Navicat Premium 16 代码能力快速提升小方法&#xff0c;看完代码自己敲一遍&#xff0c;十分有用 拖动表名到查询文件中就可以直接把名字拉进来以下是使用脚本方法&#xff0c;也可以直接进行修改中括号&#xff0c;就代表可写可不写 有些地方的代…

基于单片机的智能锁芯报警系统设计

摘 要:在传统的智能锁芯报警系统中,存在响应时间较长的问题,为此,提出一种基于单片机的智能锁芯报警系统。通过控制模块、智能锁芯设置模块、报警模块、中断模块、液晶模块等,建立系统总体框架,根据系统总体框架,通过单片机、电源适配器、智能锁芯、报警器、LED灯等…

CMake构建OpenCv并导入QT项目过程中出现的问题汇总

前言 再此之前请确保你的环境变量是否配置&#xff0c;这是总共需要配置的环境变量 E:\cmake\bin E:\OpenCv\opencv\build\x64\vc15\bin F:\Qt\Tools\mingw730_64\bin F:\Qt\5.12.4\mingw73_64\bin 问题一&#xff1a; CMake Error: CMake was unable to find a build program…

Docker 学习笔记(七):介绍 Dockerfile 相关知识,使用 Dockerfile 构建自己的 centos 镜像

一、前言 记录时间 [2024-4-12] 系列文章简摘&#xff1a; Docker学习笔记&#xff08;二&#xff09;&#xff1a;在Linux中部署Docker&#xff08;Centos7下安装docker、环境配置&#xff0c;以及镜像简单使用&#xff09; Docker 学习笔记&#xff08;三&#xff09;&#x…

51单片机入门_江协科技_27~28_OB记录的自学笔记_AT24C02数据存储秒表

27. AT24C02(I2C总线) 27.1. 存储器介绍 27.2. 存储器简化模型介绍&#xff0c;存储原理 27.3. AT24C02介绍 •AT24C02是一种可以实现掉电不丢失的存储器&#xff0c;可用于保存单片机运行时想要永久保存的数据信息 •存储介质&#xff1a;E2PROM •通讯接口&#xff1a;I2…

ElasticSearch中使用bge-large-zh-v1.5进行向量检索(一)

一、准备 系统&#xff1a;MacOS 14.3.1 ElasticSearch&#xff1a;8.13.2 Kibana&#xff1a;8.13.2 BGE是一个常见的文本转向量的模型&#xff0c;在很多大模型RAG应用中常常能见到&#xff0c;但是ElasticSearch中默认没有。BGE模型有很多版本&#xff0c;本次采用的是bg…

【QT入门】Qt自定义控件与样式设计之控件提升与自定义控件

【QT入门】Qt自定义控件与样式设计之控件提升与自定义控件 往期回顾 【QT入门】Qt自定义控件与样式设计之QProgressBar用法及qss-CSDN博客 【QT入门】 Qt自定义控件与样式设计之QSlider用法及qss-CSDN博客 【QT入门】Qt自定义控件与样式设计之qss的加载方式-CSDN博客 一、最终…

P6170 [USACO16FEB] Circular Barn G

题目 [ 传送门 ] 题目描述 作为当代建筑的爱好者&#xff0c;Farmer John 建造了一个圆形新谷仓&#xff0c;谷仓内部 个房间排成环形&#xff0c;按顺时针顺序编号为&#xff0c;每个房间都有通往与其相邻的左右房间的门&#xff0c;还有一扇门通往外面。 现在 FJ 有 n 头奶牛…

第六季:RTSP协议详解与实时流视频预览

目录 前言1 环境准备2 H.264编码原理和基本概念2.1 图像冗余信息2.2 h.264编码相关的一些概念2.3 h264视频流总体分析2.4 H264的NAL单元详解22.4.1 相关概念 2.5 NALU详解2.6 sps和pps详解2.7 H264的profile和level2.8 序列sequence 前言 本篇文章用于记录实验过程 1 环境准备…

Django中间件路由映射自动加/斜杠问题原因及分析

输入 http://127.0.0.1:8000/main/index/ 输入 http://127.0.0.1:8000/main/index 路由定义情况 urlpatterns [path("index/", views.index) ]可以发现我在输入URL的index路由时&#xff0c;如果没有和Django定义的路由匹配规则一样的话&#xff0c;浏览器自…

深度学习在三维点云处理与三维重建中的应用探索

目录 点云数据处理 数据清洗 数据降噪和简化 数据配准 特征提取 数据增强 数据组织 性能考量 PointNet PointNet 算法问题 改进方法 三维重建 重建算法 架构模块 流程步骤 标记说明 优点和挑战 点云数据处理 数据清洗 去噪&#xff1a;点云数据通常包含噪声…

云计算:Linux 部署 OVS 集群(控制端)实现OpenFlow

目录 一、实验 1.环境 2.Linux 部署 OVS 集群&#xff08;控制端&#xff09; 3.控制端对接服务端OVS网元 4.服务端OVS添加流表 5.服务端删除OVS 二、问题 1. ODL如何查找已安装插件 2.查看流表显示不全 3.如何删除OVS流表 一、实验 1.环境 (1) 主机 表1 宿主机 主…