赞
踩
Android Audio底层使用的是liunx alsa驱动,录制或播放或声音所需的基本硬件是音频芯片或声卡(Sound cards), alsa声卡设备如下图所示:
1 Control // 监听声卡上的一些音频流状态
2 Mixer // 负责路由或混合声卡上的各种模拟信号
3 Pulse Code Modulation (PCM) //负责音频流的录制及播放
- //获取系统支持的声卡
- msmnile_gvmq:/ # cat /proc/asound/cards
-
- // 获取系统支持的pcm设备节点
- msmnile_gvmq:/ # lsof /dev/snd/pcmC0D
- 定义说明例如:pcmC0D16c
- C0:card 0 ,声卡ID
- D16:device 16 :音频设备 ID
- c:capture 支持录音
- p:playback 支持播音
- // 获取音频PCM设备详细描述
- msmnile_gvmq:/ # cat /proc/asound/pcm
- 00-00: MultiMedia1 (*) : : playback 1 : capture 1
- 00-01: MultiMedia2 (*) : : playback 1 : capture 1
- 00-02: VoiceMMode1 (*) : : playback 1 : capture 1
- 定义说明例如:00-00: MultiMedia1 (*) : : playback 1 : capture 1
- 00:声卡ID
- 00:设备ID
- MultiMedia1 :别称
- playback 1 :播音
- capture 1 : 录音
- // 查看当前占用snd相关音频设备
- msmnile_gvmq:/ # lsof | grep snd
- audio@2.0-servi 17112 audioserve mem REG 259,45 23948 1294 /vendor/lib/libsndmonitor.so
- audio@2.0-servi 17112 audioserve 7u CHR 116,2 0t0 8060 /dev/snd/controlC0 audio@2.0-servi 17112 audioserve 8u CHR 116,2 0t0 8060 /dev/snd/controlC0
- audio@2.0-servi 17112 audioserve 54u CHR 116,2 0t0 8060 /dev/snd/controlC0 audio@2.0-servi 17112 audioserve 60u CHR 116,3 0t0 14418 /dev/snd/pcmC0D0p
通道配置----映射usecase与pcm的关系
- 比如蓝牙电话录音,是走id为29的pcm
- // hardware/qcom/audio/configs/msmnile_au/audio_platform_info.xml
- <usecase name="USECASE_AUDIO_HFP_SCO" type="in" id="29" />
- <usecase name="USECASE_AUDIO_HFP_SCO" type="out" id="29" />
- <usecase name="USECASE_AUDIO_HFP_SCO_WB" type="in" id="29" />
- <usecase name="USECASE_AUDIO_HFP_SCO_WB" type="out" id="29" />
-
-
- 除了audio_platform_info.xml,pcm和usecase的默认映射是在
- vendor/qcom/opensource/audio-hal/primary-hal/hal/msm8974/platform.c#368
-
Mixer---用于 Audio route (FE to BE)
- // hardware/qcom/audio/configs/msmnile_au/mixer_paths_adp.xml
- <path name="hfp-sco">
- <ctl name="AUX_PCM_RX Audio Mixer MultiMedia6" value="1" /> //value = 1 含义是开启
- <ctl name="MultiMedia6 Mixer TERT_TDM_TX_0" value="1" />
- </path>
-
- 或mixer配置
- <!-- These are actual sound device specific mixer settings -->
- <path name="adc1">
- <ctl name="AIF1_CAP Mixer SLIM TX7" value="1"/>
- <ctl name="SLIM_0_TX Channels" value="One" />
- <ctl name="SLIM TX7 MUX" value="DEC6" />
- <ctl name="DEC6 MUX" value="ADC1" />
- <ctl name="IIR1 INP1 MUX" value="DEC6" />
- </path>
-
- 或音量调节
- <ctl name="DEC1 Volume" value="84" />

简单描述下高通 HAL 层音频通路的连接流程,音频通路分为三大块:FE PCMs、BE DAIs、Devices,这三块均需要打开并串联起来才能完成一个音频通路的设置。
| Front End PCMs | SoC DSP | Back End DAIs | Audio devices |
*************
PCM0 <------------> * * <----DAI0-----> Codec Headset
* *
PCM1 <------------> * * <----DAI1-----> Codec Speakers/Earpiece
* DSP *
PCM2 <------------> * * <----DAI2-----> MODEM
* *
PCM3 <------------> * * <----DAI3-----> BT
* *
* * <----DAI4-----> DMIC
* *
* * <----DAI5-----> FM
*************
Front End PCMs:音频前端,一个前端对应着一个 PCM 设备。
FE PCMs 是在音频流打开时设置的,我们首先要了解一个音频流对应着一个 usecase,具体细节请参考:Android 音频系统:AudioTrack、AudioFlinger Threads、AudioHAL Usecases、AudioDriver PCMs
- usecase 通俗表示音频场景,对应着音频前端FE,比如:
- low_latency:按键音、触摸音、游戏背景音等低延时的放音场景
- deep_buffer:音乐、视频等对时延要求不高的放音场景
- compress_offload:mp3、flac、aac等格式的音源播放场景,这种音源不需要软件解码,直接把数据送到硬件解码器(aDSP),由硬件解码器(aDSP)进行解码
- record:普通录音场景
- record_low_latency:低延时的录音场景
- voice_call:语音通话场景
- voip_call:网络通话场景
- start_output_stream() 代码分析:
-
- // 根据 usecase 找到对应 FE PCM id
- int platform_get_pcm_device_id(audio_usecase_t usecase, int device_type)
- {
- int device_id = -1;
- if (device_type == PCM_PLAYBACK)
- device_id = pcm_device_table[usecase][0];
- else
- device_id = pcm_device_table[usecase][1];
- return device_id;
- }
-
- int start_output_stream(struct stream_out *out)
- {
- int ret = 0;
- struct audio_usecase *uc_info;
- struct audio_device *adev = out->dev;
-
- // 根据 usecase 找到对应 FE PCM id
- out->pcm_device_id = platform_get_pcm_device_id(out->usecase, PCM_PLAYBACK);
- if (out->pcm_device_id < 0) {
- ALOGE("%s: Invalid PCM device id(%d) for the usecase(%d)",
- __func__, out->pcm_device_id, out->usecase);
- ret = -EINVAL;
- goto error_open;
- }
-
- // 为这个音频流新建一个 usecase 实例
- uc_info = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
-
- if (!uc_info) {
- ret = -ENOMEM;
- goto error_config;
- }
-
- uc_info->id = out->usecase; // 音频流对应的 usecase
- uc_info->type = PCM_PLAYBACK; // 音频流的流向
- uc_info->stream.out = out;
- uc_info->devices = out->devices; // 音频流的初始设备
- uc_info->in_snd_device = SND_DEVICE_NONE;
- uc_info->out_snd_device = SND_DEVICE_NONE;
- list_add_tail(&adev->usecase_list, &uc_info->list); // 把新建的 usecase 实例添加到链表中
-
- // 根据 usecase、out->devices,为音频流选择相应的音频设备
- select_devices(adev, out->usecase);
-
- ALOGV("%s: Opening PCM device card_id(%d) device_id(%d) format(%#x)",
- __func__, adev->snd_card, out->pcm_device_id, out->config.format);
- if (!is_offload_usecase(out->usecase)) {
- unsigned int flags = PCM_OUT;
- unsigned int pcm_open_retry_count = 0;
- if (out->usecase == USECASE_AUDIO_PLAYBACK_AFE_PROXY) {
- flags |= PCM_MMAP | PCM_NOIRQ;
- pcm_open_retry_count = PROXY_OPEN_RETRY_COUNT;
- } else if (out->realtime) {
- flags |= PCM_MMAP | PCM_NOIRQ;
- } else
- flags |= PCM_MONOTONIC;
-
- while (1) {
- // 打开 FE PCM
- out->pcm = pcm_open(adev->snd_card, out->pcm_device_id,
- flags, &out->config);
- if (out->pcm == NULL || !pcm_is_ready(out->pcm)) {
- ALOGE("%s: %s", __func__, pcm_get_error(out->pcm));
- if (out->pcm != NULL) {
- pcm_close(out->pcm);
- out->pcm = NULL;
- }
- if (pcm_open_retry_count-- == 0) {
- ret = -EIO;
- goto error_open;
- }
- usleep(PROXY_OPEN_WAIT_TIME * 1000);
- continue;
- }
- break;
- }
-
-
- 语音通话的情景有所不同,它不是传统意义的音频流,流程大概是这样的:
- 进入通话时,上层会先设置音频模式为 AUDIO_MODE_IN_CALL(HAL 接口是 adev_set_mode()),再传入音频设备 routing=$device(HAL 接口是 out_set_parameters())
- out_set_parameters() 中检查音频模式是否为 AUDIO_MODE_IN_CALL,是则调用 voice_start_call() 打开语音通话的 FE_PCM

Back End DAIs:音频后端,一个后端对应着一个 DAI 接口,一个 FE PCM 能够连接到一个或多个 BE DAI
- SLIM_BUS
- Aux_PCM
- Primary_MI2S
- Secondary_MI2S
- Tertiary_MI2S
- Quatermary_MI2S
Audio Device:有 headset、speaker、earpiece、mic、bt、modem 等;不同的设备可能与不同的 DAI 接口连接,也可能与同一个 DAI 接口连接
- device 表示音频端点设备,包括输出端点(如 speaker、headphone、earpiece)和输入端点(如 headset-mic、builtin-mic)。高通 HAL 对音频设备做了扩展,比如 speaker 分为:
-
- SND_DEVICE_OUT_SPEAKER:普通的外放设备
- SND_DEVICE_OUT_SPEAKER_PROTECTED:带保护的外放设备
- SND_DEVICE_OUT_VOICE_SPEAKER:普通的通话免提设备
- SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED:带保护的通话免提设备
- 详见 platform.h 音频设备定义,下面仅列举一部分:
-
- /* Sound devices specific to the platform
- * The DEVICE_OUT_* and DEVICE_IN_* should be mapped to these sound
- * devices to enable corresponding mixer paths
- */
- enum {
- SND_DEVICE_NONE = 0,
-
- /* Playback devices */
- SND_DEVICE_MIN,
- SND_DEVICE_OUT_BEGIN = SND_DEVICE_MIN,
- SND_DEVICE_OUT_HANDSET = SND_DEVICE_OUT_BEGIN,
- SND_DEVICE_OUT_SPEAKER,
- SND_DEVICE_OUT_HEADPHONES,
- SND_DEVICE_OUT_HEADPHONES_DSD,
- SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES,
- SND_DEVICE_OUT_SPEAKER_AND_LINE,
- SND_DEVICE_OUT_VOICE_HANDSET,
- SND_DEVICE_OUT_VOICE_SPEAKER,
- SND_DEVICE_OUT_VOICE_HEADPHONES,
- SND_DEVICE_OUT_VOICE_LINE,
- SND_DEVICE_OUT_HDMI,
- SND_DEVICE_OUT_DISPLAY_PORT,
- SND_DEVICE_OUT_BT_SCO,
- SND_DEVICE_OUT_BT_A2DP,
- SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP,
- SND_DEVICE_OUT_AFE_PROXY,
- SND_DEVICE_OUT_USB_HEADSET,
- SND_DEVICE_OUT_USB_HEADPHONES,
- SND_DEVICE_OUT_SPEAKER_AND_USB_HEADSET,
- SND_DEVICE_OUT_SPEAKER_PROTECTED,
- SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED,
- SND_DEVICE_OUT_END,
-
- /* Capture devices */
- SND_DEVICE_IN_BEGIN = SND_DEVICE_OUT_END,
- SND_DEVICE_IN_HANDSET_MIC = SND_DEVICE_IN_BEGIN, // 58
- SND_DEVICE_IN_SPEAKER_MIC,
- SND_DEVICE_IN_HEADSET_MIC,
- SND_DEVICE_IN_VOICE_SPEAKER_MIC,
- SND_DEVICE_IN_VOICE_HEADSET_MIC,
- SND_DEVICE_IN_BT_SCO_MIC,
- SND_DEVICE_IN_CAMCORDER_MIC,
- SND_DEVICE_IN_END,
-
- SND_DEVICE_MAX = SND_DEVICE_IN_END,
- };
-
-
- 扩展这么多是为了方便设置 acdb id,比如外放和通话免提虽然都用了同样的喇叭设备,
- 但是这两种情景会使用不同的算法,因此需要设置不同的 acdb id 到 aDSP,
- 区分 SND_DEVICE_OUT_SPEAKER 和 SND_DEVICE_OUT_VOICE_SPEAKER 是为了匹配到各自的 acdb id。

由于高通 HAL 定义的音频设备与 Android Framework 定义的不一致,所以在高通 HAL 中会根据音频场景对框架层传入的音频设备进行转换,详见:
platform_get_output_snd_device()
platform_get_input_snd_device()
在高通 HAL 中,我们只看到 usecase(即 FE PCM)和 device,device 和 BE DAI 是“多对一”的关系,device 连接着唯一的 BE DAI(反过来就不成立了,BE DAI 可能连接着多个 device),所以确定了 device 也就能确定所连接的 BE DAI。
路由选择
- 我们在 mixer_pahts.xml 中看到 usecase 相关的通路:
-
- <path name="deep-buffer-playback speaker">
- <ctl name="QUAT_MI2S_RX Audio Mixer MultiMedia1" value="1" />
- </path>
-
- <path name="deep-buffer-playback headphones">
- <ctl name="TERT_MI2S_RX Audio Mixer MultiMedia1" value="1" />
- </path>
-
- <path name="deep-buffer-playback earphones">
- <ctl name="QUAT_MI2S_RX Audio Mixer MultiMedia1" value="1" />
- </path>
-
- <path name="low-latency-playback speaker">
- <ctl name="QUAT_MI2S_RX Audio Mixer MultiMedia5" value="1" />
- </path>
-
- <path name="low-latency-playback headphones">
- <ctl name="TERT_MI2S_RX Audio Mixer MultiMedia5" value="1" />
- </path>
-
- <path name="low-latency-playback earphones">
- <ctl name="QUAT_MI2S_RX Audio Mixer MultiMedia5" value="1" />
- </path>

这些通路其实就是连接 usecase、device 之间的路由。比如 “deep-buffer-playback speaker” 是连接 deep-buffer-playback FE PCM、speaker Device 之间的路由,打开 “deep-buffer-playback speaker”,则把 deep-buffer-playback FE PCM 和 speaker Device 连接起来;关闭 “deep-buffer-playback speaker”,则断开 deep-buffer-playback FE PCM 和 speaker Device 的连接。
之前提到“device 连接着唯一的 BE DAI,确定了 device 也就能确定所连接的 BE DAI”,因此这些路由通路其实都隐含着 BE DAI 的连接:FE PCM 并非直接到 device 的,而是 FE PCM 先连接到 BE DAI,BE DAI 再连接到 device。这点有助于理解路由控件,路由控件面向的是 FE PCM 和 BE DAI 之间的连接,回放类型的路由控件名称一般是: $BE_DAI Audio Mixer $FE_PCM,录制类型的路由控件名称一般是:$FE_PCM Audio Mixer $BE_DAI,这很容易分辨。
例如 “deep-buffer-playback speaker” 通路中的路由控件:
<ctl name="QUAT_MI2S_RX Audio Mixer MultiMedia1" value="1" />
MultiMedia1:deep_buffer usacase 对应的 FE PCM
QUAT_MI2S_RX:speaker device 所连接的 BE DAI
Audio Mixer:表示 DSP 路由功能
value:1 表示连接,0 表示断开连接
这个控件的意思是:把 MultiMedia1 PCM 与 QUAT_MI2S_RX DAI 连接起来。这个控件并没有指明 QUAT_MI2S_RX DAI 与 speaker device 之间的连接,因为 BE DAIs 与 Devices 之间并不需要路由控件,如之前所强调”device 连接着唯一的 BE DAI,确定了 device 也就能确定所连接的 BE DAI“。
路由控件的开关不仅仅影响 FE PCMs、BE DAIs 的连接或断开,同时会使能或禁用 BE DAIs,要深入理解这点的话需要去研究 ALSA DPCM(Dynamic PCM) 机制,这里稍作了解即可。
路由操作函数是 enable_audio_route()/disable_audio_route(),这两个函数名称很贴合,控制 FE PCMs 与 BE DAIs 的连接或断开。
可进行通道切换或配置
录音命令,使用命令前先用 tinymix 切换到音频通道
- tinycap /sdcard/test.pcm -D 0 -d 0 -c 4 -r 48000 -b 32 -p 768 -n 10
- -D card 声卡
- -d device 设备
- -c channels 通道
- -r rate 采样率
- -b bits pcm 位宽
- -p period_size 一次中断的帧数
- -n n_periods 周期数
使用示例
- //要先使用tinymix进行通道配置
- tinymix "MultiMedia2 Mixer QUAT_TDM_TX_0" 1
- tinycap /data/test.wav -c 8 -d 1
播音命令,使用示例
- //要先使用tinymix进行通道配置
- tinymix "QUAT_TDM_RX_0 Channels" "Two"
- tinymix "QUAT_TDM_RX_0 Audio Mixer MultiMedia1" "1"
- tinyplay /sdcard/Music/LoveYou.wav
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。