
OpenHarmony设备开发 标准系统方案之瑞芯微RK3568移植案例(上)
本文章是基于瑞芯微RK3568芯片的DAYU200开发板,进行标准系统相关功能的移植,主要包括产品配置添加,内核启动、升级,音频ADM化,Camera,TP,LCD,WIFI,BT,vibrator、sensor、图形显示模块的适配案例总结,以及相关功能的适配。
产品配置和目录规划
产品配置
在产品//productdefine/common/device
目录下创建以rk3568名字命名的json文件,并指定CPU的架构。//productdefine/common/device/rk3568.json
配置如下:
在//productdefine/common/products
目录下创建以产品名命名的rk3568.json文件。该文件用于描述产品所使用的SOC 以及所需的子系统。配置如下
主要的配置内容包括:
- product_device:配置所使用的SOC。
- type:配置系统的级别, 这里直接standard即可。
- parts:系统需要启用的子系统。子系统可以简单理解为一块独立构建的功能块。
已定义的子系统可以在//build/subsystem_config.json
中找到。当然你也可以定制子系统。
这里建议先拷贝Hi3516DV300开发板的配置文件,删除掉hisilicon_products这个子系统。这个子系统为Hi3516DV300 SOC编译内核,不适合rk3568。
目录规划
参考Board和SoC解耦的设计思路,并把芯片适配目录规划为:
内核启动
二级启动
二级启动简单来说就是将之前直接挂载sytem,从system下的init启动,改成先挂载ramdsik,从ramdsik中的init 启动,做些必要的初始化动作,如挂载system,vendor等分区,然后切到system下的init 。
Rk3568适配主要是将主线编译出来的ramdisk 打包到boot_linux.img中,主要有以下工作:
1.使能二级启动
在productdefine/common/device/rk3568.json 中使能enable_ramdisk。
2.把主线编译出来的ramdsik.img 打包到boot_linux.img
配置:
由于rk 启动uboot 支持从ramdisk 启动,只需要在打包boot_linux.img 的配置文件中增加ramdisk.img ,因此没有使用主线的its格式,具体配置就是在内核编译脚本make-ohos.sh 中增加:
打包
增加了打包boot镜像的脚本make-boot.sh,供编译完ramdisk,打包boot 镜像时调用, 主要内容:
调用make-boot.sh 的修改可以参考如下pr:
https://gitee.com/openharmony/build/pulls/569/files
INIT配置
init相关配置请参考启动子系统的规范要求即可
音频
RK3568 Audio总体结构图
ADM适配方案介绍
RK3568平台适配ADM框架图
- ADM Drivers adapter
主要完成Codec/DMA/I2S驱动注册,使得ADM可以加载驱动节点;并注册ADM与Drivers交互的接口函数 - ADM Drivers impl
主要完成ADM Drivers adapter接口函数的实现,以及Codec_config.hcs/dai_config.hcs等配置信息的获取,并注册到对应的设备 - Linux Drivers
ADM Drivers impl可以直接阅读硬件手册,完成驱动端到端的配置;也可以借用Linux原生驱动实现与接口,减少开发者工作量。
目录结构
RK3568适配ADM详细过程
梳理平台Audio框架
梳理目标平台的Audio结构,明确数据流与控制流通路。
- 针对RK3568平台,Audio的结构相对简单见RK3568 Audio总体结构图,Codec作为一个独立设备。I2C完成对设备的控制,I2S完成Codec设备与CPU之间的交互。
- 结合原理图整理I2S通道号,对应的引脚编号;I2C的通道号,地址等硬件信息。
- 获取Codec对应的datasheet,以及RK3568平台的Datasheet(包含I2S/DMA通道等寄存器的介绍)。
熟悉并了解ADM结构
ADM结构框图如下,Audio Peripheral Drivers和Platform Drivers为平台适配需要完成的工作。
结合第1步梳理出来的Audio结构分析,Audio Peripheral Drivers包含Rk809的驱动,Platform Drivers包含DMA驱动和I2S驱动。
需要适配的驱动 | ADM对应模块 | 接口文件路径 |
RK809驱动 | Accessory | drivers/framework/include/audio/audio_accessory_if.h |
DMA驱动 | platform | drivers/framework/include/audio/audio_platform_if.h |
I2S驱动 | DAI | drivers/framework/include/audio/audio_dai_if.h.h |
搭建驱动代码框架
配置HCS文件
在device_info.hcs文件中Audio下注册驱动节点
根据接入的设备,选择Codec节点还是Accessory节点,配置硬件设备对应的私有属性(包含寄存器首地址,相关control寄存器地址)涉及Codec_config.hcs和DAI_config.hcs
配置相关介绍见Audio hcs配置章节以及ADM框架的audio_parse模块代码。
codec/accessory模块
- 将驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName一致
2.Codec模块需要填充:
g_codecData:codec设备的操作函数集和私有数据集。
g_codecDaiDeviceOps:codecDai的操作函数集,包括启动传输和参数配置等函数接口。
g_codecDaiData:codec的数字音频接口的操作函数集和私有数据集。
3.完成 bind、init和release函数的实现
4.验证
在bind和init函数加调试日志,编译版本并获取系统系统日志:
DAI模块
- 将I2S驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName一致
2.DAI模块填充:
3.完成 bind、init和release函数的实现
4.验证
在bind/init函数加调试日志,编译版本并获取系统系统日志
Platform模块
- 将DMA驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName一致
2.DMA模块需要填充:
3.完成 bind、init和release函数的实现
4.验证
在bind和init函数加调试日志,编译版本并获取系统系统日志
驱动适配
code/accessory模块
- 读取DTS文件,获取到对应设备节点,使用Linux原生的驱动注册函数,获取到对应device。
2.读写寄存器函数封装 根据上述获取到的device, 使用Linux的regmap函数,开发者不需要获取模块的基地址 获取rk817的regmap代码段
寄存器读写函数代码段
3.寄存器初始化函数
因为使用Linux的regmap函数,所以需要自行定义RegDefaultInit函数,读取hcs中initSeqConfig的寄存器以及数值来进行配置
RK809RegDefaultInit代码段
4.封装控制接口的读写函数
设置控制读写函数为RK809CodecReadReg和RK809CodecWriteReg
封装控制接口的读写函数
因为原来的读写原型,涉及三个参数(unsigned long virtualAddress,uint32_t reg, uint32_t *val),其中virtualAddress我们并不需要用到,所以封装个接口即可,封装如下
5.其他ops函数
- Rk809DeviceInit,读取hcs文件,初始化Codec寄存器,同时将对应的control配置(/* reg, rreg, shift, rshift, min, max, mask, invert, value */添加到kcontrol,便于dispatch contro进行控制
- Rk809DaiStartup, 读取hcs文件,配置可选设备(codec/accessory)的控制寄存器
- Rk809DaiHwParams, 根据hal下发的audio attrs(采样率、format、channel等),配置对应的寄存器
- RK809NormalTrigger,根据hal下发的操作命令码,操作对应的寄存器,实现Codec的启动停止、录音和放音的切换等
DAI(i2s)模块
- 读写寄存器函数 思路与Codec模块的一致,读取Linux DTS文件,使用Linux的regmap函数完成寄存器的读写操作
2.其他ops函数
- Rk3568DaiDeviceInit 原始框架,主要完成DAI_config.hcs参数列表的读取,与HwParams结合,完成参数的设置。
- Rk3568DaiHwParams 主要完成I2S MCLK/BCLK/LRCLK时钟配置。
- 根据不同采样率计算MCLK
2.根据获取的mclk,计算BCLK/LRclk分频系数
- Rk3568NormalTrigger 根据输入输出类型,以及cmd(启动/停止/暂停/恢复),完成一系列配置:
- mclk的启停
- DMA搬运的启停
- 传输的启停 详细实现见代码,参考Linux原生I2s驱动对应接口函数
Platform(DMA)模块
ops函数相关函数
- Rk3568DmaBufAlloc/Rk3568DmaBufFree
获取DMA设备节点,参考I2s设备获取方式,使用系统函数dma_alloc_wc/dma_free_wc,完成DMA虚拟内存与物理内存的申请/释放 - Rk3568DmaRequestChannel
使用Linux DMA原生接口函数获取DMA传输通道,dma_request_slave_channel
- Rk3568DmaConfigChannel
- Rk3568DmaSubmit/Rk3568DmaPending
使用Linux DMA原生接口函数dmaengine_prep_dma_cyclic,初始化一个具体的周期性的DMA传输描述符dmaengine_submit接口将该描述符放到传输队列上,然后调用dma_async_issue_pending接口,启动传输。 - Rk3568PcmPointer
第4步完成之后,ADM框架调用Rk3568PcmPointer,循环写cirBuf,计算pointer
- Rk3568DmaPause
使用Linux DMA原生接口函数dmaengine_terminate_async,停止DMA传输
- Rk3568DmaResume
暂停使用的DMA停止函数,对应恢复,相当于重启DMA传输,执行Rk3568DmaSubmit/Rk3568DmaPending相关操作即可完成
适配中遇到问题与解决方案
- 播放一段时间后,停止播放,持续有尖锐的很小的声音 问题原因:播放停止后,Codec相关器件没有下电 解决方案:注册Codec的trigger函数,当接收到Cmd为Stop时,对Codec进行下电
- 播放一段时间后,停止播放,然后重新播放没有声音 问题原因:DMA驱动的PAUSE接口函数,并未停止DMA传输 解决方案:暂停状态不再使用DMA的PAUSE函数,而是使用DAM传输停止接口; 相对应的,恢复函数的业务逻辑相当于重启DMA传输,执行 Rk3568DmaSubmit/Rk3568DmaPending相关操作即可完成
- 播放存在杂音 问题原因:DMA数据搬运pointer位置不正确 解决方案:Rk3568PcmPointer函数返回值为DMA搬运的内存位置,用缓存区buf与dma_state.residue的差值计算
- 可以放音,但Mclk引脚没有时钟信号 问题原因:DTS文件pin-ctrl没有配置mclk的引脚 解决方案:修改DTS文件
Camera
基本概念
OpenHarmony相机驱动框架模型对上实现相机HDI接口,对下实现相机Pipeline模型,管理相机各个硬件设备。各层的基本概念如下。
- HDI实现层:对上实现OHOS相机标准南向接口。
- 框架层:对接HDI实现层的控制、流的转发,实现数据通路的搭建、管理相机各个硬件设备等功能。
- 适配层:屏蔽底层芯片和OS差异,支持多平台适配。
Camera驱动框架介绍
源码框架介绍
Camera 驱动框架所在的仓为:drivers_peripheral,源码目录为:“drivers/peripheral/camera”。
Camera hcs文件是每个chipset可配置的。所以放在chipset相关的仓下。以rk3568为例。仓名为: vendor_hihope,源码目录为:“vendor/hihope/rk3568/hdf_config/uhdf/camera”。
Camera chipset 相关代码路径以3568为例仓名为:device_hihope。路径为:device/board/hihope/rk3568/camera/
Camera 驱动框架配置
RK3568 配置文件路径:
“vendor/hihope/rk3568/hdf_config/uhdf/device_info.hcs”。说明:其他平台可参考RK3568适配。
参数说明: Host:一个host节点即为一个独立进程,如果需要独立进程,新增属于自己的host节点。 Policy: 服务发布策略,HDI服务请设置为“2” moduleName: 驱动实现库名。 serviceName:服务名称,请保持全局唯一性。
Camera_host驱动实现入口
文件路径:drivers/peripheral/camera/interfaces/hdi_ipc/server/src/camera_host_driver.cpp
分发设备服务消息 cmd Id:请求消息命令字。 Data:其他服务或者IO请求数据。 Reply:存储返回消息内容数据。
绑定设备服务:初始化设备服务对象和资源对象。
驱动初始化函数: 探测并初始化驱动程序
驱动资源释放函数 : 如已经绑定的设备服务对象
定义驱动描述符:将驱动代码注册给驱动框架。
Camera配置信息介绍
Camera模块内部,所有配置文件使用系统支持的HCS类型的配置文件,HCS类型的配置文件,在编译时,会转成HCB文件,最终烧录到开发板里的配置文件即为HCB格式,代码中通过HCS解析接口解析HCB文件,获取配置文件中的信息。
Camera适配介绍
新产品平台适配简介
drivers/peripheral/camera/hal/camera.gni 文件中可根据编译时传入的product_company product_name和device_name调用不同chipset的product.gni
在如下路径的product.gni指定了编译不同chipset相关的代码的路径:
如下是rk3568的product.gni:
product.gni中指定了chipset_build_deps camera_device_manager_deps 和 camera_pipeline_core_deps 三个代码编译路径。该路径在drivers/peripheral/camera/hal/BUILD.gn中会被使用
框架适配介绍
以V4l2为例,pipeline的连接方式是在HCS配置文件中配置连接,数据源我们称之为SourceNode,主要包括硬件设备的控制、数据流的轮转等。 ISPNode可根据需要确定是否添加此Node,因为在很多操作上其都可以和SensorNode统一为SourceNode。SinkNode为pipeline中数据传输的重点,到此处会将数据传输回buffer queue中。
pipeline中的Node是硬件/软件模块的抽象,所以对于其中硬件模块Node,其是需要向下控制硬件模块的,在控制硬件模块前,需要先获取其对应硬件模块的deviceManager,通过deviceManager向下传输控制命令/数据buffer,所以deviceManager中有一个v4l2 device manager抽象模块,用来创建各个硬件设备的manager、controller.如上sensorManager、IspManager,sensorController等,所以v4l2 device manager其实是各个硬件设备总的一个管理者。
deviceManager中的controller和驱动适配层直接交互。
基于以上所描述,如需适配一款以linux v4l2框架的芯片平台,只需要修改适配如上图中颜色标记模块及HCS配置文件(如为标准v4l2框架,基本可以延用当前已适配代码),接下来单独介绍修改模块。
主要适配添加如下目录:
“vendor/hihope/rk3568/hdf_config/uhdf/camera/”:当前芯片产品的HCS配置文件目录。
“device/hihope/rk3568/camera/”:当前芯片产品的代码适配目录。
“drivers/peripheral/camera/hal/adapter/platform/v4l2”:平台通用公共代码。
HCS配置文件适配介绍
以RK3568开发板为例,其hcs文件应该放在对应的路径中。
hdi_impl下的“camera_host_config.hcs”为物理/逻辑Camera配置、能力配置,此处的物理/逻辑Camera配置,需要在hal内部使用,逻辑Camera及能力配置需要上报给上层,请按照所适配的芯片产品添加其能力配置。其中所用的能力值为键值对,定义在//drivers/peripheral/camera/hal/hdi_impl/include/camera_host/metadata_enum_map.h中。
pipeline_core下的“config.hcs”为pipeline的连接方式,按场景划分每一路流由哪些Node组成,其连接方式是怎样的。
上面为preview场景的示例,normal_preview为该场景的名称,source和sink为Node,source为数据数据源端,sink为末端,source为第一个node,node的名称是source#0,status、in/out_port分别为Node状态及输入/输出口的配置。
以in_port_0为例,name = “in0”代表它的输入为“port0”,它的对端为source node的port口out0口,direction为它的源Node和对端Node是否为直连方式。如新添加芯片产品,必须按实际连接方式配置此文件。
新增功能node时需继承NodeBase类,且在cpp文件中注册该node。具体可参考//drivers/peripheral/camera/hal/pipeline_core/nodes/src下已经实现的node。
param.hcs为场景、流类型名及其id定义,pipeline内部是以流id区分流类型的,所以此处需要添加定义。
Chipset 和Platform适配介绍
platform为平台性公共代码,如linux标准v4l2适配接口定义,为v4l2框架适配的通用node.以及为v4l2框架适配的通用device_manager等。目录结构如下:
“platform”目录下的“v4l2”包含了“src”, “src”中“driver_adapter”为linux v4l2标准适配接口,如有定制化功能需求,可继承driver_adapter,将定制化的具体功能接口放在chipset中实现。如无芯片定制化功能,可直接使用已有的driver_adapter。
platform目录下的Nodes为依据linux v4l2标准实现的硬件模块v4l2_source_node和uvc_node(usb热插拔设备,此模块也为linux标准接口,可直接使用),如下图为v4l2_source_node的接口声明头文件。
Init接口为模块初始化接口。
Start为使能接口,比如start stream功能等。
Stop为停止接口。
GetDeviceController为获取deviceManager对应的controller接口。
chipset为具体某芯片平台相关代码,例如,如和“rk3568”开发板 为例。device_manager目录下可存放该开发板适配过的sensor的相关配置文件。pipeline_core路径下可以存放由chipset开发者为满足特点需求增加的pipeline node等。
device/board/hihope/rk3568/camera/目录包含了“include”和“src”,“camera_demo”“src”中“device_manager”中包含了chipset 适配的sensor的文件,配合platform下device_manager的设备管理目录,主要对接pipeline,实现平台特有的硬件处理接口及数据buffer的下发和上报、metadata的交互。
下图为device_manager的实现框图,pipeline控制管理各个硬件模块,首先要获取对应设备的manager,通过manager获取其对应的controller,controller和对应的驱动进行交互 。
deviceManager中需要实现关键接口介绍。
PowerUp为上电接口,OpenCamera时调用此接口进行设备上电操作。 PowerDown为下电接口,CloseCamera时调用此接口进行设备下电操作。 Configures为Metadata下发接口,如需设置metadata参数到硬件设备,可实现此接口进行解析及下发。 Start为硬件模块使能接口,pipeline中的各个node进行使能的时候,会去调用,可根据需要定义实现,比如sensor的起流操作就可放在此处进行实现。 Stop和Start为相反操作,可实现停流操作。 SendFrameBuffer为每一帧buffer下发接口,所有和驱动进行buffer交互的操作,都是通过此接口进行的。 SetNodeCallBack为pipeline,通过此接口将buffer回调函数设置到devicemanager。 SetMetaDataCallBack为metadata回调接口,通过此接口将从底层获取的metadata数据上报给上层。 BufferCallback上传每一帧已填充数据buffer的接口,通过此接口将buffer上报给pipeline。 SetAbilityMetaDataTag设置需要从底层获取哪些类型的metadata数据,因为框架支持单独获取某一类型或多类型的硬件设备信息,所以可以通过此接口,获取想要的metadata数据。
其余接口可参考“drivers/peripheral/camera/hal/adapter/platform/v4l2/src/device_manager/”
IPP适配介绍
IPP是pipeline 中的一个算法插件模块,由ippnode加载,对流数据进行算法处理,ippnode支持同时多路数据输入,只支持一路数据输出。ippnode加载算法插件通过如下hcs文件指定: vendor/${product_company}/${product_name}/hdf_config/uhdf/camera/pipeline_core/ipp_algo_config.hcs 其中:
name:算法插件名称 description:描述算法插件的功能 path:算法插件所在路径 mode:算法插件所运行的模式
算法插件可运行的模式由 drivers/peripheral/camera/hal/pipeline_core/ipp/include/ipp_algo.h中的IppAlgoMode提供,可以根据需要进行扩展。
算法插件由gn文件 device/${product_company}/${device_name}/camera/BUILD.gn进行编译,算法插件需实现如下接口(接口由ipp_algo.h指定)供ippnode调用:
1) Init : 算法插件初始化接口,在起流前被ippnode 调用,其中IppAlgoMeta 定义在ipp_algo.h 中,为ippnode和算法插件提供非图像数据的传递通道,如当前运行的场景,算法处理后输出的人脸坐标等等,可根据实际需求进行扩展。 2) Start:开始接口,起流时被ippnode 调用 3) Flush:刷新数据的接口,停流之前被ippnode 调用。此接口被调用时,算法插件需尽可能快地停止处理。 4) Process: 数据处理接口,每帧数据都通过此接口输入至算法插件进行处理。inBuffer是一组输入buffer,inBufferCount是输入buffer 的个数,outBuffer是输出buffer,meta是算法处理时产生的非图像数据,IppAlgoBuffer在ipp_algo.h中定义 5) Stop:停止处理接口,停流时被ippnode调用
其中上边代码中的id指的是和ippnode对应的port口id,比如inBuffer[0]的id为0,则对应的是ippnode 的第0个输入port口。需要注意的是outBuffer可以为空,此时其中一个输入buffer 被ippnode作为输出buffer传递到下个node,inBuffer至少有一个buffer不为空。输入输出buffer 由pipeline配置决定。 比如在普通预览场景无算法处理且只有一路拍照数据传递到ippnode的情况下,输入buffer只有一个,输出buffer为空,即对于算法插件输入buffer 进行了透传; 比如算法插件进行两路预览图像数据进行合并的场景,第一路buffer需要预览送显示。把第二路图像拷贝到第一路的buffer即可,此时输入buffer有两个,输出buffer为空; 比如在算法插件中进行预览数据格式转换的场景,yuv转换为RGBA,那么只有一个yuv格式的输入buffer的情况下无法完成RGBA格式buffer的输出,此时需要一个新的buffer,那么ippnode的输出port口buffer作为outBuffer传递到算法插件。也即输入buffer只有一个,输出buffer也有一个。
ippnode的port口配置请查看3.3小节的config.hcs的说明。
适配V4L2驱动实例
本章节目的是在v4l2框架下适配RK3568开发板。
区分V4L2 platform相关代码并将其放置“drivers/peripheral/camera/hal/adapter/platform/v4l2”目录下,该目录中包含了“device_manager”“driver_adapter”和“pipeline_core”三个目录。其中“driver_adapter”目录中存放着v4l2协议相关代码。可通过它们实现与v4l2底层驱动交互。该目录下“Pipeline_core”目录与“drivers/peripheral/camera/hal/pipeline_core”中代码组合为pipeline框架。v4l2_source_node 和 uvc_node为v4l2专用Node。device_manager目录存放着向北与pipeline向南与v4l2 adapter交互的代码
区分V4L2 chipset相关代码并将其放置在“device/ ${product_company}/${device_name} /camera”目录下。
其中“driver_adapter”目录中包含了关于RK3568 driver adapter的测试用例头文件。Camera_demo目录存放了camera hal 中demo测试用例的chipset相关的头文件。device_manager存放了RK3568适配的camera sensor 读取设备能力的代码 其中,project_hardware.h 比较关键,存放了device_manager支持当前chipset的设备列表。如下:
修改编译选项来达到根据不同的编译chipset来区分v4l2和其他框架代码编译。增加device/${product_company}/${device_name}/camera/product.gni
当“product.gni”被// drivers/peripheral/camera/hal/camera.gni加载,就说明要编译v4l2相关代码。在//drivers/peripheral/camera/hal/camera.gni中根据编译时传入的product_name和device_name名来加载相应的gni文件。
“drivers/peripheral/camera/hal/BUILD.gn”中会根据 chipset_build_deps camera_device_manager_deps 和 camera_pipeline_core_deps来编译不同的chipset
Camera hal层向下屏蔽了平台及芯片差异,对外(Camera service或者测试程序)提供统一接口,其接口定义在“drivers/peripheral/camera/interfaces/include”目录下:
测试时,只需要针对所提供的对外接口进行测试,即可完整测试Camera hal层代码,具体接口说明,可参考“drivers/peripheral/camera/interfaces”目录下的“README_zh.md”和头文件接口定义。具体的调用流程,可参考测试demo:drivers/peripheral/camera/hal/init。
camera适配过程中问题以及解决方案
修改SUBWINDOW_TYPE和送显format
修改RGBA888送显,模式由video 改为 SUBWINDOW_TYPE为normal模式:
由于openharmony 较早实现的是3516平台camera, 该平台采用PIXEL_FMT_YCRCB_420_SP格式送显,而RK3568需将预览流由yuv420转换为PIXEL_FMT_RGBA_8888送上屏幕才可被正确的显示。具体需修改foundation/ace/ace_engine/frameworks/core/components/camera/standard_system/camera.cpp 文件中如下内容,该文件被编译在libace.z.so中
foundation/multimedia/camera_standard/services/camera_service/src/hstream_repeat.cpp 文件中如下内容,该文件被编译在libcamera_service.z.so中
如上3516平台是使用VO通过VO模块驱动直接送显,所以在ace中配置的subwindows模式为SUBWINDOW_TYPE_VIDEO. 需在foundation/ace/ace_engine/frameworks/core/components/camera/standard_system/camera.cpp文件中做如下修改,该文件被编译在libace.z.so中
增加rk_codec_node
在该node中完成rgb转换,jpeg和h264压缩编解码前文讲过camera hal的pipeline模型的每一个node都是camera数据轮转过程中的一个节点,由于当前camera hal v4l2 adapter只支持一路流进行数据轮转,那么拍照和录像流就必须从单一的预览流中拷贝。现阶段openharmony也没有专门的服务端去做codec和rgb转换jpeg压缩的工作。那么只能在camera hal中开辟一个专有node去做这些事情,也就是rk_codec_node。 Hcs中增加rk_codec_node连接模型: 修改vendor/hihope/rk3568/hdf_config/uhdf/camera/pipeline_core/config.hcs文件
以预览加拍照双路流为列,v4l2_source_node为数据源,流向了fork_node,rork_node将预览数据直接送给RKCodec node, 将拍照数据流拷贝一份也送给RKCodec node进行转换。转换完成的数据将送给sink node后交至buffer的消费端。
device/board/hihope/rk3568/camera/src/pipeline_core/BUILD.gn中添加rk_codec_node.cpp和相关依赖库的编译。其中librga为yuv到rgb格式转换库,libmpp为yuv到H264编解码库,libjpeg为yuv到jpeg照片的压缩库。
openharmony/device/board/hihope/rk3568/camera/src/pipeline_core/node/rk_codec_node.cpp主要接口:
由fork_node出来的数据流将会被deliver到rk_codec_node的DeliverBuffer接口中,该接口会根据不同的EncodeType去做不同的转换处理。经过转换过的buffers再deliver到下一级node中处理。直到deliver到buffer消费者手中。
H264帧时间戳和音频时间戳不同步问题。
问题点:Ace在CreateRecorder时会同时获取音频和视频数据并将他们合成为.mp4文件。但在实际合成过程当中需要检查音视频信息中的时间戳是否一致,如不一致将会Recorder失败。表现出的现象是camera app点击录像按钮后无法正常停止,强行停止后发现mp4文件为空。
解决方法:首先需找到audio模块对于音频时间戳的获取方式。
可以看到,audio_capture_as_impl.cpp 文件中。audio模块用的是CLOCK_MONOTONIC,即系统启动时开始计时的相对时间。而camera 模块使用的是CLOCK_REALTIME,即系统实时时间。
解决方法:修改camera hal中rk_codec_node.cpp中的获取时间类型为CLOCK_MONOTONIC即可解决问题。
time_t改为64位以后匹配4.19 kernel问题。
背景介绍:RK3568在遇到这个问题时的环境是上层运行的32位系统,底层是linux4.19 64位kernel。在32位系统环境下time_t这个typedef是long类型的,也就是32位。但在下面这个提交中将time_t 改成_Int64位。这样就会导致camera v4l2在ioctl时发生错误。
具体错误以及临时修改方案:
1,发生错误时在hilog中搜索camera_host 会发现在V4L2AllocBuffer接口中下发VIDIOC_QUERYBUF的CMD时上报了一个Not a tty的错误。如下:
2,我们知道,一般ioctl系统调用的CMD都是以第三个参数的sizeof为CMD值主要组成传递进内核去寻找内核中相对应的switch case. 如下图,v4l2_buffer为VIDIOC_QUERYBUF宏的值得主要组成部分,那么v4l2_buffer的size发生变化,VIDIOC_QUERYBUF的值也会发生变化。
3,当kernel 打开CONFIG_COMPAT这个宏时,可以实现32位系统到64位kernel的兼容,对于32位系统下发的ioctl会先进入下面截图中的接口里去做cmd值由32到64位的转换。
4,那么在kernel中会定义一个kernel认为的VIDIOC_QUERYBUF的值。
5,前文提到过,上层musl中time_t已经由32位被改为64位,v4l2_buffer结构体中的struct timeval中就用到了time_t。那么应用层的v4l2_buffer的大小就会跟kernel层的不一致,因为kernel的struct timeval 中编译时使用的是kernel自己在time.h中定义的 kernel_time_t。这就导致应用和驱动层对于v4l2_buffer的sizeof计算不一致从而调用到内核态后找不到cmd的错误。
6,临时解决方案是修改videodev2.h中的struct timeval为自己临时定义的结构体, 保证上下层size一致。如下:
根本解决方案:
如需要根本解决这个问题,只有两种方法。第一将系统升级为64位系统,保证用户态和内核态对于time_t变量的size保持一致。第二,升级5.10之后版本的kernel 因为5.10版本的kernel在videodev2.h文件中解决了这个情况。目前我们已在5.10的kernel上验证成功,如下图,可以看到在编译kernel时考虑到了64位time_t的问题。
H264 关键帧获取上报
H264除了需要上报经过编解码的数据外,还需上报关键帧信息。即这一帧是否为关键帧?mp4编码时需要用到这些信息,那么怎么分析那一帧是关键帧那?主要是分析NALU头信息。Nalu type & 0x1f就代表该帧的类型。Nalu头是以0x00000001或0x000001为起始标志的。 该图为nal_unit_type为不同数值时的帧类型。我们主要关心type为5也就是IDR帧信息。
rk_cedec_node.cpp文件里对IDR帧分析进行了代码化:
每经过一个h264转换过的buffer都会被传入SearchIFps接口中寻找IDR帧。其中findStartCode()接口会对buffer中的内容逐个字节扫描,知道寻找出NALU头来
当找到NALU头后就会对&0x1F 找出nal_unit_type,如果type为5标记关键帧信息并通过buffer->SetEsKeyFrame(1);接口上报。
