各类方案简述:
方案 | 说明 |
---|---|
方案1 | 通过"adm adapter"对接自研ADM内核驱动,是目前社区主流方案 |
方案2 | 通过"alsa lib"对接ASLA,是针对已支持ASLA产品的友好支持方案 |
方案3 | 通过"HDI to HDIL"对接hidl接口,此为对于OEM产商不希望换hidl层的方案,此为产品化方案,厂商需要自己适配 |
方案4 | 通过实现HDI接口对接Vendor HAL,此为OEM产商自己实现的可选方案 |
名词释义:
序号 | 名词 | 释义 |
---|---|---|
1 | audio factory | 基于audio hdi实现的用于创建管理输入、输出流及负责音频控制的的模块,可单独加载用于"直调模式" |
2 | supportlib plugin | 接口插件层,主要完成与内核驱动模型的对接,采用插件化的适配器模型进行可选对接 |
3 | amate | ADM mate,ADM在用户态的接口库 |
4 | alsa-lib | ALSA驱动模型在用户态的接口库 |
5 | alsa-adapter | 适配alsa-lib的插件模块 |
参考 OH社区提供的?Audio方案
基于openHarmony 3.2Beta3以上的版本的适配
需要在各自产品的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
需要在各自产品配置文件中打开对应开关,路径如下:其中productcompany表示您的企业名称,{product_company}表示您的企业名称,{product_name}表示您的产品名称
在?vendor\${product_company}\${product_name}\config.json
中将alsa_lib的控制开关打开
drivers_peripheral_audio_alsa_lib = true
在drivers\peripheral\audio\audio.gni
中将slas_lib控制开关打开
drivers_peripheral_audio_alsa_lib = true
在vendor\${product_company}\${product_name}\hals\audio\product.gni
中将alsa模式和alsa_lib打开
enable_audio_alsa_mode = true ... drivers_peripheral_audio_alsa_lib = true
新增文件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得到的值进行校验
如需要重新实现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" ? ? ],
在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; }
在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"; ? } ? ? ... ? ? }
在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; }
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
适配成功,播放过程无报错,播放却没声音
buffer_size和period_size设置不能过大,采用固定参数,不依赖time计算设置。
buffer_size和period_size固定值,可以从aplay命令中设置的size得到。
播放时write 返回 -32
驱动发生了xrun。缓冲区里所有的数据被消耗光了,而应用又不写入新的数据,underrun就会发生,同时alsa core 会进去xrun状态。应用会得到 -EPIPE的错误。
有如下两种方式解决xrun:
修改配置文件中的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事件
修改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减小,单位微秒
暂停时framework中不发送音频数据流了,但是驱动一直播放暂停时的尾音
文件audio_policy_config.xml中render_in_idle_state用来配置暂停时填充静音数据流,1表示填充静音数据,0表示不填充。
framework中会在暂停10s后由pulseaudio发送stop命令将pcm close。
录音read 时 检查wait返回异常
返回异常时需恢复pcm的状态
恢复流程 pcm--->stop--->close--->open--->setParams--->prepare
播放杂音大,设备发烫
设置相应的通路配置
录音后不能播放
原因是录音和播放的通路配置不能共存,需要在播放的start中重新设置播放的通路配置
开机后需要kill audio服务才能播放和录音
原因,audio服务开机启动的时候加载播放和录音资源,5s后将资源由空闲状态置为挂起状态时只执行了stop操作,没有执行close操作,而驱动执行了close操作。所以在adapter中执行stop后需要close。下一次start时需要open--->setParams--->prepare
开机启动时pulseaudio加载报错
声卡驱动加载需在init中
文件路径?device\board\${product_company}\${product_name}\cfg\init.${product_name}.cfg
加到 "init" 中
让驱动加载在服务启动之前
修改系统最大最小音量
文件路径?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() 中监听物理按键
录音文件播放,前面有3s延时无声音
snd_pcm_sw_params_set_start_threshold 设置1帧
start_threshold 决定了应用开始读取数据的时间点。通常被设置成1帧,意思是只要缓冲区里有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; }
播放卡住不能继续播放
设置超时时间,将时间-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))); ... }
音频文件播放结束后尾音循环不停止
修改文件路径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; }
调试时如何查看资源加载正常
文件路径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 播放
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__)
此方案由OME厂商自己适配
此方案由OEM产商自己实现的可选方案