OpenHarmony南向之Audio

音频架构

Audio驱动框架基于HDF驱动框架实现,包含内核态(KHDF),和用户态(UHDF), 对北向提供音频HDI接口

音频框架图

oh-audio-fwk.png

驱动架构主要由以下几部分组成。

  • HDI adapter:实现Audio HAL层驱动(HDI接口适配),给Audio服务(frameworks)提供所需的音频硬件驱动能力接口。包含 Audio Manager、Audio Adapter、Audio Control、Audio Capture、Audio Render等接口对象。
  • Audio Interface Lib:向下配合内核中的Audio Driver Model使用,实现音频硬件的控制、录音数据的读取、播放数据的写入,向上和上层的Audio HDI Adapter层进行对接。
  • ADM(Audio Driver Model):音频驱动框架模型,向上服务于多媒体音频子系统,向下统一接口适配各自的驱动代码。

主要代码目录

  • drivers/hdf_core/framework/model/audiodevice/board/xxx/yyy/audio_drivers等: ADM相关驱动,KHDF部分
  • drivers/peripheral/audio: audio HAL 实现,UHDF部分
  • foundation/multimedia/audio_framework: audio framework 实现

调用流程

oh-audio-2.png

Audio Service

目录:foundation/multimedia/audio_framework/services/

client:
通过 Remote()->SendRequest,和服务端进行通信(IPC),binder
server:
server端向上通过IPC与client交互,向下与HAL交互

使用PulseAudio进行音频流管理
foundation/multimedia/audio_framework/services/audio_service/client/src/audio_service_client.cpp

HAL

HAL简单架构

img

  • alsa adapter(drivers/peripheral/audio/supportlibs/alsa_adapter):
    基于alsa lib(alsa用户态接口库),对alsa接口的封装,向下驱动基于alsa
  • adm adapter(drivers/peripheral/audio/supportlibs/adm_adapter):
    基于amate(ADM用户态接口库),对ADM接口的封装,向下驱动基于alsa
  • supportlib
    屏蔽声卡访问控制的差异,在用户态实现一套符合 hdi-adapter 规范的访问控制
  • hdi-passthrough
    将声卡(片内声卡、usb 声卡、HDMI 声卡等)抽象成 adapter,每个 adapter 都包含 supportlibs 抽象的 audioRender 和 audiocapture,最后通过 audiomanager 管理 adapters。进一步将音频 HDI 接口规范化封装
  • hdi-binder
    HDF音频驱动框架最上层的封装,基于C/S的IPC机制

所以,由此可以得到音频驱动适配的几种主流方案:

  1. 通过”adm adapter”对接自研ADM内核驱动,是目前社区主流方案
  2. 通过”alsa lib”对接ASLA,是针对已支持ASLA产品的友好适配方案
  3. 通过自己实现Vendor HAL来对接音频HDI接口,主要针对厂商有自己成熟的音频中间件的情况

目录结构

从下面代码目录结构可以很容易和上面的架构图一一对应

zdd@zdd-PC:~/WorkSpace/OHOS/oh-v3.2.2/drivers/peripheral/audio$ tree -L 2
.
├── audio.gni
├── BUILD.gn
├── bundle.json
├── config
├── hal
│   ├── hdi_binder
│   │   ├── proxy
│   │   └── server
│   ├── hdi_passthrough
│   └── pathselect
├── hdi_service
│   ├── binder
│   ├── BUILD.gn
│   ├── passthrough
│   ├── pathselect
│   └── supportlibs
├── interfaces
│   ├── 2.0
│   └── include
├── supportlibs
│   ├── adm_adapter
│   ├── alsa_adapter
│   ├── BUILD.gn
│   └── interfaces
└── test
    ├── BUILD.gn
    ├── fuzztest
    ├── resource
    ├── sample
    ├── systemtest
    └── unittest

HAL流程

HAL流程图

ADM流程

ADM流程基本上都是基于驱动消息机制(dispatch)实现:drivers/peripheral/audio/supportlibs/adm_adapter/src/audio_interface_lib_common.c

下面看看几种基本的ADM流程:

ADM启动流程

ADM启动流程图

  1. 系统启动时Audio模块的Platform、Codec、Dsp、Dai各个驱动首先被加载,各驱动从各自私有配置文件中获取配置信息,并将获取的配置信息保存到各驱动的Data数据结构中。
  2. 各驱动模块调用ADM注册接口将自己添加到各驱动模块的链表中。
  3. ADM模块读取hdf_audio_driver_0和hdf_audio_driver_1配置信息,加载各模块的具体设备。
  4. ADM模块调用各模块的初始化函数对各模块设备进行初始化。
  5. 将初始化成功的音频设备添加到cardManager链表。

HCS

以rk3568平台,结合上面的启动流程看看audio相关的hcs文件

  • device_info.hcs:
...
audio :: host {
            hostName = "audio_host";
            priority = 110;
            device_dai0 :: device {
                device0 :: deviceNode {
                    policy = 1;
                    priority = 50;
                    preload = 0;
                    permission = 0666;
                    moduleName = "DAI_RK3568";
                    serviceName = "dai_service";
                    deviceMatchAttr = "hdf_dai_driver";
                }
            }
            device_codec_0 :: device {
                device0 :: deviceNode {
                    policy = 1;
                    priority = 50;
                    preload = 0;
                    permission = 0666;
                    moduleName = "CODEC_RK809";
                    serviceName = "codec_service_0";
                    deviceMatchAttr = "hdf_codec_driver_0";
                }
            }
            device_codec_1 :: device {
                device0 :: deviceNode {
                    policy = 1;
                    priority = 50;
                    preload = 0;
                    permission = 0666;
                    moduleName = "CODEC_RK817";
                    serviceName = "codec_service_1";
                    deviceMatchAttr = "hdf_codec_driver_1";
                }
            }
            device_dsp :: device {
                device0 :: deviceNode {
                    policy = 1;
                    priority = 50;
                    preload = 0;
                    permission = 0666;
                    moduleName = "DSP_RK3568";
                    serviceName = "dsp_service_0";
                    deviceMatchAttr = "hdf_dsp_driver";
                }
            }
            device_dma :: device {
                device0 :: deviceNode {
                    policy = 1;
                    priority = 50;
                    preload = 0;
                    permission = 0666;
                    moduleName = "DMA_RK3568";
                    serviceName = "dma_service_0";
                    deviceMatchAttr = "hdf_dma_driver";
                }
            }
            device_audio :: device {
                device0 :: deviceNode {
                    policy = 2;
                    priority = 60;
                    preload = 0;
                    permission = 0666;
                    moduleName = "HDF_AUDIO";
                    deviceMatchAttr = "hdf_audio_driver_0";
                    serviceName = "hdf_audio_codec_primary_dev0";
                }
                device1 :: deviceNode {
                    policy = 2;
                    priority = 60;
                    preload = 0;
                    permission = 0666;
                    moduleName = "HDF_AUDIO";
                    deviceMatchAttr = "hdf_audio_driver_1";
                    serviceName = "hdf_audio_codec_primary_dev11";
                }
            }
            device_stream :: device {
                device0 :: deviceNode {
                    policy = 2;
                    priority = 80;
                    preload = 0;
                    permission = 0666;
                    moduleName = "HDF_AUDIO_STREAM";
                    serviceName = "hdf_audio_render";
                }
                device1 :: deviceNode {
                    policy = 2;
                    priority = 80;
                    preload = 0;
                    permission = 0666;
                    moduleName = "HDF_AUDIO_STREAM";
                    serviceName = "hdf_audio_capture";
                }
            }
            device_control :: device {
                device0 :: deviceNode {
                    policy = 2;
                    priority = 80;
                    preload = 0;
                    permission = 0666;
                    moduleName = "HDF_AUDIO_CONTROL";
                    serviceName = "hdf_audio_control";
                }
            }
            device_analog_headset :: device {
                device0 :: deviceNode {
                    policy = 1;
                    priority = 90;
                    preload = 0;
                    permission = 0666;
                    moduleName = "AUDIO_ANALOG_HEADSET";
                    serviceName = "analog_headset_service";
                    deviceMatchAttr = "analog_headset_attr";
                }
            }
        }
...
  • audio_config.hcs
root {
    platform {
        template card_controller {
            match_attr = "";
            serviceName = "";
            codecName = "";
            platformName = "";
            cpuDaiName = "";
            codecDaiName = "";
            dspName = "";
            dspDaiName = "";
        }
        controller_0x120c1000 :: card_controller {
            match_attr = "hdf_audio_driver_0";
            serviceName = "hdf_audio_codec_primary_dev0";
            codecName = "codec_service_0";
            platformName = "dma_service_0";
            cpuDaiName = "dai_service";
            codecDaiName = "codec_dai";
            dspName = "dsp_service_0";
            dspDaiName = "dsp_dai";
        }
        controller_0x120c1001 :: card_controller {
            match_attr = "hdf_audio_driver_1";
            serviceName = "hdf_audio_codec_primary_dev11";
            codecName = "codec_service_1";
            platformName = "dma_service_0";
            cpuDaiName = "dai_service";
            codecDaiName = "rk817_dai";
            dspName = "dsp_service_0";
            dspDaiName = "dsp_dai";
        }
    }
}
  • codec_config.hcs
root {
    platform {
        template codec_controller {
            match_attr = "";
            serviceName = "";
            codecDaiName = "";
        }
        controller_0x120c1030 :: codec_controller {
            match_attr = "hdf_codec_driver_0";
            serviceName = "codec_service_0";
            codecDaiName = "codec_dai";
            regConfig {

                /* reg, value */
                initSeqConfig = [
                    0x13,    0xf4,
                    ...
                ];

                controlsConfig = [
                    /*array index, iface, mixer/mux, enable,*/
                    0,  2,  0,  1,
                    ...
                ];

                /* reg, rreg, shift, rshift, min, max, mask, invert, value */
                ctrlParamsSeqConfig = [
                    0x31,    0x32,    0,    0,    0x00,    0xFF,   0xFF,   1,    0x00, // DACL/R Playback Volume
                   ...
                ];

                /* reg, rreg, shift, rshift, min, max, mask, invert, value */
                daiParamsSeqConfig = [
                    0x45,    0x45,    0,     0,    0x0,   0xFF,    0xFF,   0,     0x0C, // PLL_PREDIV_BIT
                    ...
                ];

                ctrlSapmParamsSeqConfig = [
                    0x27,    0x27,    5,     5,    0x00,    0x1,    0x1,    1,    0x00,     //LPGA MIC  -- connect MIC1
                    ...
                ];
                /*
                 sapm
                 reg is 0xFFFF: component has no sapm register bit
                 sapmType, compNameIndex, reg, mask, shift, invert, kcontrolNews, kcontrolsNum
                */
                sapmComponent = [
                    10,      0,       0x18,       0x1,     7,     1,     0,     0,  //ADCL
                    ...
                ];

                /*array index, iface, mixer/mux, enable*/
                sapmConfig = [
                    0,     2,    0,    1,
                    ...
                ];

            }
        }
        controller_0x120c1031 :: codec_controller {
            match_attr = "hdf_codec_driver_1";
            serviceName = "codec_service_1";
            codecDaiName = "rk817_dai";
        }
    }
}

ADM播放流程

ADM播放流程图

  1. 播放音频时,Interface Lib层通过播放流服务下发Render Open指令,Audio Stream Dispatch服务收到指令后分别调用各模块的函数接口对指令进行下发。
  2. Interface Lib层通过控制服务下发通路选择指令,Control Dispatch控制服务收到指令后调用Dai模块接口设置通路。
  3. Interface Lib层通过播放流服务下发硬件参数,Audio Stream Dispatch服务收到参数后分别调用各模块参数设置接口,对硬件参数进行设置。
  4. Interface Lib层通过播放流服务下发播放启动指令,Audio Stream Dispatch服务收到指令后分别调用各模块启动接口,对各模块进行启动设置。
  5. Interface Lib层通过播放流服务下发音频数据,Audio Stream Dispatch服务收到数据后调用Platform AudioPcmWrite接口将音频数据传给Dma。
  6. Interface Lib层通过播放流服务下发播放停止指令,Audio Stream Dispatch服务收到指令后分别调用各模块停止接口,对各模块进行停止设置。
  7. Interface Lib层通过播放流服务下发Render Close指令,Audio Stream Dispatch服务收到指令后调用Platform AudioRenderClose对已申请资源进行释放。

ADM控制流程

ADM控制流程图

  1. 设置音量,首先Interface Lib层通过控制服务下发获取音量范围指令,Control Dispatch控制服务收到指令后进行解析,并调用Codec模块Get函数,获取可设置音量的范围。
  2. Interface Lib层通过控制服务下发设置音量指令,Control Dispatch控制服务收到指令后进行解析,并调用Codec模块Set函数设置音量。

分布式音频组件

分布式音频是指多个设备之间音频外设跨设备协同使用的能力,如将设备A的音频通过设备B的Speaker进行播音,或者设备A使用设备B的Mic进行录音。
分布式音频不直接向应用提供接口,应用可以通过音频框架的接口来调用分布式音频能力,使用方式与本地音频一致。

概念说明

主控端(source) :分布式音频控制端设备,向被控端设备发送指令,实现在被控端设备上音频播放和录制的功能;

被控端(sink) :分布式音频被控制端设备,接收来自主控端设备的指令,使本地音频外设为主控端设备所用,用来播音或录音。

oh-distributedaudio_arch.png

参考