OpenHarmony(鸿蒙南向开发)——标准系统方案之瑞芯微RK3568移植案例(上) ...

打印 上一主题 下一主题

主题 977|帖子 977|积分 2933

​本文章是基于瑞芯微RK3568芯片的DAYU200开发板,举行标准系统干系功能的移植,主要包罗产物配置添加,内核启动、升级,音频ADM化,Camera,TP,LCD,WIFI,BT,vibrator、sensor、图形表现模块的适配案例总结,以及干系功能的适配。
产物配置和目次规划

产物配置

在产物//productdefine/common/device目次下创建以rk3568名字命名的json文件,并指定CPU的架构。//productdefine/common/device/rk3568.json配置如下:
  1. {
  2.     "device_name": "rk3568",
  3.     "device_company": "rockchip",
  4.     "target_os": "ohos",
  5.     "target_cpu": "arm",
  6.     "kernel_version": "",
  7.     "device_build_path": "device/board/hihope/rk3568",
  8.     "enable_ramdisk": true,   //是否支持ramdisk二级启动
  9.     "build_selinux": true    // 是否支持selinux权限管理
  10. }
复制代码
在//productdefine/common/products目次下创建以产物名命名的rk3568.json文件。该文件用于描述产物所利用的SOC 以及所需的子系统。配置如下
  1. {
  2.   "product_name": "rk3568",
  3.   "product_company" : "hihope",
  4.   "product_device": "rk3568",
  5.   "version": "2.0",
  6.   "type": "standard",
  7.   "parts":{
  8.     "ace:ace_engine_standard":{},
  9.     "ace:napi":{},
  10.     ...
  11.     "xts:phone_tests":{}
  12.   }
  13. }
复制代码
主要的配置内容包罗:
1.product_device:配置所利用的SOC。
2.type:配置系统的级别, 这里直接standard即可。
3.parts:系统必要启用的子系统。子系统可以简单明白为一块独立构建的功能块。
已定义的子系统可以在//build/subsystem_config.json中找到。固然你也可以定制子系统。
这里建议先拷贝Hi3516DV300开发板的配置文件,删撤除hisilicon_products这个子系统。这个子系统为Hi3516DV300 SOC编译内核,不得当rk3568。
目次规划

参考 Board和SoC解耦的设计思路 ,并把芯片适配目次规划为:
  1. device
  2. ├── board                                --- 单板厂商目录
  3. │   └── hihope                           --- 单板厂商名字:
  4. │       └── rk3568                       --- 单板名:rk3568,主要放置开发板相关的驱动业务代码
  5. └── soc                                                                         --- SoC厂商目录
  6.     └── rockchip                       --- SoC厂商名字:rockchip
  7.         └── rk3568                                                 --- SoC Series名:rk3568,主要为芯片原厂提供的一些方案,以及闭源库等
复制代码

  1. vendor
  2. └── hihope                                       
  3.     └── rk3568                                  --- 产品名字:产品、hcs以及demo相关
复制代码
内核启动

二级启动

二级启动简单来说就是将之前直接挂载sytem,从system下的init启动,改成先挂载ramdsik,从ramdsik中的init 启动,做些必要的初始化动作,如挂载system,vendor等分区,然后切到system下的init 。
Rk3568适配主要是将主线编译出来的ramdisk 打包到boot_linux.img中,主要有以下工作:
1.使能二级启动
在productdefine/common/device/rk3568.json 中使能enable_ramdisk。
  1. {
  2.     "device_name": "rk3568",
  3.     "device_company": "hihope",
  4.     "target_os": "ohos",
  5.     "target_cpu": "arm",
  6.     "kernel_version": "",
  7.     "device_build_path": "device/hihope/build",
  8.     "enable_ramdisk": true,
  9.     "build_selinux": true
  10. }
复制代码
2.把主线编译出来的ramdsik.img 打包到boot_linux.img
配置:
由于rk 启动uboot 支持从ramdisk 启动,只必要在打包boot_linux.img 的配置文件中增加ramdisk.img ,因此没有利用主线的its格式,具体配置就是在内核编译脚本make-ohos.sh 中增加:
  1. function make_extlinux_conf()
  2. {
  3.         dtb_path=$1
  4.         uart=$2
  5.         image=$3
  6.        
  7.         echo "label rockchip-kernel-5.10" > ${EXTLINUX_CONF}
  8.         echo "        kernel /extlinux/${image}" >> ${EXTLINUX_CONF}
  9.         echo "        fdt /extlinux/${TOYBRICK_DTB}" >> ${EXTLINUX_CONF}
  10.         if [ "enable_ramdisk" == "${ramdisk_flag}" ]; then
  11.                 echo "        initrd /extlinux/ramdisk.img" >> ${EXTLINUX_CONF}
  12.         fi
  13.         cmdline="append earlycon=uart8250,mmio32,${uart} root=PARTUUID=614e0000-0000-4b53-8000-1d28000054a9 rw rootwait rootfstype=ext4"
  14.         echo "  ${cmdline}" >> ${EXTLINUX_CONF}
  15. }
复制代码
打包

增加了打包boot镜像的脚本make-boot.sh,供编译完ramdisk,打包boot 镜像时调用, 主要内容:
  1. genext2fs -B ${blocks} -b ${block_size} -d boot_linux -i 8192 -U boot_linux.img
复制代码
调用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原生驱动实现与接口,减少开发者工作量。
目次结构

  1. ./device/board/hihope/rk3568/audio_drivers
  2. ├── codec
  3. │   └── rk809_codec
  4. │       ├── include
  5. │       │   ├── rk809_codec_impl.h
  6. │       │   └── rk817_codec.h
  7. │       └── src
  8. │           ├── rk809_codec_adapter.c
  9. │           ├── rk809_codec_linux_driver.c
  10. │           └── rk809_codec_ops.c
  11. ├── dai
  12. │   ├── include
  13. │   │   ├── rk3568_dai_linux.h
  14. │   │   └── rk3568_dai_ops.h
  15. │   └── src
  16. │       ├── rk3568_dai_adapter.c
  17. │       ├── rk3568_dai_linux_driver.c
  18. │       └── rk3568_dai_ops.c
  19. ├── dsp
  20. │   ├── include
  21. │   │   └── rk3568_dsp_ops.h
  22. │   └── src
  23. │       ├── rk3568_dsp_adapter.c
  24. │       └── rk3568_dsp_ops.c
  25. ├── include
  26. │   ├── audio_device_log.h
  27. │   └── rk3568_audio_common.h
  28. └── soc
  29.     ├── include
  30.     │   └── rk3568_dma_ops.h
  31.     └── src
  32.         ├── rk3568_dma_adapter.c
  33.         └── rk3568_dma_ops.c
复制代码
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驱动Accessorydrivers/framework/include/audio/audio_accessory_if.hDMA驱动platformdrivers/framework/include/audio/audio_platform_if.hI2S驱动DAIdrivers/framework/include/audio/audio_dai_if.h.h 搭建驱动代码框架

配置HCS文件

在device_info.hcs文件中Audio下注册驱动节点
  1.         audio :: host {
  2.             hostName = "audio_host";
  3.             priority = 60;
  4.             device_dai0 :: device {
  5.                 device0 :: deviceNode {
  6.                     policy = 1;
  7.                     priority = 50;
  8.                     preload = 0;
  9.                     permission = 0666;
  10.                     moduleName = "DAI_RK3568";
  11.                     serviceName = "dai_service";
  12.                     deviceMatchAttr = "hdf_dai_driver";
  13.                 }
  14.             }
  15.             device_codec :: device {
  16.                 device0 :: deviceNode {
  17.                     policy = 1;
  18.                     priority = 50;
  19.                     preload = 0;
  20.                     permission = 0666;
  21.                     moduleName = "CODEC_RK809";
  22.                     serviceName = "codec_service_0";
  23.                     deviceMatchAttr = "hdf_codec_driver";
  24.                 }
  25.             }
  26.             device_codec_ex :: device {
  27.                 device0 :: deviceNode {
  28.                     policy = 1;
  29.                     priority = 50;
  30.                     preload = 0;
  31.                     permission = 0666;
  32.                     moduleName = "CODEC_RK817";
  33.                     serviceName = "codec_service_1";
  34.                     deviceMatchAttr = "hdf_codec_driver_ex";
  35.                 }
  36.             }
  37.             device_dsp :: device {
  38.                 device0 :: deviceNode {
  39.                     policy = 1;
  40.                     priority = 50;
  41.                     preload = 0;
  42.                     permission = 0666;
  43.                     moduleName = "DSP_RK3568";
  44.                     serviceName = "dsp_service_0";
  45.                     deviceMatchAttr = "hdf_dsp_driver";
  46.                 }
  47.             }
  48.             device_dma :: device {
  49.                 device0 :: deviceNode {
  50.                     policy = 1;
  51.                     priority = 50;
  52.                     preload = 0;
  53.                     permission = 0666;
  54.                     moduleName = "DMA_RK3568";
  55.                     serviceName = "dma_service_0";
  56.                     deviceMatchAttr = "hdf_dma_driver";
  57.                 }
  58.             }
  59.             ......
  60.         }
  61. c
复制代码
根据接入的装备,选择Codec节点还是Accessory节点,配置硬件装备对应的私有属性(包罗寄存器首地址,干系control寄存器地址)涉及Codec_config.hcs和DAI_config.hcs
配置干系介绍见 Audio hcs配置章节以及ADM框架的audio_parse模块代码。
codec/accessory模块

1.将驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName同等
  1. struct HdfDriverEntry g_codecDriverEntry = {
  2.    .moduleVersion = 1,
  3.    .moduleName = "CODEC_HI3516",
  4.    .Bind = CodecDriverBind,
  5.    .Init = CodecDriverInit,
  6.    .Release = CodecDriverRelease,
  7. };
  8. HDF_INIT(g_codecDriverEntry);
复制代码
2.Codec模块必要填充:
g_codecData:codec装备的操纵函数集和私有数据集。
g_codecDaiDeviceOps:codecDai的操纵函数集,包罗启动传输和参数配置等函数接口。
g_codecDaiData:codec的数字音频接口的操纵函数集和私有数据集。
3.完成 bind、init和release函数的实现
4.验证
在bind和init函数加调试日志,编译版本并获取系统系统日志:
  1. [    1.548624] [E/"rk809_codec_adapter"]  [Rk809DriverBind][line:258]: enter
  2. [    1.548635] [E/"rk809_codec_adapter"]  [Rk809DriverBind][line:260]: success
  3. [    1.548655] [E/"rk809_codec_adapter"]  [Rk809DriverInit][line:270]: enter
  4. [    1.549050] [E/"rk809_codec_adapter"]  [GetServiceName][line:226]: enter
  5. [    1.549061] [E/"rk809_codec_adapter"]  [GetServiceName][line:250]: success
  6. [    1.549072] [E/"rk809_codec_adapter"]  [Rk809DriverInit][line:316]: g_chip->accessory.drvAccessoryName = codec_service_1
  7. [    1.549085] [E/audio_core]  [AudioSocRegisterDai][line:86]: Register [accessory_dai] success.
  8. [    1.549096] [E/audio_core]  [AudioRegisterAccessory][line:120]: Register [codec_service_1] success.
  9. [    1.549107] [E/"rk809_codec_adapter"]  [Rk809DriverInit][line:323]: success!
复制代码
DAI模块
1.将I2S驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName同等
  1. struct HdfDriverEntry g_daiDriverEntry = {
  2.     .moduleVersion = 1,
  3.     .moduleName = "DAI_RK3568",
  4.     .Bind = DaiDriverBind,
  5.     .Init = DaiDriverInit,
  6.     .Release = DaiDriverRelease,
  7. };
  8. HDF_INIT(g_daiDriverEntry);
复制代码
2.DAI模块填充:
  1. struct AudioDaiOps g_daiDeviceOps = {
  2.     .Startup = Rk3568DaiStartup,
  3.     .HwParams = Rk3568DaiHwParams,
  4.     .Trigger = Rk3568NormalTrigger,
  5. };
  6. struct DaiData g_daiData = {
  7.     .Read = Rk3568DeviceReadReg,
  8.     .Write = Rk3568DeviceWriteReg,
  9.     .DaiInit = Rk3568DaiDeviceInit,
  10.     .ops = &g_daiDeviceOps,
  11. };
复制代码
3.完成 bind、init和release函数的实现
4.验证
在bind/init函数加调试日志,编译版本并获取系统系统日志
  1. [    1.549193] [I/device_node] launch devnode dai_service
  2. [    1.549204] [E/HDF_LOG_TAG]  [DaiDriverBind][line:38]: entry!
  3. [    1.549216] [E/HDF_LOG_TAG]  [DaiDriverBind][line:55]: success!
  4. [    1.549504] [E/audio_core]  [AudioSocRegisterDai][line:86]: Register [dai_service] success.
  5. [    1.549515] [E/HDF_LOG_TAG]  [DaiDriverInit][line:116]: success.
复制代码
Platform模块
1.将DMA驱动注册到HDF框架中,代码片段如下,启动moduleName与HCS文件的中moduleName同等
  1. struct HdfDriverEntry g_platformDriverEntry = {
  2.     .moduleVersion = 1,
  3.     .moduleName = "DMA_RK3568",
  4.     .Bind = PlatformDriverBind,
  5.     .Init = PlatformDriverInit,
  6.     .Release = PlatformDriverRelease,
  7. };
  8. HDF_INIT(g_platformDriverEntry);
复制代码
2.DMA模块必要填充:
  1. struct AudioDmaOps g_dmaDeviceOps = {
  2.     .DmaBufAlloc = Rk3568DmaBufAlloc,
  3.     .DmaBufFree = Rk3568DmaBufFree,
  4.     .DmaRequestChannel = Rk3568DmaRequestChannel,
  5.     .DmaConfigChannel = Rk3568DmaConfigChannel,
  6.     .DmaPrep = Rk3568DmaPrep,
  7.     .DmaSubmit = Rk3568DmaSubmit,
  8.     .DmaPending = Rk3568DmaPending,
  9.     .DmaPause = Rk3568DmaPause,
  10.     .DmaResume = Rk3568DmaResume,
  11.     .DmaPointer = Rk3568PcmPointer,
  12. };
  13. struct PlatformData g_platformData = {
  14.     .PlatformInit = AudioDmaDeviceInit,
  15.     .ops = &g_dmaDeviceOps,
  16. };
复制代码
3.完成 bind、init和release函数的实现
4.验证
在bind和init函数加调试日志,编译版本并获取系统系统日志
  1. [    1.548469] [E/rk3568_platform_adapter]  [PlatformDriverBind][line:42]: entry!
  2. [    1.548481] [E/rk3568_platform_adapter]  [PlatformDriverBind][line:58]: success!
  3. [    1.548492] [E/rk3568_platform_adapter]  [PlatformDriverInit][line:100]: entry.
  4. [    1.548504] [E/rk3568_platform_adapter]  [PlatformGetServiceName][line:67]: entry!
  5. [    1.548515] [E/rk3568_platform_adapter]  [PlatformGetServiceName][line:91]: success!
  6. [    1.548528] [E/audio_core]  [AudioSocRegisterPlatform][line:63]: Register [dma_service_0] success.
  7. [    1.548536] [E/rk3568_platform_adapter]  [PlatformDriverInit][line:119]: success.
复制代码
驱动适配

code/accessory模块


  • 读取DTS文件,获取到对应装备节点,利用Linux原生的驱动注册函数,获取到对应device。
  1. static int rk817_platform_probe(struct platform_device *pdev) {
  2.     rk817_pdev = pdev;
  3.     dev_info(&pdev->dev, "got rk817-codec platform_device");
  4.     return 0;
  5. }
  6. static struct platform_driver rk817_codec_driver = {
  7.         .driver = {
  8.                    .name = "rk817-codec",                     // codec node in dts file
  9.                    .of_match_table = rk817_codec_dt_ids,
  10.                    },
  11.         .probe = rk817_platform_probe,
  12.         .remove = rk817_platform_remove,
  13. };
复制代码
2.读写寄存器函数封装 根据上述获取到的device, 利用Linux的regmap函数,开发者不必要获取模块的基地址 获取rk817的regmap代码段
  1. g_chip = devm_kzalloc(&rk817_pdev->dev, sizeof(struct Rk809ChipData), GFP_KERNEL);
  2.     if (!g_chip) {
  3.         AUDIO_DEVICE_LOG_ERR("no memory");
  4.         return HDF_ERR_MALLOC_FAIL;
  5.     }
  6.     g_chip->pdev = rk817_pdev;
  7.     struct rk808 *rk808 = dev_get_drvdata(g_chip->pdev->dev.parent);
  8.     if (!rk808) {
  9.         AUDIO_DEVICE_LOG_ERR("%s: rk808 is NULL\n", __func__);
  10.         ret = HDF_FAILURE;
  11.         RK809ChipRelease();
  12.                 return ret;
  13.     }
  14.     g_chip->regmap = devm_regmap_init_i2c(rk808->i2c,
  15.                 &rk817_codec_regmap_config);
  16.     if (IS_ERR(g_chip->regmap)) {
  17.         AUDIO_DEVICE_LOG_ERR("failed to allocate regmap: %ld\n", PTR_ERR(g_chip->regmap));
  18.         RK809ChipRelease();
  19.                 return ret;
  20.     }
复制代码
寄存器读写函数代码段
  1. int32_t Rk809DeviceRegRead(uint32_t reg, uint32_t *val)
  2.   {
  3.       if (regmap_read(g_chip->regmap, reg, val)) {
  4.           AUDIO_DRIVER_LOG_ERR("read register fail: [%04x]", reg);
  5.           return HDF_FAILURE;
  6.       }
  7.       return HDF_SUCCESS;
  8.   }
  9.   int32_t Rk809DeviceRegWrite(uint32_t reg, uint32_t value) {
  10.       if (regmap_write(g_chip->regmap, reg, value)) {
  11.           AUDIO_DRIVER_LOG_ERR("write register fail: [%04x] = %04x", reg, value);
  12.           return HDF_FAILURE;
  13.       }
  14.       return HDF_SUCCESS;
  15.   }
  16.   int32_t Rk809DeviceRegUpdatebits(uint32_t reg, uint32_t mask, uint32_t value) {
  17.       if (regmap_update_bits(g_chip->regmap, reg, mask, value)) {
  18.           AUDIO_DRIVER_LOG_ERR("update register bits fail: [%04x] = %04x", reg, value);
  19.           return HDF_FAILURE;
  20.       }
  21.       return HDF_SUCCESS;
  22.   }
复制代码
3.寄存器初始化函数
因为利用Linux的regmap函数,所以必要自行定义RegDefaultInit函数,读取hcs中initSeqConfig的寄存器以及数值来举行配置
RK809RegDefaultInit代码段
  1. int32_t RK809RegDefaultInit(struct AudioRegCfgGroupNode **regCfgGroup)
  2. {
  3.   int32_t i;
  4.   struct AudioAddrConfig *regAttr = NULL;
  5.   if (regCfgGroup == NULL || regCfgGroup[AUDIO_INIT_GROUP] == NULL ||
  6.      regCfgGroup[AUDIO_INIT_GROUP]->addrCfgItem == NULL || regCfgGroup[AUDIO_INIT_GROUP]->itemNum <= 0) {
  7.      AUDIO_DEVICE_LOG_ERR("input invalid parameter.");
  8.      return HDF_ERR_INVALID_PARAM;
  9.   }
  10.   regAttr = regCfgGroup[AUDIO_INIT_GROUP]->addrCfgItem;
  11.   for (i = 0; i < regCfgGroup[AUDIO_INIT_GROUP]->itemNum; i++) {
  12.      Rk809DeviceRegWrite(regAttr[i].addr, regAttr[i].value);
  13.   }
  14.   return HDF_SUCCESS;
  15. }
复制代码
4.封装控制接口的读写函数
设置控制读写函数为RK809CodecReadReg和RK809CodecWriteReg
  1. struct CodecData g_rk809Data = {
  2.     .Init = Rk809DeviceInit,
  3.     .Read = RK809CodecReadReg,
  4.     .Write = RK809CodecWriteReg,
  5. };
  6. struct AudioDaiOps g_rk809DaiDeviceOps = {
  7.     .Startup = Rk809DaiStartup,
  8.     .HwParams = Rk809DaiHwParams,
  9.         .Trigger = RK809NormalTrigger,
  10. };
  11. struct DaiData g_rk809DaiData = {
  12.     .DaiInit = Rk809DaiDeviceInit,
  13.     .ops = &g_rk809DaiDeviceOps,
  14. };
复制代码
封装控制接口的读写函数
因为原来的读写原型,涉及三个参数(unsigned long virtualAddress,uint32_t reg, uint32_t *val),此中virtualAddress我们并不必要用到,所以封装个接口即可,封装如下
  1. int32_t RK809CodecReadReg(unsigned long virtualAddress,uint32_t reg, uint32_t *val)
  2. {
  3.     if (val == NULL) {
  4.         AUDIO_DRIVER_LOG_ERR("param val is null.");
  5.         return HDF_FAILURE;
  6.     }
  7.     if (Rk809DeviceRegRead(reg, val)) {
  8.         AUDIO_DRIVER_LOG_ERR("read register fail: [%04x]", reg);
  9.         return HDF_FAILURE;
  10.     }
  11.     ADM_LOG_ERR("read reg 0x[%02x] = 0x[%02x]",reg,*val);
  12.     return HDF_SUCCESS;
  13. }
  14. int32_t RK809CodecWriteReg(unsigned long virtualAddress,uint32_t reg, uint32_t value)
  15. {
  16.     if (Rk809DeviceRegWrite(reg, value)) {
  17.         AUDIO_DRIVER_LOG_ERR("write register fail: [%04x] = %04x", reg, value);
  18.         return HDF_FAILURE;
  19.     }   
  20.     ADM_LOG_ERR("write reg 0x[%02x] = 0x[%02x]",reg,value);
  21.     return HDF_SUCCESS;
  22. }
复制代码
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)模块
1.读写寄存器函数 思路与Codec模块的同等,读取Linux DTS文件,利用Linux的regmap函数完成寄存器的读写操纵
  1. int32_t Rk3568DeviceReadReg(unsigned long regBase, uint32_t reg, uint32_t *val)
  2. {
  3.      AUDIO_DEVICE_LOG_ERR("entry");
  4.      (void)regBase;
  5.      struct device_node *dmaOfNode = of_find_node_by_path("/i2s@fe410000");
  6.      if(dmaOfNode == NULL) {
  7.          AUDIO_DEVICE_LOG_ERR("of_node is NULL.");
  8.      }
  9.      struct platform_device *platformdev = of_find_device_by_node(dmaOfNode);
  10.      struct rk3568_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(&platformdev->dev);
  11.      
  12.      (void)regBase;
  13.      if (regmap_read(i2s_tdm->regmap, reg, val)) {
  14.          AUDIO_DEVICE_LOG_ERR("read register fail: [%04x]", reg);
  15.          return HDF_FAILURE;
  16.      }
  17.      return HDF_SUCCESS;
  18. }
  19. int32_t Rk3568DeviceWriteReg(unsigned long regBase, uint32_t reg, uint32_t value)
  20. {   
  21.      AUDIO_DEVICE_LOG_ERR("entry");
  22.      (void)regBase;
  23.      struct device_node *dmaOfNode = of_find_node_by_path("/i2s@fe410000");
  24.      if(dmaOfNode == NULL) {
  25.          AUDIO_DEVICE_LOG_ERR("of_node is NULL.");
  26.      }
  27.      struct platform_device *platformdev = of_find_device_by_node(dmaOfNode);
  28.      struct rk3568_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(&platformdev->dev);
  29.      if (regmap_write(i2s_tdm->regmap, reg, value)) {
  30.          AUDIO_DEVICE_LOG_ERR("write register fail: [%04x] = %04x", reg, value);
  31.          return HDF_FAILURE;
  32.      }
  33.      return HDF_SUCCESS;
  34. }
复制代码
2.其他ops函数


  • Rk3568DaiDeviceInit 原始框架,主要完成DAI_config.hcs参数列表的读取,与HwParams结合,完成参数的设置。
  • Rk3568DaiHwParams 主要完成I2S MCLK/BCLK/LRCLK时钟配置。
1.根据差别采样率计算MCLK
  1.     int32_t RK3568I2sTdmSetSysClk(struct rk3568_i2s_tdm_dev *i2s_tdm, const struct AudioPcmHwParams *param)
  2.     {
  3.         /* Put set mclk rate into rockchip_i2s_tdm_set_mclk() */
  4.         uint32_t sampleRate = param->rate;
  5.         uint32_t mclk_parent_freq = 0;
  6.         switch (sampleRate) {
  7.             case AUDIO_DEVICE_SAMPLE_RATE_8000:
  8.             case AUDIO_DEVICE_SAMPLE_RATE_16000:
  9.             case AUDIO_DEVICE_SAMPLE_RATE_24000:
  10.             case AUDIO_DEVICE_SAMPLE_RATE_32000:
  11.             case AUDIO_DEVICE_SAMPLE_RATE_48000:
  12.             case AUDIO_DEVICE_SAMPLE_RATE_64000:
  13.             case AUDIO_DEVICE_SAMPLE_RATE_96000:
  14.             mclk_parent_freq = i2s_tdm->bclk_fs * AUDIO_DEVICE_SAMPLE_RATE_192000;
  15.             break;
  16.             case AUDIO_DEVICE_SAMPLE_RATE_11025:
  17.             case AUDIO_DEVICE_SAMPLE_RATE_22050:
  18.             case AUDIO_DEVICE_SAMPLE_RATE_44100:
  19.             mclk_parent_freq = i2s_tdm->bclk_fs * AUDIO_DEVICE_SAMPLE_RATE_176400;
  20.             break;
  21.             default:
  22.             AUDIO_DEVICE_LOG_ERR("Invalid LRCK freq: %u Hz\n", sampleRate);
  23.                 return HDF_FAILURE;
  24.         }
  25.         i2s_tdm->mclk_tx_freq = mclk_parent_freq;
  26.         i2s_tdm->mclk_rx_freq = mclk_parent_freq;
  27.         return HDF_SUCCESS;
  28.     }
复制代码
2.根据获取的mclk,计算BCLK/LRclk分频系数


  • Rk3568NormalTrigger 根据输入输出类型,以及cmd(启动/停止/暂停/恢复),完成一系列配置:
1.mclk的启停
2.DMA搬运的启停
3.传输的启停 详细实现见代码,参考Linux原生I2s驱动对应接口函数
  1.     // 启动/恢复流程
  2.     if (streamType == AUDIO_RENDER_STREAM) {
  3.         clk_prepare_enable(i2s_tdm->mclk_tx);
  4.         regmap_update_bits(i2s_tdm->regmap, I2S_DMACR,
  5.                    I2S_DMACR_TDE_ENABLE,
  6.                    I2S_DMACR_TDE_ENABLE);
  7.     } else {
  8.         clk_prepare_enable(i2s_tdm->mclk_rx);
  9.         regmap_update_bits(i2s_tdm->regmap, I2S_DMACR,
  10.                    I2S_DMACR_RDE_ENABLE,
  11.                    I2S_DMACR_RDE_ENABLE);
  12.         if (regmap_read(i2s_tdm->regmap, I2S_DMACR, &val)) {
  13.             AUDIO_DEVICE_LOG_ERR("read register fail: [%04x]", I2S_DMACR);
  14.             return ;
  15.             }
  16.         AUDIO_DEVICE_LOG_ERR("i2s reg: 0x%x = 0x%x ", I2S_DMACR, val);
  17.     }
  18.     if (atomic_inc_return(&i2s_tdm->refcount) == 1) {
  19.         regmap_update_bits(i2s_tdm->regmap, I2S_XFER,
  20.                    I2S_XFER_TXS_START |
  21.                    I2S_XFER_RXS_START,
  22.                    I2S_XFER_TXS_START |
  23.                    I2S_XFER_RXS_START);
  24.         if (regmap_read(i2s_tdm->regmap, I2S_XFER, &val)) {
  25.             AUDIO_DEVICE_LOG_ERR("read register fail: [%04x]", I2S_XFER);
  26.             return ;
  27.             }
  28.         AUDIO_DEVICE_LOG_ERR("i2s reg: 0x%x = 0x%x ", I2S_XFER, val);
  29.     }
复制代码
Platform(DMA)模块
ops函数干系函数
1.Rk3568DmaBufAlloc/Rk3568DmaBufFree
获取DMA装备节点,参考I2s装备获取方式,利用系统函数dma_alloc_wc/dma_free_wc,完成DMA假造内存与物理内存的申请/释放
2.Rk3568DmaRequestChannel
利用Linux DMA原生接口函数获取DMA传输通道,dma_request_slave_channel
  1. dmaRtd->dmaChn[streamType] = dma_request_slave_channel(dmaDevice, dmaChannelNames[streamType]);
复制代码
3.Rk3568DmaConfigChannel
  1.    //设置通道配置参数
  2.    // 放音通道参数配置
  3.    slave_config.direction = DMA_MEM_TO_DEV;
  4.    slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
  5.    slave_config.dst_addr = I2S1_ADDR + I2S_TXDR;
  6.    slave_config.dst_maxburst = 8;
  7.    // 录音通道参数配置
  8.    slave_config.direction = DMA_DEV_TO_MEM;
  9.    slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
  10.    slave_config.src_addr = I2S1_ADDR + I2S_RXDR;
  11.    slave_config.src_maxburst = 8;
  12.    //使用Linux DMA原生接口函数完成DMA通道配置
  13.    ret = dmaengine_slave_config(dmaChan, &slave_config);
  14.    if (ret != 0) {
  15.        AUDIO_DEVICE_LOG_ERR("dmaengine_slave_config failed");
  16.        return HDF_FAILURE;
  17.    }
复制代码
4.Rk3568DmaSubmit/Rk3568DmaPending
利用Linux DMA原生接口函数dmaengine_prep_dma_cyclic,初始化一个具体的周期性的DMA传输描述符dmaengine_submit接口将该描述符放到传输队列上,然后调用dma_async_issue_pending接口,启动传输。
5.Rk3568PcmPointer
第4步完成之后,ADM框架调用Rk3568PcmPointer,循环写cirBuf,计算pointer
  1.   
  2.    dma_chn = dmaRtd->dmaChn[DMA_TX_CHANNEL];
  3.    buf_size = data->renderBufInfo.cirBufSize;
  4.    dmaengine_tx_status(dma_chn, dmaRtd->cookie[DMA_TX_CHANNEL], &dma_state);
  5.    if (dma_state.residue) {
  6.        currentPointer = buf_size - dma_state.residue;
  7.        *pointer = BytesToFrames(data->pcmInfo.frameSize, currentPointer);
  8.    } else {
  9.        *pointer = 0;
  10.    }
复制代码

  • Rk3568DmaPause
利用Linux DMA原生接口函数dmaengine_terminate_async,停止DMA传输
  1.      dmaengine_terminate_async(dmaChan);
复制代码

  • 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”。
  1. |-- README_zh.md
  2. |-- figures
  3. |  -- logic-view-of-modules-related-to-this-repository_zh.png
  4. |-- hal
  5. |  |-- BUILD.gn               #Camera驱动框架构建入口
  6. |  |-- adapter                 #平台适配层,适配平台
  7. |  |-- buffer_manager
  8. |  |-- camera.gni               #定义组件所使用的全局变量
  9. |  |-- device_manager
  10. |  |-- hdi_impl
  11. |  |-- include
  12. |  |-- init                   #demo sample
  13. |  |-- pipeline_core
  14. |  |-- test                   #测试代码
  15. |  |-- utils
  16. |-- hal_c                    #为海思平台提供专用C接口
  17. |  |-- BUILD.gn
  18. |  |-- camera.gni
  19. |  |-- hdi_cif
  20. |  |-- include
  21. |-- interfaces                  #HDI接口
  22.   |-- hdi_ipc
  23. |-- hdi_passthrough
  24.    |-- include
复制代码
Camera hcs文件是每个chipset可配置的。所以放在chipset干系的仓下。以rk3568为例。仓名为: vendor_hihope,源码目次为:“vendor/hihope/rk3568/hdf_config/uhdf/camera”。
  1. ├── hdi_impl
  2. │   └── camera_host_config.hcs
  3. └── pipeline_core
  4.     ├── config.hcs
  5.     ├── ipp_algo_config.hcs
  6.     └── params.hcs
复制代码
Camera chipset 干系代码路径以3568为例仓名为:device_hihope。路径为:device/board/hihope/rk3568/camera/
  1. ├── BUILD.gn
  2. ├── demo
  3. │   └── include
  4. │       └── project_camera_demo.h
  5. ├── device_manager
  6. │   ├── BUILD.gn
  7. │   ├── include
  8. │   │   ├── imx600.h
  9. │   │   ├── project_hardware.h
  10. │   │   └── rkispv5.h
  11. │   └── src
  12. │       ├── imx600.cpp
  13. │       └── rkispv5.cpp
  14. ├── driver_adapter
  15. │   └── test
  16. │       ├── BUILD.gn
  17. │       ├── unittest
  18. │       │   ├── include
  19. │       │   │   └── utest_v4l2_dev.h
  20. │       │   └── src
  21. │       │       └── utest_v4l2_dev.cpp
  22. │       └── v4l2_test
  23. │           └── include
  24. │               └── project_v4l2_main.h
  25. └── pipeline_core
  26.     ├── BUILD.gn
  27.     └── src
  28.         ├── ipp_algo_example
  29.         │   └── ipp_algo_example.c
  30.         └── node
  31.             ├── rk_codec_node.cpp
  32.             └── rk_codec_node.h     
复制代码
Camera 驱动框架配置

RK3568 配置文件路径:
“vendor/hihope/rk3568/hdf_config/uhdf/device_info.hcs”。阐明:其他平台可参考RK3568适配。
  1.         hdi_server :: host {
  2.             hostName = "camera_host";
  3.             priority = 50;
  4.             caps = ["DAC_OVERRIDE", "DAC_READ_SEARCH"];
  5.             camera_device :: device {
  6.                  device0 :: deviceNode {
  7.                      policy = 2;
  8.                      priority = 100;
  9.                      moduleName = "libcamera_hdi_impl.z.so";
  10.                      serviceName = "camera_service";
  11.                  }
  12.              }
  13.             ...
  14.         }
复制代码
参数阐明: 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:存储返回消息内容数据。
  1. static int32_t CameraServiceDispatch(struct HdfDeviceIoClient *client, int cmdId,
  2.     struct HdfSBuf *data, struct HdfSBuf *reply)
  3. {
  4.     HdfCameraService *hdfCameraService = CONTAINER_OF(client->device->service, HdfCameraService, ioservice);
  5.       return CameraHostServiceOnRemoteRequest(hdfCameraService->instance, cmdId, data, reply);
  6. }
复制代码
绑定装备服务:初始化装备服务对象和资源对象。
  1. int HdfCameraHostDriverBind(HdfDeviceObject *deviceObject)
  2. {
  3.     HDF_LOGI("HdfCameraHostDriverBind enter!");
  4.     if (deviceObject == nullptr) {
  5.         HDF_LOGE("HdfCameraHostDriverBind: HdfDeviceObject is NULL !");
  6.         return HDF_FAILURE;
  7. }
复制代码
驱动初始化函数: 探测并初始化驱动程序
  1. int HdfCameraHostDriverInit(struct HdfDeviceObject *deviceObject)
  2. {
  3.       return HDF_SUCCESS;
  4. }
复制代码
驱动资源释放函数 : 如已经绑定的装备服务对象
  1.   void HdfCameraHostDriverRelease(HdfDeviceObject *deviceObject)
  2.   {
  3.           if (deviceObject == nullptr || deviceObject->service == nullptr) {
  4.           HDF_LOGE("%{public}s deviceObject or deviceObject->service  is NULL!", __FUNCTION__);
  5.                 return;
  6.       }
  7.           HdfCameraService *hdfCameraService = CONTAINER_OF(deviceObject->service, HdfCameraService, ioservice);
  8.       if (hdfCameraService == nullptr) {
  9.            HDF_LOGE("%{public}s hdfCameraService is NULL!", __FUNCTION__);
  10.            return;
  11.        }
复制代码
定义驱动描述符:将驱动代码注册给驱动框架。
  1. struct HdfDriverEntry g_cameraHostDriverEntry = {
  2.       .moduleVersion = 1,
  3.       .moduleName = "camera_service",
  4.       .Bind = HdfCameraHostDriverBind,
  5.       .Init = HdfCameraHostDriverInit,
  6.       .Release = HdfCameraHostDriverRelease,
  7.   };   
复制代码
Camera配置信息介绍

Camera模块内部,全部配置文件利用系统支持的HCS类型的配置文件,HCS类型的配置文件,在编译时,会转成HCB文件,最终烧录到开发板里的配置文件即为HCB格式,代码中通过HCS剖析接口剖析HCB文件,获取配置文件中的信息。
  1. hc_gen("build_camera_host_config") {
  2.     sources = [ rebase_path(
  3.                   "$camera_product_name_path/hdf_config/uhdf/camera/hdi_impl/camera_host_config.hcs") ]
  4.   }
  5.         
  6.   ohos_prebuilt_etc("camera_host_config.hcb") {
  7.           deps = [ ":build_camera_host_config" ]
  8.     hcs_outputs = get_target_outputs(":build_camera_host_config")
  9.           source = hcs_outputs[0]
  10.     relative_install_dir = "hdfconfig"
  11.           install_images = [ chipset_base_dir ]
  12.     subsystem_name = "hdf"
  13.           part_name = "camera_device_driver"
  14.   }
复制代码
Camera适配介绍

新产物平台适配简介

drivers/peripheral/camera/hal/camera.gni 文件中可根据编译时传入的product_company product_name和device_name调用差别chipset的product.gni
  1.   if (defined(ohos_lite)) {
  2.           import("//build/lite/config/component/lite_component.gni")
  3.     import(
  4.               "//device/soc/hisilicon/common/hal/media/camera/hi3516dv300/linux_standard/camera/product.gni")
  5.   } else {
  6.           import("//build/ohos.gni")
  7.     if ("${product_name}" == "ohos-arm64") {
  8.             import(
  9.           "//drivers/peripheral/camera/hal/adapter/chipset/rpi/rpi3/device/camera/product.gni")
  10.           } else if ("${product_name}" == "Hi3516DV300") {
  11.       import(
  12.                 "//device/soc/hisilicon/common/hal/media/camera/hi3516dv300/linux_standard/camera/product.gni")
  13.     } else if ("${product_name}" == "watchos") {
  14.             import(
  15.           "//device/soc/hisilicon/common/hal/media/camera/hi3516dv300/linux_standard/camera/product.gni")
  16.           } else {
  17.       import(
  18.                 "//device/board/${product_company}/${device_name}/camera/product.gni")
  19.     }
  20.         }
复制代码
在如下路径的product.gni指定了编译差别chipset干系的代码的路径:
  1. device/${product_company}/${device_name}/camera/
复制代码
如下是rk3568的product.gni:
  1.   camera_device_name_path = "//device/board/${product_company}/${device_name}"
  2.         is_support_v4l2 = true
  3.   if (is_support_v4l2) {
  4.           is_support_mpi = false
  5.     defines += [ "SUPPORT_V4L2" ]
  6.           chipset_build_deps = "$camera_device_name_path/camera/:chipset_build"
  7.     camera_device_manager_deps =
  8.               "$camera_device_name_path/camera/src/device_manager:camera_device_manager"
  9.     camera_pipeline_core_deps =
  10.               "$camera_device_name_path/camera/src/pipeline_core:camera_pipeline_core"
  11.   }
复制代码
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配置文件适配介绍

  1.   ├── hdi_impl
  2.   │   └── camera_host_config.hcs
  3.   └── pipeline_core
  4.       ├── config.hcs
  5.       ├── ipp_algo_config.hcs
  6.       └── params.hcs
复制代码
以RK3568开发板为例,其hcs文件应该放在对应的路径中。
  1. vendor/${product_company}/${product_name}/ hdf_config/uhdf/camera/  
复制代码

  1. template ability {
  2.   logicCameraId = "lcam001";
  3.   physicsCameraIds = [
  4.   "CAMERA_FIRST",
  5.   "CAMERA_SECOND"
  6.   ];
  7. metadata {
  8.    aeAvailableAntiBandingModes = [
  9.        "OHOS_CONTROL_AE_ANTIBANDING_MODE_OFF",
  10.        "OHOS_CONTROL_AE_ANTIBANDING_MODE_50HZ",
  11.        "OHOS_CONTROL_AE_ANTIBANDING_MODE_60HZ",
  12.        "OHOS_CONTROL_AE_ANTIBANDING_MODE_AUTO"
  13.         ];
复制代码
hdi_impl下的“camera_host_config.hcs”为物理/逻辑Camera配置、本事配置,此处的物理/逻辑Camera配置,必要在hal内部利用,逻辑Camera及本事配置必要上报给上层,请按照所适配的芯片产物添加其本事配置。此中所用的本事值为键值对,定义在//drivers/peripheral/camera/hal/hdi_impl/include/camera_host/metadata_enum_map.h中。
  1.       normal_preview :: pipeline_spec {
  2.       name = "normal_preview";
  3.             v4l2_source :: node_spec {
  4.           name = "v4l2_source#0";
  5.                 status = "new";
  6.           out_port_0 :: port_spec {
  7.                     name = "out0";
  8.               peer_port_name = "in0";
  9.                     peer_port_node_name = "sink#0";
  10.               direction = 1;
  11.                     width = 0;
  12.               height = 0;
  13.                     format = 0;
  14.           }
  15.             }
  16.       sink :: node_spec {
  17.                 name = "sink#0";
  18.           status = "new";
  19.                 stream_type = "preview";
  20.           in_port_0 :: port_spec {
  21.                     name = "in0";
  22.               peer_port_name = "out0";
  23.                     peer_port_node_name = "v4l2_source#0";
  24.               direction = 0;
  25.                 }
  26.       }
  27.     }
复制代码
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。
  1. root {
  2.   module = "";
  3.         template stream_info {
  4.       id = 0;
  5.             name = "";
  6.   }
  7.         template scene_info {
  8.       id = 0;
  9.             name = "";
  10.   }
  11.         preview :: stream_info {
  12.       id = 0;
  13.             name = "preview";
  14.   }
  15.         video :: stream_info {
  16.       id = 1;
  17.             name = "video";
  18.   }
复制代码
param.hcs为场景、流类型名及其id定义,pipeline内部是以流id区分流类型的,所以此处必要添加定义。
Chipset 和Platform适配介绍

platform为平台性公共代码,如linux标准v4l2适配接口定义,为v4l2框架适配的通用node.以及为v4l2框架适配的通用device_manager等。目次结构如下:
  1.   drivers/peripheral/camera/hal/adapter/platform
  2.   ├── mpp
  3.   │   └── src
  4.   │       ├── device_manager
  5.   │       └── pipeline_core
  6.   └── v4l2
  7.       └── src
  8.           ├── device_manager
  9.           ├── driver_adapter
  10.           └── pipeline_core
复制代码
“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的接口声明头文件。
  1. namespace OHOS::Camera {
  2.   class V4L2SourceNode : public SourceNode {
  3.         public:
  4.       V4L2SourceNode(const std::string& name, const std::string& type);
  5.             ~V4L2SourceNode() override;
  6.       RetCode Init(const int32_t streamId) override;
  7.             RetCode Start(const int32_t streamId) override;
  8.       RetCode Flush(const int32_t streamId) override;
  9.             RetCode Stop(const int32_t streamId) override;
  10.       RetCode GetDeviceController();
  11.             void SetBufferCallback() override;
  12.       RetCode ProvideBuffers(std::shared_ptr<FrameSpec> frameSpec) override;
  13.         
  14.   private:
  15.             std::mutex                              requestLock_;
  16.       std::map<int32_t, std::list<int32_t>>   captureRequests_ = {};
  17.             std::shared_ptr<SensorController>       sensorController_ = nullptr;
  18.       std::shared_ptr<IDeviceManager>     deviceManager_ = nullptr;
  19.         };
  20.   } // namespace OHOS::Camera
复制代码
Init接口为模块初始化接口。
Start为使能接口,好比start stream功能等。
Stop为停止接口。
GetDeviceController为获取deviceManager对应的controller接口。
chipset为具体某芯片平台干系代码,例如,如和“rk3568”开发板 为例。device_manager目次下可存放该开发板适配过的sensor的干系配置文件。pipeline_core路径下可以存放由chipset开发者为满意特点需求增加的pipeline node等。
  1. device/board/hihope/rk3568/camera
  2.   ├── BUILD.gn
  3.   ├── camera_demo
  4.   │   └── project_camera_demo.h
  5.   ├── include
  6.   │   └── device_manager
  7.   ├── product.gni
  8.   └── src
  9.       ├── device_manager
  10.       ├── driver_adapter
  11.       └── pipeline_core
复制代码
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中必要实现关键接口介绍。
  1.       class SensorController : public IController {
  2.       public:
  3.           SensorController();
  4.           explicit SensorController(std::string hardwareName);
  5.           virtual ~SensorController();
  6.           RetCode Init();
  7.           RetCode PowerUp();
  8.           RetCode PowerDown();
  9.           RetCode Configure(std::shared_ptr<CameraStandard::CameraMetadata> meta);
  10.           RetCode Start(int buffCont, DeviceFormat& format);
  11.           RetCode Stop();
  12.           RetCode SendFrameBuffer(std::shared_ptr<FrameSpec> buffer);
  13.           void SetNodeCallBack(const NodeBufferCb cb);
  14.           void SetMetaDataCallBack(const MetaDataCb cb);
  15.           void BufferCallback(std::shared_ptr<FrameSpec> buffer);
  16.           void SetAbilityMetaDataTag(std::vector<int32_t> abilityMetaDataTag);
  17.     }
复制代码
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/                                             p                            r                            o                            d                            u                            c                                       t                               c                                      o                            m                            p                            a                            n                            y                                  /                              {product_company}/                  productc​ompany/{product_name}/hdf_config/uhdf/camera/pipeline_core/ipp_algo_config.hcs 此中:
  1.   root {
  2.      module="sample";
  3.      ipp_algo_config {
  4.      algo1 {
  5.           name = "example";
  6.           description = "example algorithm";
  7.           path = "libcamera_ipp_algo_example.z.so";
  8.           mode = "IPP_ALGO_MODE_NORMAL";
  9.      }
  10.      }
  11.   }
复制代码
name:算法插件名称 description:描述算法插件的功能 path:算法插件地点路径 mode:算法插件所运行的模式
算法插件可运行的模式由 drivers/peripheral/camera/hal/pipeline_core/ipp/include/ipp_algo.h中的IppAlgoMode提供,可以根据必要举行扩展。
  1.   enum IppAlgoMode {
  2.       IPP_ALGO_MODE_BEGIN,
  3.       IPP_ALGO_MODE_NORMAL = IPP_ALGO_MODE_BEGIN,
  4.       IPP_ALGO_MODE_BEAUTY,
  5.       IPP_ALGO_MODE_HDR,
  6.       IPP_ALGO_MODE_END
  7.   };
复制代码
算法插件由gn文件 device/                                             p                            r                            o                            d                            u                            c                                       t                               c                                      o                            m                            p                            a                            n                            y                                  /                              {product_company}/                  productc​ompany/{device_name}/camera/BUILD.gn举行编译,算法插件需实现如下接口(接口由ipp_algo.h指定)供ippnode调用:
  1.   typedef struct IppAlgoFunc {
  2.       int (*Init)(IppAlgoMeta* meta);
  3.       int (*Start)();
  4.       int (*Flush)();
  5.       int (*Process)(IppAlgoBuffer* inBuffer[], int inBufferCount, IppAlgoBuffer* outBuffer, IppAlgoMeta* meta);
  6.       int (*Stop)();
  7.   } IppAlgoFunc;
复制代码
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调用
  1. typedef struct IppAlgoBuffer {
  2.       void* addr;
  3.       unsigned int width;
  4.       unsigned int height;
  5.       unsigned int stride;
  6.       unsigned int size;
  7.       int id;
  8.   } IppAlgoBuffer;
复制代码
此中上边代码中的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交互的代码
  1.   drivers/peripheral/camera/hal/adapter/platform/v4l2/src/
  2.   ├── device_manager
  3.   │   ├── enumerator_manager.cpp
  4.   │   ├── flash_controller.cpp
  5.   │   ├── flash_manager.cpp
  6.   │   ├── idevice_manager.cpp
  7.   │   ├── include
  8.   │   ├── isp_controller.cpp
  9.   │   ├── isp_manager.cpp
  10.   │   ├── sensor_controller.cpp
  11.   │   ├── sensor_manager.cpp
  12.   │   └── v4l2_device_manager.cpp
  13.   ├── driver_adapter
  14.   │   ├── BUILD.gn
  15.   │   ├── include
  16.   │   ├── main_test
  17.   │   └── src
  18.   └── pipeline_core
  19.       └── nodes
复制代码
区分V4L2 chipset干系代码并将其放置在“device/                                              p                            r                            o                            d                            u                            c                                       t                               c                                      o                            m                            p                            a                            n                            y                                  /                              {product_company}/                  productc​ompany/{device_name} /camera”目次下。
  1.   ├── BUILD.gn
  2.   ├── camera_demo
  3.   │   └── project_camera_demo.h
  4.   ├── include
  5.   │   └── device_manager
  6.   ├── product.gni
  7.   └── src
  8.       ├── device_manager
  9.       ├── driver_adapter
  10.       └── pipeline_core
复制代码
此中“driver_adapter”目次中包罗了关于RK3568 driver adapter的测试用例头文件。Camera_demo目次存放了camera hal 中demo测试用例的chipset干系的头文件。device_manager存放了RK3568适配的camera sensor 读取装备本事的代码 此中,project_hardware.h 比较关键,存放了device_manager支持当前chipset的装备列表。如下:
  1. namespace OHOS::Camera {
  2.     std::vector<HardwareConfiguration> hardware = {
  3.         {CAMERA_FIRST, DM_M_SENSOR, DM_C_SENSOR, (std::string) "rkisp_v5"},
  4.         {CAMERA_FIRST, DM_M_ISP, DM_C_ISP, (std::string) "isp"},
  5.         {CAMERA_FIRST, DM_M_FLASH, DM_C_FLASH, (std::string) "flash"},
  6.         {CAMERA_SECOND, DM_M_SENSOR, DM_C_SENSOR, (std::string) "Imx600"},
  7.         {CAMERA_SECOND, DM_M_ISP, DM_C_ISP, (std::string) "isp"},
  8.         {CAMERA_SECOND, DM_M_FLASH, DM_C_FLASH, (std::string) "flash"}
  9.    };
  10.   } // namespace OHOS::Camera
复制代码
修改编译选项来到达根据差别的编译chipset来区分v4l2和其他框架代码编译。增加device/                                             p                            r                            o                            d                            u                            c                                       t                               c                                      o                            m                            p                            a                            n                            y                                  /                              {product_company}/                  productc​ompany/{device_name}/camera/product.gni
  1.   camera_product_name_path = "//vendor/${product_company}/${product_name}"
  2.   camera_device_name_path = "//device/board/${product_company}/${device_name}"
  3.   is_support_v4l2 = true
  4.   if (is_support_v4l2) {
  5.       is_support_mpi = false
  6.       defines += [ "SUPPORT_V4L2" ]
  7.       chipset_build_deps = "$camera_device_name_path/camera/:chipset_build"
  8.       camera_device_manager_deps =
  9.           "$camera_device_name_path/camera/src/device_manager:camera_device_manager"
  10.       camera_pipeline_core_deps =
  11.           "$camera_device_name_path/camera/src/pipeline_core:camera_pipeline_core"
  12.   }
复制代码
当“product.gni”被// drivers/peripheral/camera/hal/camera.gni加载,就阐明要编译v4l2干系代码。在//drivers/peripheral/camera/hal/camera.gni中根据编译时传入的product_name和device_name名来加载相应的gni文件。
  1.   import("//build/ohos.gni")
  2.   if ("${product_name}" == "ohos-arm64") {
  3.     import(
  4.         "//drivers/peripheral/camera/hal/adapter/chipset/rpi/rpi3/device/camera/product.gni")
  5.   } else if ("${product_name}" == "Hi3516DV300") {
  6.     import(
  7.         "//device/soc/hisilicon/common/hal/media/camera/hi3516dv300/linux_standard/camera/product.gni")
复制代码
“drivers/peripheral/camera/hal/BUILD.gn”中会根据 chipset_build_deps camera_device_manager_deps 和 camera_pipeline_core_deps来编译差别的chipset
  1.      print("product_name : , ${product_name}")
  2.       group("camera_hal") {
  3.         if (is_standard_system) {
  4.           deps = [
  5.             "$camera_path/../interfaces/hdi_ipc/client:libcamera_client",
  6.             "buffer_manager:camera_buffer_manager",
  7.             "device_manager:camera_device_manager",
  8.             "hdi_impl:camera_hdi_impl",
  9.             "init:ohos_camera_demo",
  10.             "pipeline_core:camera_pipeline_core",
  11.             "utils:camera_utils",
  12.           ]
  13.           deps += [ "${chipset_build_deps}" ]
  14.         }
复制代码
Camera hal层向下屏蔽了平台及芯片差别,对外(Camera service或者测试程序)提供统一接口,其接口定义在“drivers/peripheral/camera/interfaces/include”目次下:
  1.         ├── icamera_device_callback.h
  2.         ├── icamera_device.h
  3.         ├── icamera_host_callback.h
  4.         ├── icamera_host.h
  5.         ├── ioffline_stream_operator.h
  6.         ├── istream_operator_callback.h
  7.         ├── istream_operator.h
复制代码
测试时,只必要针对所提供的对外接口举行测试,即可完备测试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中
  1.   #ifdef PRODUCT_RK
  2.       previewSurface_->SetUserData(SURFACE_FORMAT, std::to_string(PIXEL_FMT_RGBA_8888));
  3.       previewSurface_->SetUserData(CameraStandard::CameraManager::surfaceFormat,
  4.                                    std::to_string(OHOS_CAMERA_FORMAT_RGBA_8888));
  5.   #else
  6.       previewSurface_->SetUserData(SURFACE_FORMAT, std::to_string(PIXEL_FMT_YCRCB_420_SP));
  7.       previewSurface_->SetUserData(CameraStandard::CameraManager::surfaceFormat,
  8.                                    std::to_string(OHOS_CAMERA_FORMAT_YCRCB_420_SP));
  9.   #endif
复制代码
foundation/multimedia/camera_standard/services/camera_service/src/hstream_repeat.cpp 文件中如下内容,该文件被编译在libcamera_service.z.so中
  1. void HStreamRepeat::SetStreamInfo(std::shared_ptr<Camera::StreamInfo> streamInfo)
  2.     {
  3.         int32_t pixelFormat;
  4.         auto it = g_cameraToPixelFormat.find(format_);
  5.         if (it != g_cameraToPixelFormat.end()) {
  6.             pixelFormat = it->second;
  7.         } else {
  8.     #ifdef RK_CAMERA
  9.             pixelFormat = PIXEL_FMT_RGBA_8888;
  10.     #else
  11.             pixelFormat = PIXEL_FMT_YCRCB_420_SP;
  12.     #endif
复制代码
如上3516平台是利用VO通过VO模块驱动直接送显,所以在ace中配置的subwindows模式为SUBWINDOW_TYPE_VIDEO. 需在foundation/ace/ace_engine/frameworks/core/components/camera/standard_system/camera.cpp文件中做如下修改,该文件被编译在libace.z.so中
  1.   #ifdef PRODUCT_RK
  2.       option->SetWindowType(SUBWINDOW_TYPE_NORMAL);
  3.   #else
  4.       option->SetWindowType(SUBWINDOW_TYPE_VIDEO);
  5.   #endif
复制代码
增加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文件
  1.           normal_preview_snapshot :: pipeline_spec {
  2.                 name = "normal_preview_snapshot";
  3.                 v4l2_source :: node_spec {
  4.                     name = "v4l2_source#0";
  5.                     status = "new";
  6.                     out_port_0 :: port_spec {
  7.                         name = "out0";
  8.                         peer_port_name = "in0";
  9.                         peer_port_node_name = "fork#0";
  10.                         direction = 1;
  11.                     }
  12.                 }
  13.                 fork :: node_spec {
  14.                     name = "fork#0";
  15.                     status = "new";
  16.                     in_port_0 :: port_spec {
  17.                         name = "in0";
  18.                         peer_port_name = "out0";
  19.                         peer_port_node_name = "v4l2_source#0";
  20.                         direction = 0;
  21.                     }
  22.                     out_port_0 :: port_spec {
  23.                         name = "out0";
  24.                         peer_port_name = "in0";
  25.                         peer_port_node_name = "RKCodec#0";
  26.                         direction = 1;
  27.                     }
  28.                     out_port_1 :: port_spec {
  29.                         name = "out1";
  30.                         peer_port_name = "in0";
  31.                         peer_port_node_name = "RKCodec#1";
  32.                         direction = 1;
  33.                     }
  34.                 }
  35.                 RKCodec_1 :: node_spec {
  36.                     name = "RKCodec#0";
  37.                     status = "new";
  38.                     in_port_0 :: port_spec {
  39.                         name = "in0";
  40.                         peer_port_name = "out0";
  41.                         peer_port_node_name = "fork#0";
  42.                         direction = 0;
  43.                     }
  44.                     out_port_0 :: port_spec {
  45.                         name = "out0";
  46.                         peer_port_name = "in0";
  47.                         peer_port_node_name = "sink#0";
  48.                         direction = 1;
  49.                     }
  50.                 }
  51.                 RKCodec_2 :: node_spec {
  52.                     name = "RKCodec#1";
复制代码
以预览加拍照双路流为列,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照片的压缩库。
  1.     ohos_shared_library("camera_pipeline_core") {
  2.         sources = [
  3.           "$camera_device_name_path/camera/src/pipeline_core/node/rk_codec_node.cpp",
  4.           "$camera_path/adapter/platform/v4l2/src/pipeline_core/nodes/uvc_node/uvc_node.cpp",
  5.                   "$camera_path/adapter/platform/v4l2/src/pipeline_core/nodes/v4l2_source_node/v4l2_source_node.cpp",
  6.            deps = [
  7.             "$camera_path/buffer_manager:camera_buffer_manager",
  8.             "$camera_path/device_manager:camera_device_manager",
  9.             "//device/soc/rockchip/hardware/mpp:libmpp",
  10.             "//device/soc/rockchip/hardware/rga:librga",
  11.             "//foundation/multimedia/camera_standard/frameworks/native/metadata:metadata",
  12.             "//third_party/libjpeg:libjpeg_static",
复制代码
openharmony/device/board/hihope/rk3568/camera/src/pipeline_core/node/rk_codec_node.cpp主要接口:
  1.    void RKCodecNode::DeliverBuffer(std::shared_ptr<IBuffer>& buffer)
  2.     {
  3.         if (buffer == nullptr) {
  4.             CAMERA_LOGE("RKCodecNode::DeliverBuffer frameSpec is null");
  5.             return;
  6.         }
  7.    
  8.         int32_t id = buffer->GetStreamId();
  9.         CAMERA_LOGE("RKCodecNode::DeliverBuffer StreamId %{public}d", id);
  10.         if (buffer->GetEncodeType() == ENCODE_TYPE_JPEG) {
  11.             Yuv420ToJpeg(buffer);
  12.         } else if (buffer->GetEncodeType() == ENCODE_TYPE_H264) {
  13.             Yuv420ToH264(buffer);
  14.         } else {
  15.             Yuv420ToRGBA8888(buffer);
  16.         }
复制代码
由fork_node出来的数据流将会被deliver到rk_codec_node的DeliverBuffer接口中,该接口会根据差别的EncodeType去做差别的转换处理。经过转换过的buffers再deliver到下一级node中处理。直到deliver到buffer消费者手中。
H264帧时间戳和音频时间戳差别步题目。

题目点:Ace在CreateRecorder时会同时获取音频和视频数据并将他们合成为.mp4文件。但在实际合成过程当中必要查抄音视频信息中的时间戳是否同等,如不同等将会Recorder失败。表现出的现象是camera app点击录像按钮后无法正常停止,强行停止后发现mp4文件为空。
办理方法:首先需找到audio模块对于音频时间戳的获取方式。
  1.    int32_t AudioCaptureAsImpl::GetSegmentInfo(uint64_t &start)
  2.     {
  3.         CHECK_AND_RETURN_RET(audioCapturer_ != nullptr, MSERR_INVALID_OPERATION);
  4.         AudioStandard::Timestamp timeStamp;
  5.         auto timestampBase = AudioStandard::Timestamp::Timestampbase::MONOTONIC;
  6.         CHECK_AND_RETURN_RET(audioCapturer_->GetAudioTime(timeStamp, timestampBase), MSERR_UNKNOWN);
  7.         CHECK_AND_RETURN_RET(timeStamp.time.tv_nsec >= 0 && timeStamp.time.tv_sec >= 0, MSERR_UNKNOWN);
  8.         if (((UINT64_MAX - timeStamp.time.tv_nsec) / SEC_TO_NANOSECOND) <= static_cast<uint64_t>(timeStamp.time.tv_sec)) {
  9.             MEDIA_LOGW("audio frame pts too long, this shouldn't happen");
  10.         }
  11.         start = timeStamp.time.tv_nsec + timeStamp.time.tv_sec * SEC_TO_NANOSECOND;
  12.         MEDIA_LOGI("timestamp from audioCapturer: %{public}" PRIu64 "", start);
  13.         return MSERR_OK;
  14.     }
复制代码
可以看到,audio_capture_as_impl.cpp 文件中。audio模块用的是CLOCK_MONOTONIC,即系统启动时开始计时的相对时间。而camera 模块利用的是CLOCK_REALTIME,即系统及时时间。
  1.             mppStatus_ = 1;
  2.             buf_size = ((MpiEncTestData *)halCtx_)->frame_size;
  3.    
  4.             ret = hal_mpp_encode(halCtx_, dma_fd, (unsigned char *)buffer->GetVirAddress(), &buf_size);
  5.             SearchIFps((unsigned char *)buffer->GetVirAddress(), buf_size, buffer);
  6.    
  7.             buffer->SetEsFrameSize(buf_size);
  8.             clock_gettime(CLOCK_MONOTONIC, &ts);
  9.             timestamp = ts.tv_nsec + ts.tv_sec * TIME_CONVERSION_NS_S;
  10.             buffer->SetEsTimestamp(timestamp);
  11.             CAMERA_LOGI("RKCodecNode::Yuv420ToH264 video capture on\n");
复制代码
办理方法:修改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.   TYPEDEF _Int64 time_t;
  2.   TYPEDEF _Int64 suseconds_t;   
复制代码
具体错误以及临时修改方案:
1,发生错误时在hilog中搜刮camera_host 会发现在V4L2AllocBuffer接口中下发VIDIOC_QUERYBUF的CMD时上报了一个Not a tty的错误。如下:
  1. V4L2AllocBuffer error:ioctl VIDIOC_QUERYBUF failed: Not a tty
复制代码

  1. RetCode HosV4L2Buffers::V4L2AllocBuffer(int fd, const std::shared_ptr<FrameSpec>& frameSpec)
  2. {
  3.     struct v4l2_buffer buf = {};
  4.     struct v4l2_plane planes[1] = {};
  5.     CAMERA_LOGD("V4L2AllocBuffer\n");
  6.     if (frameSpec == nullptr) {
  7.         CAMERA_LOGE("V4L2AllocBuffer frameSpec is NULL\n");
  8.         return RC_ERROR;
  9.     }
  10.     switch (memoryType_) {
  11.         case V4L2_MEMORY_MMAP:
  12.             // to do something
  13.             break;
  14.         case V4L2_MEMORY_USERPTR:
  15.             buf.type = bufferType_;
  16.             buf.memory = memoryType_;
  17.             buf.index = (uint32_t)frameSpec->buffer_->GetIndex();
  18.             if (bufferType_ == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
  19.                 buf.m.planes = planes;
  20.                 buf.length = 1;
  21.             }
  22.             CAMERA_LOGD("V4L2_MEMORY_USERPTR Print the cnt: %{public}d\n", buf.index);
  23.             if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
  24.                 CAMERA_LOGE("error: ioctl VIDIOC_QUERYBUF failed: %{public}s\n", strerror(errno));
  25.                 return RC_ERROR;
复制代码
2,我们知道,一般ioctl系统调用的CMD都是以第三个参数的sizeof为CMD值主要构成通报进内核去寻找内核中相对应的switch case. 如下图,v4l2_buffer为VIDIOC_QUERYBUF宏的值得主要构成部分,那么v4l2_buffer的size发生变化,VIDIOC_QUERYBUF的值也会发生变化。
  1.   #define VIDIOC_S_FMT        _IOWR('V',  5, struct v4l2_format)
  2.   #define VIDIOC_REQBUFS      _IOWR('V',  8, struct v4l2_requestbuffers)
  3.   #define VIDIOC_QUERYBUF     _IOWR('V',  9, struct v4l2_buffer)
  4.   #define VIDIOC_G_FBUF        _IOR('V', 10, struct v4l2_framebuffer)
复制代码
3,当kernel 打开CONFIG_COMPAT这个宏时,可以实现32位系统到64位kernel的兼容,对于32位系统下发的ioctl会先进入下面截图中的接口里去做cmd值由32到64位的转换。
  1.   long v4l2_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg)
  2.   {
  3.       struct video_device *vdev = video_devdata(file);
  4.       long ret = -ENOIOCTLCMD;
  5.   
  6.       if (!file->f_op->unlocked_ioctl)
  7.           return ret;
  8.   
  9.       if (_IOC_TYPE(cmd) == 'V' && _IOC_NR(cmd) < BASE_VIDIOC_PRIVATE)
  10.           ret = do_video_ioctl(file, cmd, arg);
  11.       else if (vdev->fops->compat_ioctl32)
  12.           ret = vdev->fops->compat_ioctl32(file, cmd, arg);
复制代码
4.那么在kernel中会定义一个kernel以为的VIDIOC_QUERYBUF的值。
  1.     #define VIDIOC_S_FMT32      _IOWR('V',  5, struct v4l2_format32)
  2.     #define VIDIOC_QUERYBUF32   _IOWR('V',  9, struct v4l2_buffer32)
  3.     #define VIDIOC_QUERYBUF32_TIME32 _IOWR('V',  9, struct v4l2_buffer32_time32)
复制代码
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的错误。
  1.    struct v4l2_buffer {
  2.             __u32           index;
  3.              __u32           type;
  4.             __u32           bytesused;
  5.             __u32           flags;
  6.             __u32           field;
  7.             struct timeval      timestamp;
  8.             struct v4l2_timecode    timecode;
  9.             __u32           sequence;
复制代码
6,临时办理方案是修改videodev2.h中的struct timeval为本身临时定义的结构体, 保证上下层size同等。如下:
  1.             struct timeval1 {
  2.                 long tv_sec;
  3.                 long tv_usec;
  4.             }
  5.             struct v4l2_buffer {
  6.                 __u32           index;
  7.                 __u32           type;
  8.                 __u32           bytesused;
  9.                 __u32           flags;
  10.                 __u32           field;
  11.                 struct timeval1      timestamp;
  12.                 struct v4l2_timecode    timecode;
复制代码
根本办理方案:
如必要根本办理这个题目,只有两种方法。第一将系统升级为64位系统,保证用户态和内核态对于time_t变量的size保持同等。第二,升级5.10之后版本的kernel 因为5.10版本的kernel在videodev2.h文件中办理了这个环境。目前我们已在5.10的kernel上验证乐成,如下图,可以看到在编译kernel时考虑到了64位time_t的题目。
  1. struct v4l2_buffer {
  2.             __u32           index;
  3.             __u32           type;
  4.             __u32           bytesused;
  5.             __u32           flags;
  6.             __u32           field;
  7.         #ifdef __KERNEL__
  8.             struct __kernel_v4l2_timeval timestamp;
  9.         #else
  10.             struct timeval      timestamp;
  11.         #endif
  12.             struct v4l2_timecode    timecode;
  13. }
  14. struct __kernel_v4l2_timeval {
  15.       long long   ._sec;
  16.   #if defined(__sparc__) && defined(__arch64__)
  17.       int     tv_usec;
  18.       int     __pad;
  19.   #else
  20.       long long   tv_usec;
  21.   #endif
  22.   };
复制代码
H264 关键帧获取上报

H264除了必要上报经过编解码的数据外,还需上报关键帧信息。即这一帧是否为关键帧?mp4编码时必要用到这些信息,那么怎么分析那一帧是关键帧那?主要是分析NALU头信息。Nalu type & 0x1f就代表该帧的类型。Nalu头是以0x00000001或0x000001为起始标志的。 该图为nal_unit_type为差别数值时的帧类型。我们主要关心type为5也就是IDR帧信息。

rk_cedec_node.cpp文件里对IDR帧分析举行了代码化:
  1.     static constexpr uint32_t nalBit = 0x1F;
  2.     #define NAL_TYPE(value)             ((value) & nalBit)
  3.     void RKCodecNode::SearchIFps(unsigned char* buf, size_t bufSize, std::shared_ptr<IBuffer>& buffer)
  4.     {
  5.         size_t nalType = 0;
  6.         size_t idx = 0;
  7.         size_t size = bufSize;
  8.         constexpr uint32_t nalTypeValue = 0x05;
  9.    
  10.         if (buffer == nullptr || buf == nullptr) {
  11.             CAMERA_LOGI("RKCodecNode::SearchIFps parameter == nullptr");
  12.             return;
  13.         }
  14.    
  15.         for (int i = 0; i < bufSize; i++) {
  16.             int ret = findStartCode(buf + idx, size);
  17.             if (ret == -1) {
  18.                 idx += 1;
  19.                 size -= 1;
  20.             } else {
  21.                 nalType = NAL_TYPE(buf[idx + ret]);
  22.                 CAMERA_LOGI("ForkNode::ForkBuffers nalu == 0x%{public}x buf == 0x%{public}x \n", nalType, buf[idx + ret]);
复制代码
每经过一个h264转换过的buffer都会被传入SearchIFps接口中寻找IDR帧。此中findStartCode()接口会对buffer中的内容逐个字节扫描,知道寻找出NALU头来
  1.    int RKCodecNode::findStartCode(unsigned char *data, size_t dataSz)
  2.       {
  3.           constexpr uint32_t dataSize = 4;
  4.           constexpr uint32_t dataBit2 = 2;
  5.           constexpr uint32_t dataBit3 = 3;
  6.       
  7.           if (data == nullptr) {
  8.               CAMERA_LOGI("RKCodecNode::findStartCode parameter == nullptr");
  9.               return -1;
  10.           }
  11.       
  12.           if ((dataSz > dataSize) && (data[0] == 0) && (data[1] == 0) && \
  13.               (data[dataBit2] == 0) && (data[dataBit3] == 1)) {
  14.               return 4; // 4:start node
  15.           }
  16.       
  17.           return -1;
  18.       }
复制代码
当找到NALU头后就会对&0x1F 找出nal_unit_type,如果type为5标记关键帧信息并通过buffer->SetEsKeyFrame(1);接口上报。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

祗疼妳一个

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表