Android Audio Driver基础

         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 
[email protected] 17112 audioserve  mem       REG             259,45     23948       1294 /vendor/lib/libsndmonitor.so 
[email protected] 17112 audioserve    7u      CHR              116,2       0t0       8060 /dev/snd/controlC0 [email protected] 17112 audioserve    8u      CHR              116,2       0t0       8060 /dev/snd/controlC0 
[email protected] 17112 audioserve   54u      CHR              116,2       0t0       8060 /dev/snd/controlC0 [email protected] 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 的连接或断开。

alsa调试命令

tinymix

可进行通道切换或配置

tinycap

录音命令,使用命令前先用 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 

tinyplay

播音命令,使用示例

//要先使用tinymix进行通道配置 
tinymix "QUAT_TDM_RX_0 Channels" "Two" 
tinymix "QUAT_TDM_RX_0 Audio Mixer MultiMedia1" "1" 
tinyplay /sdcard/Music/LoveYou.wav 

猜你喜欢

转载自blog.csdn.net/wangbuji/article/details/126374182
今日推荐