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

1


上一篇分享了module-udev-detect如何通过udev发现ALSA声卡。
识别到声卡只是第一步,对PulseAudio来说,发现到一个ALSA card以后,还不能直接播放声音。因为PulseAudio内部真正参与路由的是Card、Profile、Port、Sink和Source。
也就是说,module-udev-detect解决的是:ALSA声卡在哪里?
module-alsa-card解决的是:这张ALSA声卡在PulseAudio里应该长成什么样?
一张声卡进入PulseAudio以后,最终会变成以下对象:
本文就沿着module-alsa-card的加载过程,看它如何把一张ALSA声卡转换成PulseAudio内部可用的音频对象。

2


module-alsa-card不是自己发现声卡,在module-udev-detect模块发现声卡后,会拼出一段加载参数,里面最关键的是device_id。
这个device_id就是ALSA声卡编号,例如声卡0,对应的参数就是:
device_id="0"module-alsa-card加载后,入口函数是pa__init(),它先解析模块参数,然后取出device_id,再调用snd_card_get_index()确认这张ALSA声卡是否存在。
加载层级关系:

这里要注意,module-alsa-card并不负责监听udev事件,它拿到的是已经被发现的声卡ID,然后开始为这张声卡创建PulseAudio对象。

3


声卡确认存在以后,module-alsa-card要解决第二个问题:这张声卡支持哪些使用模式?
在PulseAudio里,这个模式叫Profile。
例如一张声卡可能有这些模式:
HiFiAnalog Stereo OutputAnalog Stereo DuplexOffProfile不是凭空生成的,module-alsa-card有两条来源路径。
1.第一条是UCM路径。
1.如果use_ucm为真,module-alsa-card会调用pa_alsa_ucm_query_profiles()查询声卡的UCM配置。查询成功后,2.再调用pa_alsa_ucm_add_profile_set()把UCM里的Verb、Device等信息转换成PulseAudio的profile set。
2.第二条是传统profile-set路径。
1.如果UCM不可用,module-alsa-card会回退到pa_alsa_profile_set_new(),读取PulseAudio自己的profile-set配置。2这个配置定义Profile和ALSA PCM mapping的关系。
UCM可用vs不可用两条路径:

所以use_ucm=1不是绝对保证,它只是让PulseAudio先尝试UCM,只要UCM查询失败,源码就会走传统profile-set路径。

4


module-alsa-card拿到profile set以后,不会马上创建Sink和Source,它先创建Card模型,调用发生在pa_card_new_data_init()之后。
module-alsa-card先准备一个pa_card_new_data对象,把声卡属性、声卡名、Profile和Port都放进去,其中Profile和Port由add_profiles()函数加入。
add_profiles()会遍历profile set里的每个pa_alsa_profile,为它创建一个pa_card_profile。如果这个Profile有输出mapping,就统计它能创建多少个Sink。如果有输入mapping,就统计它能创建多少个Source。
同时,add_profiles()还会根据当前路径添加Port。
1.在UCM路径下,Port来自UCM Device。2.在传统路径下,Port来自mixer path。
对象关系:

然后module-alsa-card调用pa_card_new()创建真正的Card对象。
pa_card_new()会把data.profiles和data.ports接到Card上,并且让每个Profile和Port反向指向这张Card。
到这一步,PulseAudio已经知道:
1.这是一张声卡。2.这张声卡有哪些Profile。3.每个Profile关联哪些Port。4.每个Profile理论上会产生几个Sink和Source。5.但是Sink和Source此时还没有真正创建。

5


module-alsa-card创建Card以后,会调用pa_card_choose_initial_profile()选择初始Profile。
这个选择规则在PulseAudio core里:默认选择优先级最高、并且不是unavailable的Profile。如果所有Profile都不可用,就选优先级最高的Profile。
初始Profile确定以后,module-alsa-card先调用pa_card_put()把Card发布到PulseAudio core里。
接着才调用init_profile(),它会读取当前Card的active_profile,然后按Profile里的mapping创建Sink和Source:
pa_alsa_sink_new()。pa_alsa_source_new()。这就是为什么我们在pactl list cards里看到的是Card和Profile,在pactl list sinks里看到的是Sink,在pactl list sources里看到的是Source,它们不是同一层对象。
1.Card先存在。2.Profile和Port先挂到Card上。3.Sink和Source根据active Profile创建。
module-alsa-card主链路:


6


初始Profile有一个特殊点:它不会走card_set_profile()。
PulseAudio core里的pa_card_set_profile()有一个判断:如果Card还没有linked,说明这是初始Profile阶段,它只更新active_profile,不调用具体驱动模块的set_profile回调,所以初始创建靠init_profile()。
运行时切换Profile时,Card已经发布到core里,这时才会走module-alsa-card注册的card_set_profile()。
card_set_profile()主要做三件事。
第一 先处理旧Profile
1.如果旧Profile里的某个输出mapping在新Profile里不存在,就释放对应Sink。2.如果旧Profile里的某个输入mapping在新Profile里不存在,就释放对应Source。
第二 如果使用UCM
就调用pa_alsa_ucm_set_profile()切换UCM Verb或相关Device状态。第三 再处理新Profile。
1.新Profile里有输出mapping但还没有Sink,就调用pa_alsa_sink_new()创建Sink。2.新Profile里有输入mapping但还没有Source,就调用pa_alsa_source_new()创建Source。3.所以Profile切换不是简单改一个字符串。它会影响真实播放设备和录音设备的创建与销毁。
Profile切换过程:


7


pa_alsa_sink_new()不是只创建一个PulseAudio对象。
它会根据mapping确定采样格式、声道数、声道布局,然后打开ALSA播放PCM。接着准备pa_sink_new_data,填入设备属性、Profile信息、端口信息,最后调用pa_sink_new()创建Sink对象,并调用pa_sink_put()发布Sink。
如果是UCM路径,Sink的Port来自UCM上下文。如果是传统路径,Sink的Port来自mixer path。
Source类似:
pa_alsa_source_new()会打开ALSA录音PCM,准备pa_source_new_data,添加录音端口,然后调用pa_source_new()和pa_source_put()。
所以Sink和Source才是真正接近ALSA PCM数据通道的对象。
1.Card描述整张声卡。2.Profile描述声卡模式。3.Port描述输入输出端口。4.Sink/Source负责播放和录音数据入口。

8


1.是否加载了module-alsa-card
pactl list modules | grep -A20 module-alsa-card关键字段:
device_id=use_ucm=card_properties=module-udev-detect.discovered=12.再看Card、Profile和Port
pactl list cards关键字段:
Name: alsa_card.xxxProfiles:Active Profile:Ports:3.Sink是否由当前Profile创建
pactl list sinks关键字段:
Name:Driver:Properties:device.profile.namedevice.profile.descriptionPorts:Active Port:4.确认Source
pactl list sources关键字端:
Name:Driver:Properties:device.profile.namedevice.profile.descriptionPorts:Active Port:5.按对象层级确认
1.card是否存在?2.profile是否生成?3.active_profile是否正确?4.port是否可用?5.sink/source是否创建?6.active_port是否符合预期?这样才能准确判断问题出在哪一层。

9


1.功能
1.Card表示一张声卡。它是PulseAudio对ALSA card的整体建模。2.Profile表示声卡的一种工作模式。它决定当前模式下有哪些输入输出mapping可以使用。3.Port表示具体输入输出端口,比如耳机、喇叭、HDMI、麦克风。Port通常来自UCM Device或mixer path。4.Sink表示播放设备。应用播放声音时,最终会连到某个Sink。5.Source表示录音设备。应用录音时,最终会从某个Source采集数据。
2.串联起来总结一下:
1.module-alsa-card先把ALSA声卡建成Card。2.再把UCM或profile-set转换成Profile和Port。3.最后根据active Profile创建真正用于播放和录音的Sink和Source。
若读者朋友发现有错误、疑问的地方,或者好的建议,欢迎拍砖!!!