はじめに
以前に書いた2015-04-04 ざっくりとALSAとPulseAudioの関係が未だにアクセスがあるようなのでちょっと振り返ってみる。
基本的な理解としては、
- 最終的に音を鳴らすのはデバイス
- デバイスを叩くためにデバイスドライバ(カーネルランド)
- サウンド関連でカーネルランドとユーザーランド橋渡しをする部分を取りまとめているのがALSA
- 各アプリケーション間でサウンド関連のリソースを調停するのがPulseAudioなどのサウンドサーバ
これは変わっていない。
先述の記事では次のように書いた。
それを回避するためにPulseAudioなどのサウンドサーバはALSAのデバイスのふりをして、ALSAのAPIを直接叩いているアプリケーションのサウンドリソースを横取りしてしまう。 そうして、管理下に置いたあと本物のALSAに音を流す。
ここで言う本物のALSA
とは、カーネルランドにいるALSAのドライバフレームワークを主に示している。
実験環境
確認にはラズベリーパイ3 Model Bを使用。 OSはYocto Projectで作成したものを使用。(warrior/2.7)
ホストPCの環境はUbuntu 18.04。
ほんとに横取りしているのか
これを簡単に確認するにはPulseAudioが動く環境でaplayを実行してみるのがわかりやすい。
Yocto環境の設定
aplayとPulseAudioが動く環境を作るためにlocal.conf
に次の行を追加する。
# userland ALSA IMAGE_INSTALL_append = " alsa-utils" # pulseaudio IMAGE_INSTALL_append = " pulseaudio-server" # Enable onboard audio RPI_EXTRA_CONFIG = ' \n \ # Enable audio (loads snd_bcm2835) \n \ dtparam=audio=on \n \ '
RPI_EXTRA_CONFIGでは、オンボードのサウンドデバイスを有効化している。
bitbakeでcore-image-baseを作成し、そのイメージでラズベリーパイを起動する。
横取りの確認
sshなどを使用して、ラズベリーパイのターミナルを2つ立ち上げる。
片方でpulseaudioを起動する。
$ pulseaudio --verbose
そしてもう片方でaplayを実行する。
$ aplay /usr/share/sounds/alsa/Front_Left.wav
PulseAudioのターミナルの表示が次のようになり、aplayを実行することでPulseAudioが動作していることがわかる。
I: [pulseaudio] main.c: Daemon startup complete. I: [pulseaudio] module-suspend-on-idle.c: Sink alsa_output.platform-soc_audio.analog-mono idle for too long, suspending ... I: [alsa-sink-bcm2835 ALSA] alsa-sink.c: Device suspended... I: [pulseaudio] client.c: Created 0 "Native client (UNIX socket client)" I: [pulseaudio] protocol-native.c: Got credentials: uid=0 gid=0 success=1 I: [pulseaudio] sink-input.c: Trying to change sample rate I: [pulseaudio] alsa-sink.c: Updating rate for device hw:0, new rate is 48000 I: [pulseaudio] source.c: Changed sampling rate successfully I: [pulseaudio] sink.c: Changed format successfully I: [pulseaudio] sink-input.c: Rate changed to 48000 Hz I: [alsa-sink-bcm2835 ALSA] alsa-sink.c: Trying resume... I: [alsa-sink-bcm2835 ALSA] alsa-util.c: Cannot disable ALSA period wakeups I: [alsa-sink-bcm2835 ALSA] alsa-util.c: ALSA period wakeups were not disabled I: [alsa-sink-bcm2835 ALSA] alsa-sink.c: Time scheduling watermark is 36.75ms I: [alsa-sink-bcm2835 ALSA] alsa-sink.c: Resumed successfully... I: [alsa-sink-bcm2835 ALSA] alsa-sink.c: Starting playback. I: [pulseaudio] sink-input.c: Created input 0 "ALSA Playback" on alsa_output.platform-soc_audio.analog-mono with sample spec s16le 1ch 48000Hz and channel map mono I: [pulseaudio] sink-input.c: media.name = "ALSA Playback" I: [pulseaudio] sink-input.c: application.name = "ALSA plug-in [aplay]" I: [pulseaudio] sink-input.c: native-protocol.peer = "UNIX socket client" I: [pulseaudio] sink-input.c: native-protocol.version = "32" I: [pulseaudio] sink-input.c: application.process.id = "321" I: [pulseaudio] sink-input.c: application.process.user = "root" I: [pulseaudio] sink-input.c: application.process.host = "raspberrypi3" I: [pulseaudio] sink-input.c: application.process.binary = "aplay" I: [pulseaudio] sink-input.c: application.language = "C" I: [pulseaudio] sink-input.c: application.process.machine_id = "e08cd0ccf41446ecb446dbd2a097d362" I: [pulseaudio] sink-input.c: application.process.session_id = "c1" I: [pulseaudio] sink-input.c: module-stream-restore.id = "sink-input-by-application-name:ALSA plug-in [aplay]" I: [pulseaudio] protocol-native.c: Requested tlength=500.00 ms, minreq=125.00 ms I: [pulseaudio] protocol-native.c: Final latency 625.00 ms = 250.00 ms + 2*125.00 ms + 125.00 ms I: [alsa-sink-bcm2835 ALSA] alsa-sink.c: Tried rewind, but was apparently not possible. I: [alsa-sink-bcm2835 ALSA] alsa-sink.c: Tried rewind, but was apparently not possible. I: [alsa-sink-bcm2835 ALSA] alsa-sink.c: Tried rewind, but was apparently not possible. I: [alsa-sink-bcm2835 ALSA] alsa-sink.c: Tried rewind, but was apparently not possible. I: [alsa-sink-bcm2835 ALSA] alsa-sink.c: Tried rewind, but was apparently not possible. I: [pulseaudio] sink-input.c: Freeing input 0 "ALSA Playback" I: [pulseaudio] client.c: Freed 0 "ALSA plug-in [aplay]" I: [pulseaudio] protocol-native.c: Connection died. I: [pulseaudio] module-suspend-on-idle.c: Sink alsa_output.platform-soc_audio.analog-mono idle for too long, suspending ... I: [alsa-sink-bcm2835 ALSA] alsa-sink.c: Device suspended...
PulseAudioはフォアグラウンドで実行している時、一定時間クライアントから接続がないと終了してしまうので注意が必要。
callgrindで見てみる
実際のところ、どのようになっているのかをcallgrindで見てみる。
$ valgrind --tool=callgrind aplay /usr/share/sounds/alsa/Front_Left.wav
で作成したコールグラフをホストPCのkcachegrindで開く。
画面左上のリストからaplay
を選択し、左下のリストでmain
を選択する。
画面右下の方にあるCall Graph
のタブをクリックしコールグラフを開く。
するとaplayのメイン関数に注目したコールグラフが表示される。
しかし肝心な部分が<cycle 3>
としてまとめられてしまっていて細かいことを読み取ることができない。
ツールバーにあるCycle Detection
をクリックし、cycle検出機能をオフにすると、<cycle 3>
の内容が展開される。
main'2
からsnd_pcm_open
が呼び出されていることがわかる。
コールグラフ中の関数をダブルクリックすると、注目する関数を切り替えることができる。その時に注目している関数がどのライブラリ(ELF Object)に属しているかは画面左上のリストの選択状態でわかる。
このようにして関数を調べていくと、ALSA APIのインターフェースであるlibasound.so.2.0.0
のioplug_priv_transfer_areas
関数からPulseAudioのモジュールであるlibasound_module_pcm_pulse.so
のpulse_start
関数が呼び出されていることがわかった。
つまり、アプリケーションからALSAのAPIを呼び出すと内部からpulseaudioのモジュールが呼び出されているということになる。
まとめ
aplayやcallgrindをつかってユーザー空間でのALSA APIの挙動を調べたところ、PulseAudioがALSAの処理を横取りしていることが確認できた。
gstreamerでalsasinkを使う場合などでもcallgrindでみると同じようになっていることが確認できる。