OpenHarmony开发者论坛

标题: audio适配方案 [打印本页]

作者: Laval社区小助手    时间: 2023-10-30 16:47
标题: audio适配方案
[md]详情可点击:[ audio适配方案](https://laval.csdn.net/64eef49d6 ... tml?login=from_csdn)

## 1. 音频驱动可选四种集成方案

![audio1](https://devpress.csdnimg.cn/2dae6ba1a6c642f4a5909a0ead38f84d.png)

各类方案简述:

| 方案  | 说明                                                                                                 |
| ------- | ------------------------------------------------------------------------------------------------------ |
| 方案1 | 通过"adm adapter"对接自研ADM内核驱动,是目前社区主流方案                                             |
| 方案2 | 通过"alsa lib"对接ASLA,是针对已支持ASLA产品的友好支持方案                                           |
| 方案3 | 通过"HDI to HDIL"对接hidl接口,此为对于OEM产商不希望换hidl层的方案,此为产品化方案,厂商需要自己适配 |
| 方案4 | 通过实现HDI接口对接Vendor HAL,此为OEM产商自己实现的可选方案                                         |

## 2. 进程示意图

![]()

![](https://devpress.csdnimg.cn/e5d847c917e34d2b876589fe4e42feda.png)

名词释义:

| 序号 | 名词              | 释义                                                                                        |
| ------ | ------------------- | --------------------------------------------------------------------------------------------- |
| 1    | audio factory     | 基于audio hdi实现的用于创建管理输入、输出流及负责音频控制的的模块,可单独加载用于"直调模式" |
| 2    | supportlib plugin | 接口插件层,主要完成与内核驱动模型的对接,采用插件化的适配器模型进行可选对接                |
| 3    | amate             | ADM mate,ADM在用户态的接口库                                                               |
| 4    | alsa-lib          | ALSA驱动模型在用户态的接口库                                                                |
| 5    | alsa-adapter      | 适配alsa-lib的插件模块                                                                      |

## 3. 如下介绍各方案的适配方法

### 3.1 方案1 ADM适配方法

参考 OH社区提供的 [Audio方案](https://gitee.com/openharmony/do ... dio?login=from_csdn)

### 3.2 方案2 alsa适配方法

基于openHarmony 3.2Beta3以上的版本的适配

#### 3.2.1 内核编译开关配置

需要在各自产品的Linux kernel配置文件中打开对应开关,路径如下:其中\${product\_name}表示您的产品名称

kernel/linux/config/linux-5.10/arch/arm64/configs/\${product\_name}\_standard\_defconfig

以rk3568\_standard\_defconfig为例配置如下:

```
CONFIG_SOUND=y
CONFIG_SND=y

# CONFIG_DRIVERS_HDF_AUDIO is not set
# CONFIG_DRIVERS_HDF_AUDIO_RK3568  is not set
```

#### 3.2.2 产品化编译开关配置

需要在各自产品配置文件中打开对应开关,路径如下:其中productcompany表示您的企业名称,{product\_company}表示您的企业名称,{product\_name}表示您的产品名称

1. 在 `vendor\${product_company}\${product_name}\config.json`中将alsa\_lib的控制开关打开
   ```
   drivers_peripheral_audio_alsa_lib = true
   ```
2. 在`drivers\peripheral\audio\audio.gni`中将slas\_lib控制开关打开
   ```
   drivers_peripheral_audio_alsa_lib = true
   ```
3. 在`vendor\${product_company}\${product_name}\hals\audio\product.gni`中将alsa模式和alsa\_lib打开
   ```
   enable_audio_alsa_mode = true
   ...
   drivers_peripheral_audio_alsa_lib = true
   ```
4. 新增文件`vendor\${product_company}\${product_name}\hals\audio\alsa_adapter.json`配置声卡设备
   ```
   {
       "adapters": [{
           "name": "primary",
           "cardId": "xxxx",
           "daiId": ""
       }]
   }
   ```

注:"cardId"值在开发板中通过cat /proc/asound/card0/id查看,将值配入其中。 此参数使用的地方在`drivers\peripheral\audio\supportlibs\alsa_adapter\src\alsa_lib_common.c`中与snd\_ctl\_card\_info\_get\_id得到的值进行校验

#### 3.2.3 alsa-lib和alsa-utils组件编译依赖添加

如需要重新实现Audio HAL,则需要添加alsa-lib组件的编译依赖,可以采用以下两种方法进行添加。

在`drivers\peripheral\audio\bundle.json`中配置alsa\_lib和alsa\_utils库

```
"third_party": [
        "alsa-lib"
      ]
      
  "sub_component": [
        "//third_party/alsa-utils:alsa-utils"
      ],
```

#### 3.2.4. 针对芯片的适配修改

##### 3.2.4.1 播放的适配修改

1. 在`drivers\peripheral\audio\supportlibs\alsa_adapter\src\alsa_lib_render.c`中 根据alsa-utils工具中amixer命令`amixer controls`在开发板中找到声音播放相关的通路配置,将其打开。不同芯片通路配置不一样,依照芯片功能配置通路
   ```
   static int32_t InitMixerCtlElement(const char *adapterName, struct AudioCardInfo *cardIns, snd_mixer_t *mixer)
   {
      ...
     if (strncmp(adapterName, PRIMARY, strlen(PRIMARY)) == 0) {
        pcmElement = snd_mixer_first_elem(mixer);
        if (pcmElement == NULL) {
            AUDIO_FUNC_LOGE("snd_mixer_first_elem failed.");
            return HDF_FAILURE;
        }
   ​
        ret = GetPriMixerCtlElement(cardIns, pcmElement,0);  // 增加一个变量区分render和capture  0表示render
        if (ret < 0) {
            AUDIO_FUNC_LOGE("Render GetPriMixerCtlElement failed.");
            return HDF_FAILURE;
        }
      }
     ...
      // 播放的通路配置
      ret = AudioMixerSetCtrlMode(cardIns, adapterName, "Speaker Function", XX, 1);
      if (ret < 0) {
         AUDIO_FUNC_LOGE("AudioMixerSetCtrlMode Speaker Function failed!");
         return HDF_FAILURE;
      }
   ​
      ret = AudioMixerSetCtrlMode(cardIns, adapterName, "SPKL Mixer DACLSPKL Switch", XX, 1);
      if (ret < 0) {
         AUDIO_FUNC_LOGE("AudioMixerSetCtrlMode SPKL Mixer DACLSPKL Switch failed!");
         return HDF_FAILURE;
      }
      ...
   }
   ​
   int32_t AudioCtlRenderSceneSelect(
       const struct DevHandle *handle, int cmdId, const struct AudioHwRenderParam *handleData)
   {
       ...
   ​
       switch (descPins) {
           case PIN_OUT_SPEAKER:
               AudioMixerSetCtrlMode(cardIns, adapterName, "Speaker Function", XX, 1);
               AudioMixerSetCtrlMode(cardIns, adapterName, "SPKL Mixer DACLSPKL Switch", XX, 1);
               return HDF_SUCCESS;
           case PIN_OUT_HEADSET:
               ...
       }
   ​
       return HDF_FAILURE;
   }
   ```
2. 在`drivers\peripheral\audio\supportlibs\alsa_adapter\src\alsa_lib_common.c`中通过alsa-utils工具中amixer命令amixer scontrols找name
   ```
   #define MAX_ELEMENT           100  //根据amixer scontrols得的数量修改此值大小
   ​
   int32_t GetPriMixerCtlElement(struct AudioCardInfo *cardIns, snd_mixer_elem_t *pcmElement,int scene)
   {
       if (scene == 0){
           // 播放的左右声道混音器
          const char *mixerCtrlLeftVolName = "SPKL";
          const char *mixerCtrlRightVolName = "DAC";
      } else {
           // 录音的左右声道混音器
          const char *mixerCtrlLeftVolName = "SPKL";
          const char *mixerCtrlRightVolName = "DAC";
      }
      
       ...
      
   }
   ```

##### 3.2.4.2. 录音的适配修改

1. 在`drivers\peripheral\audio\supportlibs\alsa_adapter\src\alsa_lib_capture.c`中 根据alsa-utils工具中amixer命令amixer controls在开发板中找到声音播放相关的通路配置,将其打开。不同芯片通路配置不一样,依照芯片功能配置通路
   ```
   static int32_t InitMixerCtlElement(const char *adapterName, struct AudioCardInfo *cardIns, snd_mixer_t *mixer)
   {
       int32_t ret;
       ...
       if (strncmp(adapterName, PRIMARY, strlen(PRIMARY)) == 0) {
           ret = GetPriMixerCtlElement(cardIns, pcmElement,1);  // 增加一个变量区分render和capture 1表示capture
           if (ret != HDF_SUCCESS) {
               AUDIO_FUNC_LOGE("Capture GetPriMixerCtlElement failed.");
               return ret;
           }
       } else if (strncmp(adapterName, USB, strlen(USB)) == 0) {
           cardIns->ctrlLeftVolume = AudioUsbFindElement(mixer);
       } else {
           AUDIO_FUNC_LOGE("The selected sound card not supported, please check!");
           return HDF_FAILURE;
       }
       ...
       //录音的通路配置
       ret = AudioMixerSetCtrlMode(cardIns, adapterName, "Capture MIC Path", xx, 1);
       if (ret != HDF_SUCCESS) {
           AUDIO_FUNC_LOGE("AudioMixerSetCtrlMode failed!");
           return ret;
       }
   ​
       return HDF_SUCCESS;
   }
   ​
   int32_t AudioCtlCaptureSetMuteStu(
       const struct DevHandleCapture *handle, int cmdId, const struct AudioHwCaptureParam *handleData)
   {
       int32_t ret;
       ...
       if (muteState == false) {
           ret =
               AudioMixerSetCtrlMode(cardIns, adapterName, "Digital Capture mute", XX, 1); //录音的通路配置
       } else {
           ret =
               AudioMixerSetCtrlMode(cardIns, adapterName, "Digital Capture mute", SND_CAP_MIC_PATH, SND_IN_CARD_MAIN_MIC);
       }
       if (ret != HDF_SUCCESS) {
           AUDIO_FUNC_LOGE("AudioMixerSetCtrlMode failed!");
           return ret;
       }
       cardIns->captureMuteValue = (int32_t)handleData->captureMode.ctlParam.mute;
   ​
       return HDF_SUCCESS;
   }
   ```

#### 3.2.5. 问题定位

1. mmap方式读写数据相应的修改
   1.1 access 设置变化
   将
   
   ```
   ret = snd_pcm_hw_params_set_access(handle, params, access);
   ```
   
   改成
   
   ```
   snd_pcm_access_mask_t *mask = alloca(snd_pcm_access_mask_sizeof());
   ​
   snd_pcm_access_mask_none(mask);
   ​
   snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
   ​
   snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
   ​
   snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_COMPLEX);
   ​
   err = snd_pcm_hw_params_set_access_mask(handle, params, mask);
   ```
   
   1.2 读写修改
   
   ```
   snd_pcm_writei替换接口snd_pcm_mmap_writei
   ​
   snd_pcm_readi替换接口snd_pcm_mmap_readi
   ```
2. 适配成功,播放过程无报错,播放却没声音
   buffer\_size和period\_size设置不能过大,采用固定参数,不依赖time计算设置。
   buffer\_size和period\_size固定值,可以从aplay命令中设置的size得到。
3. 播放时write 返回 -32
   驱动发生了xrun。缓冲区里所有的数据被消耗光了,而应用又不写入新的数据,underrun就会发生,同时alsa core 会进去xrun状态。应用会得到 -EPIPE的错误。
   有如下两种方式解决xrun:
   
   1. 修改配置文件中的rate,使每秒钟发送的音频帧变大
      修改文件路径`vendor\${product_company}\${product_name}\audio\arm64\audio_policy_config.xml` 产品是64位使用arm64,32位使用arm
      
      ```
      <module name="Speaker" lib="libmodule-hdi-sink.z.so" role="sink" channels="2" rate="48000"  buffer_size="4096">
                      <Ports>
                          <Port adapter_name="primary" id="0" channels="2" rate="48000" buffer_size="4096" fixed_latency="1" render_in_idle_state="1" open_mic_speaker="0"/>
                      </Ports>
                  </module>
      ```
      
      rate要配置成芯片处理的最大rate值,小于芯片的处理rate就会发生xrun事件
   2. 修改framework中发送音频帧的频率
      修改路径`foundation\multimedia\audio_framework\frameworks\native\pulseaudio\modules\hdi\hdi_sink.c`
      
      ```
      static void ThreadFuncUseTiming(void *userdata)
      {
          ...
      ​
          while (true) {
              ...
      ​
              // Render some data and drop it immediately
              if (u->render_in_idle_state && PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
                  if (u->timestamp <= now && pa_atomic_load(&u->dflag) == 0) {
                      pa_atomic_add(&u->dflag, 1);
                      ProcessRenderUseTiming(u, now);
                  }
      ​
                  pa_usec_t sleep_for_usec = pa_bytes_to_usec(u->sink->thread_info.max_request, &u->sink->sample_spec);
                  sleep_for_usec -= 10000; // 修改发生流的时间间隔
                  pa_rtpoll_set_timer_relative(u->rtpoll, sleep_for_usec);
              } else if (!u->render_in_idle_state && PA_SINK_IS_RUNNING(u->sink->thread_info.state)) {
                  if (u->timestamp <= now && pa_atomic_load(&u->dflag) == 0) {
                      pa_atomic_add(&u->dflag, 1);
                      ProcessRenderUseTiming(u, now);
                  }
      ​
                  pa_usec_t sleep_for_usec = pa_bytes_to_usec(u->sink->thread_info.max_request, &u->sink->sample_spec);
                  sleep_for_usec -= 10000; // 修改发生流的时间间隔
                  pa_rtpoll_set_timer_relative(u->rtpoll, sleep_for_usec);
              } else {
                  pa_rtpoll_set_timer_disabled(u->rtpoll);
              }
              ...
          }
          ...
      }
      ```
      
      注:发生卡顿 将sleep\_for\_usec减小,单位微秒
4. 暂停时framework中不发送音频数据流了,但是驱动一直播放暂停时的尾音
   文件audio\_policy\_config.xml中render\_in\_idle\_state用来配置暂停时填充静音数据流,1表示填充静音数据,0表示不填充。
   framework中会在暂停10s后由pulseaudio发送stop命令将pcm close。
5. 录音read 时 检查wait返回异常
   返回异常时需恢复pcm的状态
   恢复流程 pcm--->stop--->close--->open--->setParams--->prepare
6. 播放杂音大,设备发烫
   设置相应的通路配置
7. 录音后不能播放
   原因是录音和播放的通路配置不能共存,需要在播放的start中重新设置播放的通路配置
8. 开机后需要kill audio服务才能播放和录音
   原因,audio服务开机启动的时候加载播放和录音资源,5s后将资源由空闲状态置为挂起状态时只执行了stop操作,没有执行close操作,而驱动执行了close操作。所以在adapter中执行stop后需要close。下一次start时需要open--->setParams--->prepare
9. 开机启动时pulseaudio加载报错
   声卡驱动加载需在init中
   文件路径 `device\board\${product_company}\${product_name}\cfg\init.${product_name}.cfg`
   
   ```
   加到  "init" 中
   ```
   
   让驱动加载在服务启动之前
10. 修改系统最大最小音量
   文件路径 `foundation\multimedia\audio_framework\interfaces\inner_api\native\audiomanager\include\audio_system_manager.h`
   
   ```
   static constexpr int32_t NEW_MAX_VOLUME_LEVEL = 30;   // 新增一个最大音量值,不改变物理按键值
   ```
   
   文件路径 `foundation\multimedia\audio_framework\services\audio_service\client\src\audio_system_manager.cpp`
   
   ```
   float AudioPolicyServer::MapVolumeToHDI(int32_t volume)
   {
       float value = (float)volume / NEW_MAX_VOLUME_LEVEL;
       float roundValue = (int)(value * CONST_FACTOR);
   ​
       return (float)roundValue / CONST_FACTOR;
   }
   ​
   int32_t AudioPolicyServer::ConvertVolumeToInt(float volume)
   {
       float value = (float)volume * NEW_MAX_VOLUME_LEVEL;
       return nearbyint(value);
   }
   ```
   
   在`foundation\multimedia\audio_framework\services\audio_policy\server\src\audio_policy_server.cpp` 中 SubscribeKeyEvents() 中监听物理按键
11. 录音文件播放,前面有3s延时无声音
   snd\_pcm\_sw\_params\_set\_start\_threshold 设置1帧
   start\_threshold 决定了应用开始读取数据的时间点。通常被设置成1帧,意思是只要缓冲区里有1帧,应用就可以把他读走。

#### 3.2.6. 补充知识

1. 调用snd\_hctl\_load返回异常
   驱动中部分混音设备异常,这时需要alsa-lib在异常的地方不做处理
   文件路径`third_party\alsa-lib\src\control\hcontrol.c`
   
   ```
   int snd_hctl_load(snd_hctl_t *hctl)
   {
       ...
   
       for (idx = 0; idx < hctl->count; idx++) {
           int res = snd_hctl_throw_event(hctl, SNDRV_CTL_EVENT_MASK_ADD,
                              hctl->pelems[idx]);
           if (res < 0)
               // return res; //异常不做处理
       }
       err = snd_ctl_subscribe_events(hctl->ctl, 1);
    _end:
       free(list.pids);
       return err;
   }
   ```
2. 播放卡住不能继续播放
   设置超时时间,将时间-1无超时机制改成具体超时时间
   文件路径`third_party\alsa-lib\src\pcm\pcm.c`
   
   ```
   int snd_pcm_wait_nocheck(snd_pcm_t *pcm, int timeout)
   {
       ...
   
       do {
           __snd_pcm_unlock(pcm->fast_op_arg);
           err_poll = poll(pfd, npfds, 80);  // 修改timeout时间为80
           __snd_pcm_lock(pcm->fast_op_arg);
           ...
       } while (!(revents & (POLLIN | POLLOUT)));
   
       ...
   }
   ```
3. 音频文件播放结束后尾音循环不停止
   修改文件路径`third_party\alsa-lib\src\pcm\pcm.c`
   
   ```
   int snd_pcm_drain(snd_pcm_t *pcm)
   {
       ...
   
       /* lock handled in the callback */
       if (pcm->fast_ops->drain)
           SNDMSG("pcm->fast_ops->drain");
           // err = pcm->fast_ops->drain(pcm->fast_op_arg);  //注释
       else
           err = -ENOSYS;
       return err;
   }
   ```
4. 调试时如何查看资源加载正常
   文件路径`third_party\pulseaudio\conf\default.pa`
   打开 load-module libmodule-cli-protocol-unix.z.so 重新编译即可
   或板子中将 /system/etc/pulse/default.pa 中的 # load-module libmodule-cli-protocol-unix.z.so 注释放开,重启即可
   查看命令
   
   ```
   pacmd list-sources  // mic 录音
   ​
   pacmd list-sinks // speak 播放
   ```
5. third\_part 中增加hilog日志
   alsa-lib中增加说明
   文件路径 `third_party/alsa-lib/bundle.json`
   
   ```
   "components": [
       "libhilog"
   ],
   ```
   
   文件路径`third_party\alsa-lib\BUILD.gn`
   
   ```
   include_dirs = [
       "//base/hiviewdfx/hilog/interfaces/native/innerkits/include",
   ]
   external_deps = [
       "hilog_native:libhilog"
   ]
   ```
   
   在基础头文件中引人
   
   ```
   #include "hilog/log.h"
   #undef LOG_DOMAIN
   #undef LOG_TAG
   #define LOG_DOMAIN 0xD002D00   // 要在白名单中
   #define LOG_TAG "alsa_lib"
   ​
   // 使用方法
   HILOG_INFO(LOG_CORE,__VA_ARGS__)
   HILOG_ERROR(LOG_CORE,__VA_ARGS__)
   ```

### 3.3 方案3 HDI to HDIL方案

此方案由OME厂商自己适配

### 3.4 方案4 HDI接口对接Vendor HAL 方案

此方案由OEM产商自己实现的可选方案

[/md]




欢迎光临 OpenHarmony开发者论坛 (https://forums.openharmony.cn/) Powered by Discuz! X3.5