点击下方👇关注 Android系统攻城狮
第204篇原创文章
持续迭代 | Android+Linux多媒体进阶体系
专栏·课程 知识体系持续更新

1


上一篇我们梳理了module-alsa-sink如何把播放数据写入ALSA播放设备,本篇我们继续分析录音链路,录音链路的数据方向相反,声音先由ALSA采集,再送进PulseAudio,最后交给录音应用。
录音无声时,使用命令pactl list sources可以看到麦克风对应的Source,录出来的录音文件,却没有声音。Source出现在列表里,说明PulseAudio已经创建了录音设备对象,还需要检查ALSA capture PCM是否启动、IO线程是否读到数据、Source Output是否收到音频。
本篇继续探索如何采集ALSA录音设备,先分析module-alsa-source从哪里获取设备信息,再分析IO线程如何读取音频,以及采集数据如何进入录音应用。

2


第九篇分析了的Card路径进入时,module-alsa-card已经选中了当前Profile。Profile中存在input mapping,初始加载会在init_profile()里调用pa_alsa_source_new(),运行时切换Profile则由card_set_profile()创建新Source,或者释放旧Profile不再使用的Source。
input mapping记录当前Profile使用的ALSA录音设备、采样格式、声道布局和Port信息。pa_alsa_source_new()读取这些配置后开始建立录音数据通道。
module-alsa-source也支持单独加载,入口函数是pa__init()。模块参数解析完成后直接调用pa_alsa_source_new(),后面的ALSA设备打开、Source创建和数据采集使用同一套实现。
录音链路从加载Profile配置开始:

Card路径先检查当前Profile有没有input mapping,单独加载路径检查模块参数中的device_id或device是否指向正确设备。

3


创建顺序是先打开ALSA录音设备,再创建PulseAudio Source。
pa_alsa_source_new()先整理采样格式、采样率、声道数、fragment、buffer、mmap和tsched参数。带有mapping时调用pa_alsa_open_by_device_id_mapping(),只有device_id时调用pa_alsa_open_by_device_id_auto(),直接传入device字符串时调用pa_alsa_open_by_device_string()。三条路径打开的都是录音方向PCM:SND_PCM_STREAM_CAPTURE
PCM打开成功后,PulseAudio得到pcm_handle以及ALSA最终接受的采样参数。pa_alsa_source_new()接着初始化pa_source_new_data结构,写入Source名称、Profile、Port、采样参数和设备属性,再调用pa_source_new()创建Source。
Source创建完成后,pa_alsa_source_new()设置状态和端口回调,创建入口为thread_func()的IO线程,最后调用pa_source_put()发布Source。

pactl list sources能够看到Source,说明初始化阶段已经打开ALSA PCM并完成Source发布,IO线程决定录音过程中是否持续读到数据。

4


IO线程先检查Source是否处于打开状态。第一次进入采集路径时,ALSA Source私有运行数据中的first标志为真,线程调用snd_pcm_start()启动capture PCM。声卡开始向ALSA buffer写入录音数据后,IO线程进入读取过程。
读取方式由同一份运行数据中的use_mmap标志决定。
普通模式进入unix_read()。函数查询ALSA当前可读取的frame数量,申请PulseAudio memblock,再调用snd_pcm_readi()把数据从capture PCM复制到memblock中,最后组成memchunk,看下流程图所示。

mmap模式进入mmap_read()。函数通过snd_pcm_mmap_begin()获取ALSA已经采集的内存区域,把这段区域包装成memchunk,交给pa_source_post()处理,最后调用snd_pcm_mmap_commit()提交已经消费的frame,流程图如下。

播放方向先由PulseAudio准备数据,再写入ALSA,录音方向先启动ALSA capture PCM,再由PulseAudio服务读取录音数据。

5


snd_pcm_readi()和mmap接口负责从ALSA buffer读取数据,IO线程得到memchunk后,调用pa_source_post()把数据交给Source。
pa_source_post()遍历连接在当前Source上的普通Source Output。录音应用在PulseAudio里对应一个Source Output,Source采集到的数据通过这个对象发送给录音应用。Source设置了软件静音或软件音量时,pa_source_post()先处理memchunk,再把结果推给Source Output。
Source表示录音设备,Source Output表示正在使用这台设备的录音流。Source存在,说明设备通道已经建立。Source Output存在并连接到目标Source,说明录音应用已经接入采集链路,链路如下:

module-alsa-source负责启动ALSA采集并读取数据,Source负责把数据分发给录音流。

6


录音无声时,按以下顺序定位:
1.确认当前Profile是否提供录音通道
pactl list cards关键字段:Active Profile,当前Profile需要包含输入能力,module-alsa-card才会根据input mapping创建硬件Source。
2.确认Source是否已经创建
pactl list sources关键字段:Source名称、State、Sample Specification、Active Port和Properties
1.device.string对应ALSA设备2.device.profile.name对应来源Profile3.device.access_mode表示普通读取或mmap读取
3.确认录音应用是否接入目标Source
保持录音应用正在采集,再执行:
pactl list source-outputs关键字段:Source Output编号和Source。
1.Source字段需要指向目标Source。2.没有Source Output,说明应用还没有创建PulseAudio录音流。
4.确认ALSA是否提供capture PCM
arecord -lcat /proc/asound/pcm关键字段:目标声卡、PCM设备号和capture标记。
5.确认录音设备的占用和权限
fuser -v /dev/snd/pcmC*D*cls -l /dev/snd/pcmC*D*ccapture PCM被其他进程占用,或者PulseAudio运行用户没有访问权限时,pa_alsa_source_new()无法正常打开设备,后续Source创建和数据采集也不会执行。

7


1.播放时
module-alsa-sink从Sink Input获取音频,再写入ALSA playback 。2.录音时
module-alsa-source先启动ALSA capture PCM,通过普通读取或mmap获取数据,再调用pa_source_post()把音频发送给Source Output。

3.PulseAudio录音无声时
1.先确认Profile有没有input mapping2.再检查Source和Source Output3.最后确认ALSA capture PCM是否存在、设备能否打开。4.检查结果可以把问题定位到对象创建、录音应用接入或ALSA采集。
若读者朋友发现有错误、疑问的地方,或者好的建议,欢迎拍砖!!!