この連休にはオーディオ(スピーカー・部屋)の音響特性の測定と調整をしようと思っていた。が、今日は下の家族が居るようで、子どもが走り回ったりして雑音が出るので延期した。それで、ちょっと前に思い付いた機能を作ってみた。
僕は、gmusicbrowser(GMB)の出力は標準のPulseAudioでなくJACKに出すようにしている。それに深い意味や大きなメリットがある訳ではないのだが、イコライザや出力はJACKなので、PulseAudioを介さずに直接JACKに入れることで、わずかに音の劣化が減ることを期待しているのだ。
が、それにはちょっとした問題がある。GMBのJACK出力は、曲を再生中以外ではなくなってしまうので、そのままでは再生するたびに接続しなければならず、全く実用的でない。それで、jack-plumbingという自動接続プログラムで接続するようにした。
それで問題なく使えていたのだが、先日、一つだけ問題が見つかった。音源がモノラル(正確にはチャネル数が1)の場合、左のスピーカーからしか音が出ないのだ(JACKでなく、PulseAudioの時は出ていた)。
どういうことかというと、GMBやJACKにはモノラルをステレオに変換(正確には、単一チャネルの音を両チャネルに出す)する機能がないため、モノラルの場合にはGMBからは最初の出力ポート(左チャネル)しか生成されず、jack-plumbingにもチャネル数が1の時は重複接続するような機能はないため、結局、左チャネルの入力ポートにしか接続されないので、左からしか音が出ないのだ。
モノラル(1チャネル)の音源なんてほとんどないから、我慢・無視すればいいだけなのだが、好奇心とか興味でなんとかしたくなった。それで、それにはGMBの改造が要るので、どうせやるなら、ついでにJACKへの自動接続機能も付けて、jack-plumbingを廃止したくなった。やり方は大体分かっていたので、今日は実装と修正をした。
それから、別件だが、少し前に、特定のアプリの音を特定の出力(例: ヘッドフォン)だけに出したくなって、それも実現していた。
以下に、それぞれの実現方法や実装の概要やちょっと苦労した点を書く。
(4/29 13:25追記) さっき、昨日思い付いたこと(下記の「Gstreamerの機能でも接続できるのかも知れない。」)を試したら、意外に苦労はしたものの、概ねうまく行った。おかげで、処理を大幅に簡素化できた。Gstreamerには分からないことが多過ぎて、まだ気に入らないこと(モノラルの処理)があるのだが、とりあえずは良しとする。以下に、その内容を"[4/29]"として追記する。
(4/30 9:42追記) 昨夜(4/29)、上に書いた「気に入らないこと(モノラルの処理)」もできた。Gstreamerの動作が調べたことと違うので、やっぱり苦労した。他に、特定アプリの音に関する接続漏れが見つかったので、修正した。以下に、それらの内容を"[4/30]"として追記する。
GMBからJACKへの自動接続機能
- GMBで再生時、Gstreamer(再生用ライブラリ)の再生準備をした後(playbinというものの初期化後)、指定したJACKの接続先(入力ポート)に接続する。
- JACKへの接続にはjack_connectコマンドを使った。もしかしたら、Gstreamerの機能でも接続できるのかも知れない。
- [4/29] GstreamerのJACKモジュール、jackaudiosinkの自動接続機能でJACKに接続できるようになった。今までできなかったのは、仕様(使い方)を理解していなかったのと、JACKサーバに知らない機能があったためだった。
- jackaudiosinkのプロパティconnectを"auto"にし、port-patternに接続先を設定(正規表現。例: "jack_mixer:In .+")すれば自動的に接続される。
- JACKサーバのバージョンによっては、self connect(アプリが自分で接続する機能)が無効化されているために接続できない場合がある。その場合は、jack_controlコマンドでself connectモードを" "(self connect有効)に設定し、サーバを再起動する。
- 例: jack_control eps self-connect-mode " "
- self connectを有効にした時、PulseAudioサーバのバージョンによっては、pacmdでmodule-jack-sinkをロードした時に、自動的にデフォルト出力に接続される場合がある。その場合は、ロード時に自動接続を無効化すればいい。
- 例: pacmd load-module module-jack-sink connect=false
- ただし、私はJACKサーバをjackdbusで起動しており、その場合にはmodule-jack-sinkが自動でロードされるようで、上記の指定は無効なので、JACKサーバの起動後に自分で切断している。設定ファイルに書けばいいのだろうが、JACKの開始スクリプトに書いた方が確実と考えた。
- 曲をスキップさせたり再生ゲインのモードを変更した時などにはplaybinが初期化されるので、再度接続する。
- [4/29] jackaudiosinkの機能で接続する場合には不要になった。
- 理由は分からないのだが、playbinの初期化直後は接続に失敗するので、その場合は一定時間後にリトライするようにした。
- たまたま、GMBには500msの周期処理(UpdateTime)があることに気付いたので、その中にリトライを追加した。
- [4/29] jackaudiosinkの機能で接続する場合には、自分で接続しないので失敗することもなく、リトライは不要になった。
- 接続済みの場合に再度接続しようとすると、無駄な処理の繰り返しになって負荷が増えるので、接続しようとする前に接続済みかどうかを調べるようにした。
- JACKの接続確認にはjack_lspコマンドを使う自作のコマンドを使った。
- [4/29] jackaudiosinkの機能で接続する場合には、自分で接続しないので不要になった。
- 接続先ポートはGUIで設定できるようにすべきだが、頻繁に変える訳ではないので、今はプログラムの中に記述している。
GMBでのモノ→ステレオ変換
- 上記のJACKへの自動接続時に、再生しようとしている曲がモノラル(チャネル数=1)だった場合、最初のチャネル(左)をJACKの右チャネル入力にも重複接続する。
- [4/29] Gstreamerの信号処理機能を使えば、1チャネルを2チャネルにすることも可能そうだが、方法が分からなかったので、ミキサーにモノラル入力を追加し、モノラルの場合には、出力先をそこにすることにした。
- 試しにモノラル入力に音を入れてみたら左右両方に出力されることが分かったので、こうした。
- [4/30] Gstreamerの信号処理機能を使い、1チャネルを2チャネルにするようにした。
- GMBのイコライザの処理を参考にして、Gstreamerの「パイプライン」というものを追加した。
- 調べたところでは、"audioconvert ! audio/x-raw,channels=2"(チャネルを2つに増やす)というパイプラインでできるはずなのだが、どうしてもエラーになってしまう。また、"audioconvert mix-matrix='<<(float)1.0, (float)0.0>, <(float)1.0, (float)0.0>>'"(左チャネルの音を右チャネルにも出す)も駄目だった。試行錯誤の結果、"audiochannelmix left-to-right=1 right-to-right=0 ! audioconvert"(左チャネルの音を右チャネルにも出す)は成功したので、これを使うことにした。
- 曲がステレオ(チャネル数>1)に戻った場合は、右チャネルへの重複接続※を切断し、通常の接続を行う。
- JACK接続の切断にはjack_disconnectコマンドを使った。
- [4/29] ※「右チャネルへの重複接続」は「モノラル入力への接続」と読み替える。
- [4/29] jackaudiosinkの機能で接続する場合には明示的な切断手順がないため、一旦、playbinの状態を初期化してから設定すことにし直した。以下に手順を示す。
- playbinをset_state("null")で初期状態にする。
- 同様に、Ready状態にする。
- 出力先を変更する(port-patternを設定)。
- 1と同様に、Paused状態、Playing状態にする。→ 再生が再開する。
- [4/30] GMBからは常に2チャネルが出力されるようになったので、重複接続やモノラル入力への接続切り替えは不要になった。
- この時、通常の接続を接続する前に重複接続を切断すると、一瞬音が切れるように聞こえるので、通常の、右から右への接続を行ってから、重複接続(左から右)を切断するようにした。
- こうすると、一瞬音が混じるはずだが、音が切れるよりはマシなので、こうした。実際には混じって聞こえる感じはしないので、一種の錯覚なのだろう。
- [4/29] jackaudiosinkの機能で接続する場合には音が途切れないので、不要になった。
特定のアプリの音を特定の出力だけに出す。
- PulseAudioに、新しい出力先を作る。
- 新しい出力先を作るには、pacmdコマンドでmodule-jack-sinkをロードした(pactlでもできるはず)。
- 例: pacmd load-module module-jack-sink channels=2 client_name=PA_alt_jack_sink sink_name=PA_alt_jack_sink
- 対象のアプリの音をその新しい出力に出すようにする。
- アプリの音の出力先は、音量調節アプリ(pavucontrol)で設定すれば、それが記憶されて、以降は自動で出力されるようになるが、プログラムで明示的に接続した方が、後日、設定したことを忘れて困ることがないと思うので、次のようにした。
- pactl subscribeコマンドでPulseAudioのイベントを監視し、対象のクライアントが接続されたら、pactl move-sink-inputコマンドで、対象のクライアントの出力を新しい出力に接続する。
- subscribeではクライアントIDしか出力されないので、pactl list sink-inputsコマンドでクライアント名を取得して対象のクライアントかどうかを判定し、同時に得られるクライアントの出力(sink-input)IDをmove-sink-inputに指定する。
- なお、pactlコマンドもクライアントとして認識されるので、上記処理でpactlを実行するたびに新規クライアントのイベントが生成されて無限ループになってしまうので、pactlコマンドの実行後は新規クライアントのイベントを1個読み捨てるようにした。そのため、タイミングによっては本当の新規クライアントイベントが破棄される可能性があるが、起こる確率が低いので、対処はしていない。
- アプリの音の出力先は、音量調節アプリ(pavucontrol)で設定すれば、それが記憶されて、以降は自動で出力されるようになるが、プログラムで明示的に接続した方が、後日、設定したことを忘れて困ることがないと思うので、次のようにした。
以下にJACKの接続例とミキサーの図を示す。
- 通常(ステレオ)再生時
- モノラル再生時: 1チャネルを重複接続
- JACKのミキサー (特定アプリで再生中)
左図で、左中央の"gmusicbrowser"がGMBの出力で、上記の自動接続機能によって中央のミキサーの入力(jack_mixerのIN L,R)に自動接続される。モノラル時には、上記の変換機能によって、中央図のように、GMBの1つの出力が重複してL,Rチャネルに自動接続されて、両方のスピーカーから音が出るようになる。
[4/29] 改良後のJACKの接続例とミキサーの図を示す。
- 通常(ステレオ)再生時の接続
- モノラル再生時: 1チャネルをモノ入力に接続
- 通常(ステレオ)再生時のミキサー
- モノラル再生時のミキサー
[4/29] モノラル時には、上段右図のように、GMBの出力がミキサーのモノラル入力(jack_mixerのMono_In)に入り、ミキサーによってL,Rチャネルに出力されるので、両方のスピーカーから音が出るようになる。
[4/30] モノラル時も左右チャネルから音が出るようになったので、ミキサーのモノラル入力への接続は行わなくなった。そのため、モノラル時の接続状態は上の「通常(ステレオ)再生時」と同じである。
特定アプリの音は、PulseAudioから図左下のPA_alt_jack_sinkに出力され、ミキサーの入力(jack_mixerのAlt_In L,R)に接続されている(この接続は勝手に切れないので、静的な設定にしている)。ミキサーでは、この音(図の中央列: Alt_In)をミュートしている(図のMボタンがミュート)ので、通常の出力(MAIN L,R)からは出ないのでスピーカーからは出ず、特定の出力(Realtek_aout)だけに出力される。ミュートを解除すれば、通常の出力にも流れて、イコライザ(jack_rack)を経由してJACKの標準の出力(system)に出て、スピーカーからも出すこともできる。
[4/30] 特定アプリ以外の音(通常の音)をヘッドフォンに出すことを忘れていたので、接続を追加した(jack_mixer: In Out L/R → Realtek_aout: playback_1/2)。
まあ、オーディオシステムは、あくまでも音楽を聴くのが目的であって、こういう作業はまったく本質ではない、自己満足とか慰みの領域ではあるのだが、自分で構築したシステム(暇さえあれば、自分のアイデアで改良できる)で音楽を聴けるのは、なかなか楽しい。そして、こんなことはWindowsやMacではまず無理(可能ではあるだろうが、手軽にはできない)だろうから、Linux+JACKにして良かったと思う。とはいえ、実は、こここに書いたことだけなら、WindowsやMacではやる必要のない作業なのかも知れないw
が、それにしても、こういう作業をすると、今まで知らなかった・やったことのないことを見つけたり気付いたりすることがあって、技術者としてはおもしろいし、いつか仕事でも使えるかも知れないから、一石三鳥だw まじめな話、Windowsだと、基本機能になければ(その基本機能もコロコロ変わる)、他人が作ったアプリを探して使うしかない(ものすごく時間を掛けて、自分で作れば別)から、おもしろくないしつぶしが効かない。あ、Mac?、あんなものは林檎社がUNIXを腐らせしまっているので、全く論外ですw
PS. 今の録音エンジニアなどの方には当たり前なのだろうが、今回のように、「接続を忘れてたから追加する」とかいうのがマウス1個wでできるのは、JACKの大きなメリットのように思う。昔(僕が中高大の頃)は、本物のケーブルで接続しなければならなかったから、端子やケーブルや機器が足りなければ接続できなかったし、端子の形状が違っても駄目だったし、信号レベルやインピーダンスも考慮すべきだったし、並列・直列接続し過ぎると音質が劣化するし、ミキサーを介さずに複数の出力を同じ入力に繋げるのは電気・音質的に良くないことだった。
今はそんなことを全く意識する必要がなく、自由に接続できるので、すごく便利だ。機器にしたって、「ミキサー(あるいはミキサーのチャネル)がない・足りないからから増やそう」と思っても、お金を工面して買いに行くw必要などなく、即座に無料で増やせる。もちろん、PulseAudioやALSAでもそういう変更は可能だが、GUIではできず、設定ファイルを書き換える必要があるから、「ちょっとやってみよう」という気にはならない。
そういうことがあるから、いろいろな手間や欠点があったにも関わらず、JACKに乗り換えようと思ったのかも知れない。 (4/29 10:34)
(4/29 5:02 加筆・修正; 13:58 改良した内容を追記; 4/30 10:16 4/29夜の改良・修正点を追記、その他、若干修正; 4/30 10:37 題を変更; 4/30 17:59 若干修正)