Linux车机平台pulseaudio多alsasink配置

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Miss_yuan/article/details/78252284

https://www.freedesktop.org/wiki/Software/PulseAudio 官网上的介绍是这样的:
pulseaudio 是一个POSIX操作系统上的声音系统。是音频应用的代理。它允许你对音频数据,在从应用传递到硬件的过程中,做更多的操作。像把音频数据传递到另一台机器,更改采样率,声道,多路音频混音等。

车机平台,会包含多种声音的处理。多媒体,语音,导航等。
我要做的是,在平台中声卡驱动及alsalib接口已经就绪的状态下,提供的接口,用于不同音频类型的播放及音量控制,还有录音。
其实直接用alsa也不是不可以,但是感觉pulseaudio功能还是多一些。而且后续可以通过配置属性实现软件的 audio route 控制。

使用alsa的小工具,可以查看当前配置好的playback和record接口。下面是我开发板中的信息:

root@atlas7-arm:~# aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: kasaudiocard [kas-audio-card], device 0: Music Playback (*) []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: kasaudiocard [kas-audio-card], device 1: Navigation Playback (*) []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: kasaudiocard [kas-audio-card], device 2: Alarm Playback (*) []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
...

root@atlas7-arm:~# arecord -l
**** List of CAPTURE Hardware Devices ****
card 0: kasaudiocard [kas-audio-card], device 8: Analog Capture (*) []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
...

#不同音频类型的输出方案
pulseaudio 通过alsalib API处理音频输出的是alsa-sink对象。每个alsa-sink对象会对应一个alsa hw的输出设备(hw:0,0 hw:0,1 dmixer这些)。
所以,想法就是为不同的alsa hw设备接口,分别创建不同的alsa-sink,这样在处理不同APP的音频数据时,采用对应的alsa-sink作为输出端。
这样,需要做的就是为pulseaudio生成不同的alsa-sink,然后在处理不同类型的音频数据时,做对应的选择即可。

#Pulseaudio 的配置文件及模块加载
pulseaudio是一个守护进程,参考了官网的一些资料,把pulseaudio运行在了system-wide模式。
In some situations however, such as embedded systems where no real notion of a user exists, it makes sense to use the system-wide mode.
默认的配置文件在 /etc/pulse/daemon.conf
pulseaudio 加载模块,可以通过配置pa文件的方式(普通模式下是default.pa, system-wide模式下是system.pa)来在pulseaudio daemon启动后,自动加载
通过pa文件自动加载的话,要确保/etc/pulse/daemon.conf中的相关配置正确(load-default-script-file 和 default-script-file )
也可以通过pacmd工具来发送命令动态加载。

#创建alsa-sink对象
有几种方式

  1. 加载module-alsa-sink模块,每加载一次module-alsa-sink,会创建一个对应的alsa-sink对象。
  2. 加载module-alsa-card 模块,module-alsa-card模块会根据profile set 来创建一个或者多个alsa-sink对象。
  3. 加载module-udev-detect 模块,这个模块会发现alsa-card设备,然后加载module-alsa-card,然后就和方式2一样

按照前面说的,其实我是希望创建3个alsa-sink,分别对应hw:0,0 (Music Playback),hw:0,1 (Navigation Playback),hw:0,2(Alarm Playback)这几个alsa设备。

先看一下直接加载module-alsa-sink这种方式,从参数说明看,可以通过device和device_id来指定具体要关联打开的alsa设备。
通过阅读代码和实际测试发现:
如果设置了device_id参数的话,device参数就不会生效,而是会先生成一个默认的profile set,然后根据其中每个profile配置里的设备字串模版,把device_id值带入,然后尝试打开设备。
而这个默认的profile set内容来自于 /usr/share/pulseaudio/alsa-mixer/profile-sets/default.conf,其中配置了不同的mapping,比如这种

[General]
auto-profiles = yes
...
[Mapping analog-stereo]
device-strings = front:%f hw:%f
channel-map = left,right
paths-output = analog-output analog-output-lineout ...
paths-input = analog-input-front-mic analog-input-rear-mic ...
priority = 10

auto-profiles 配置为yes的话,就会自动为每个mapping创建一个profile。
然后在创建alsa-sink时,就会循环的对每个profile中的device-strings带入device_id参数。比如device_id设置为1,front:1 hw:1 就会被尝试打开。
其实可以看出来,这个默认的配置并不能和咱们嵌入式开发板上的声卡配置相匹配。
在不自己重新配置的情况下,我怎么能打开hw:0,1 hw:0,2 这样的设备呢…

如果不使用device_id参数,直接配置device参数为hw:0,0或者 hw:0,1 这样,就会直接使用这个字串去打开alsa设备。
像这样 load-module module-alsa-sink device=hw:0,0
当然我们可以设置其他相关的音频参数 load-module module-alsa-sink device=hw:0,0 channels=2 rate=44100,也可以为这个alsa-sink指定名称 sink_name=Muisc-Playback
这样的话,只要在 system.pa 写入如下几行,就可以按需求配置出不同的alsa-sink对象了。

load-module module-alsa-sink device=hw:0,0 sink_name=Muisc-Playback
load-module module-alsa-sink device=hw:0,1 sink_name=Navigation-Playback
load-module module-alsa-sink device=hw:0,2 sink_name=Alarm-Playback

再来看一下加载module-alsa-card 模块这种方式,
从代码里可以发现,这种方式还是会先生成profile set,然后根据一个选中的 profile(active_profile),为其中每个output_mapping创建一个alsa-sink对象。
(input_mapping创建对应的alsa-source对象)
其实本人目前对于profile,mapping,path的概念也是一知半解。
从已知的情况来看,一个profile就是一组mapping的集合,一个mapping对应puseaudio中的一个sink或者source,而path则对应这sink或者source上的port。
path中定义了一些控制元素element,也就是和alsa mixer control element对应的。
从代码看来,alsa-sink创建后,会激活其中的一个port,会probe其中的element,pusleaudio只关注其中可以控制mute和volume的element,如果有这种类型的element,则会记录下来,在之后的volume和mute操作中,会通过这些element来使用硬件接口实现volume和mute。

扫描二维码关注公众号,回复: 3737833 查看本文章

如果按照默认的配置,因为 hw:0 这个设备字串可以成功打开,analog-stereo这个profile会被选中,module-alsa-card模块会用hw:0创建一个alsa-sink对象。然后probe analog-output path中指定的那些element,当然probe后也都是无效的。
前面也提到了,默认的profile和我们开发板的声卡配置是不能匹配的。而且我是希望创建多个alsa-sink的,所以参考原有的配置,并查找资料写了一个自己的配置
/usr/share/pulseaudio/alsa-mixer/profile-sets/my-default.conf(测试只写了2个mapping)

[General]
auto-profiles = no

[Profile output:Music-Playback+output:Navigation-Playback]
description = multiple-sink-profiles
output-mappings = Music-Playback Navigation-Playback
priority = 10

[Mapping Music-Playback]
device-strings = hw:%f,0
channel-map = left,right
paths-output = music-analog-output
priority = 9
direction = output

[Mapping Navigation-Playback]
device-strings = hw:%f,1
channel-map = left,right
paths-output = navi-analog-output
priority = 9
direction = output

/usr/share/pulseaudio/alsa-mixer/paths/music-analog-output.conf是这样写的,navi-analog-output.conf类似

[Element Music Stream Vol]
switch = ignore
volume = merge

[Element Music Stream Mute]
switch =mute
volume = ignore

其中 Music Stream Vol 和 Music Stream Mute 分别是 hw:0,0 这个卡的volume和mute的control element。
声卡0 的alsa mixer的控制元素,可以通过如下命令查看到

amixer -c 0 contents

这个 [Profile output:Music-Playback+output:Navigation-Playback] 是一个profile包含多个mapping的写法方式。
auto-profiles = no 是为了不给mapping在自动生成profile,不然的话,这个文件读出来会有3个profile,而且后两个自动生成的profile优先级会高于第一个。那样就不会选择第一个profile为active_profile了。(当然咱们也可以通过 module-alsa-card 的profile 指定active_profile)
通过指定自己的profile set方式来加载 module-alsa-card 模块:

load-module module-alsa-card device_id=0 profile_set=my-default.conf

会发现module-alsa-card 会根据profile的output-mappings为我们创建了对应的2个alsa-sink(加载多个的alsa-sink和alsa-source的方式都类似)

最后一种通过加载module-udev-detect 模块和第二种类似,只是在默认情况下,module-alsa-card 的参数没有设置profile set,那样就会使用默认的配置了。并且也没有看到有参数可以指定profile set,所以这个方式暂时不使用了。

#Puseaudio使用硬件接口控制volume和mute
pusleaudio最终还是需要通过alsa mixer 接口来控制volume和mute的,这样就需要pulseaudio知道对应的mixer control element。

如果使用直接加载 module-alsa-sink 的方式来创建alsa-sink的话,有一个control参数可以设置,代码中可以发现,通过这个control参数可以设置一个element,pulseaudio会去检测这个element的属性,看是否可以控制volume或者mute的。(代码在alsa-sink.c的find_mixer()函数中),但是如果需要volume和mute两个element,该如何设置呢,这个还不太清楚。

如果使用加载module-alsa-card 模块的方式在创建alsa-sink的话,就可以通过在path的配置中指定element。这样pulseaudio可以同时得到volume和mute的控制element。个人觉得如果需要使用硬件接口来控制volume和mute的话,还是需要这个方式吧。

#控制调试Pulseaudio
启动pulseaudio我用的这个命令,把能打印的log都打印出来。

pulseaudio --log-level=4 --daemonize=no --system --single-user --log-target=file:/skypine/pa.log --log-time &

pacmd可以通过protocal-native模块和pulseaudio交互。可以查询各种信息。用如下命令启动。启动后输入help就会有用法。

PULSE_RUNTIME_PATH=/var/run/pulse pacmd

#alsa-sink的选择
创建alsa-sink的时候,会有名称的。
使用paplay测试可以加 -d 参数,比如想用music那个alsa-sink就这样

paplay -d Muisc-Playback test.wav

如果是用API播放,pa_stream_connect_playback()第二次参数填alsa-sink的名称。

#遇到的一些问题
我的hw:0,1卡驱动里是配置固定48000采样率的。建立alsa-sink之后,如果送入44100采样率的数据发现播放没有声音了。
原因是,由于hw不支持,pulseaudio不能重新用44100参数打开设备,就会使用软件的resampler来重采样,然后默认配置的resample method是 speex-float-0。dump了一下resample的输出,发现全是0数据。后来换了一个resample方式,没问题了。板子上speex的库也是有的,resample数据的API返回值也正确,不知道为啥数据不对。暂时没继续看了。

使用自己配置的profile,创建出来的music alsa-sink,播放没有声音。
原因,从log看并没有什么不正常。仔细查了一下,发现alsa-sink在avtive port之后查询了一下 “Music Stream Mute” 这个element 的mute 状态。得到的结果是0(结果没错),然是接着通过alsa mixer API设置了mute状态为!0,alsa-sink被静音了。手动通过amixer 把这个element值设置回来就好了。但是不清楚代码逻辑为啥是这样的。
因为我这边不是一定需要用硬件控制mute啊,直接把 [Element Music Stream Mute] 中的 switch 设置为 ignore 也就不会有问题了。

alsa-sink播放的时候,经常会underrun,把log关掉或者输出到文件后会好一些,但是偶尔还是会出现。
查了一下log,发现alsa-sink线程在hw buffer数据充足,而且没有其他事件时,sleep了一个固定的时间,然后underrun了,线程又被alsa叫醒。
最简单的办法是加载 alsa-sink 时,tsched设置为0,这个值默认是1的。这样不sleep,就不会underrun,但性能上可能差一些吧。
其实看代码,sleep设置的时间是hw buffer 的数据时长 减去了一个 watermark 值(默认20ms),按道理应该可以在underrun之前结束sleep的啊。后面调整一下这个值,可能会有改善的。

猜你喜欢

转载自blog.csdn.net/Miss_yuan/article/details/78252284