2017年3月16日木曜日

AndroidでWindowsの音声だけを(概ね)遅滞なく再生させたい。(1)

本当にくだらないことをやってみましたので、顛末を記録して長く恥をさらすことにしました。

自分で操作中のPCの音をAndroidに飛ばしたい。無線LANで。なるべく遅滞なく。

今回はすべて余談(というかffmpeg利用編)です。

さて、なぜこのようなことをしようと思ったのかはクSonyのBluetoothレシーバです。
有機ELディスプレイがはるか昔に死んでてペアリングがだるいのです。
画面が見えないから。

画像付きでいいならリモートデスクトップがあるので意味がございません。
ただ、リモートデスクトップだと、同一ユーザでPCを操作しながら音声だけ別計算機から出力するというわけにも参りませんので今回の要件としてはパスです。

こないだ連打病から帰ってきた(ついでに基盤交換までされてしまって再ペアリングが必要になってしまっていた)XperiaAに見えないままどうにか再ペアリングを果たしたので、こいつがペアリングされているAndorid機にPCの音声を飛ばせばBluetooth経由で再ペアリングなしで使いまわせるぞ!という目論見です。

なんという恥知らずな目論見でしょうか。
レシーバなんか数千円なんだからもう一つ買えばいいのに、と理性は申します。

ですが、使えるとなると骨の髄まで啜ってやるのが供養というものではないかと情に流されてしまうのです。

などと意味不明な言い訳をしつつ(ケチ)、まず試したのは既存のアプリを使用してPCの音声をAndroidに送って再生させるというものです。

この試みに選択したのはffmpeg+vlcの組み合わせです。
ffmpegはPC上の音をミキサを経由してDirectShow(以下ffmpeg方言式にdshowと表記します)で録音して、それをtcp,udp,rtpなど様々な形式で単体で送信することができます。
vlcはそれらすべてを受信することができますし、加えて、Androidはもちろん、様々なOSにポーティングされているのでOS間の比較・検証がやりやすいのでやってみました。

まず何も考えずにdshow経由でvlcにudpのポート番号1234にマルチキャスト送信するコマンドは次の通りです。
ffmpeg  -f dshow -i audio="ステレオ ミキサー”  -f mpegts  udp://224.0.0.1:1234
なお、入力元(-i audio=)に使えるdshowデバイスの一覧は次のコマンドで表示することができます。表示されたお使いの計算機の "DirectShow audio devices" の欄からチョイスしてください。
ffmpeg -f dshow -list_devices true -i NUL
この例ではブロードキャストアドレスをサンプルにしていますがユニキャストでもどっちも同じで、遅延は5秒以上、しかも(マルチキャストなので同時受信していた別PCでは取りこぼしはなかったのですが)Android相手だと2台ある両者とも音の取りこぼしがひどいありさまでとても使い物になりませんでした。

rtpも同様です。rtpもudpに乗っかってるのが原因と思われます。まあ、udpってそういうもんですからいいっちゃいいんですが、近所に誰も基地局を設置していない5GHz帯でもこのありさまなのは少々失望の念を禁じえません。

vlc側の設定はudpの場合のみudp://@224.0.0.1:1234のようにアットマークが必要なのでご留意ください(ユニキャストも同様です。ユニキャストの場合は送信元のffmpegに指定するuriは送信先計算機宛、vlcで指定するのは自計算機のアドレスとなります)。

ま、さすがにudpやrtpはudpで送ってるんだからパケットがetherの藻屑に消えるのはプロトコル上の仕様ですから、今度はtcpでやってみます。

tcpはマルチキャストという芸当が使えませんから(当たり前だわな)一対一で通信しますのでどっちかが待ち受けて、どっちかが接続しにいくことになります。
Android側で待ち受けさせてからPC側で接続させるというのも運用上極めて問題があることは自明なので、PC側で待ち受けして、Android側が気が向いたときに接続してくるようにできるとすると、次のようなコマンドになります。
ffmpeg -f dshow -i audio="ステレオ ミキサー" -f mpegts tcp://192.168.0.1:1234?listen
この場合の192.168.0.1というのが送信元のPC機のIPアドレスです。uriの後ろに?listenがつくのが味噌です。
vlc側はtcp://192.168.0.1:1234と指定すると、ffmpegに接続に来ます。?listenを抜けば逆になります。

これだと、確かに音の抜けがなくなりましたが、やはり遅延がすごい。すごいけど、音飛びがなくなったので遅延のほうに目を向けてみたいと思います。

やはり、仕組み上どうしようもない要件としてdshow経由だと
再生デバイス鳴動->録音->tcp|udp送信
という手順を踏むことになり、特に録音段階で録音時間を100msとするなら100ms、確実に遅延します。なんせ録音してるんですからその間どうしようもない。

ですが、5~7秒とか現状ではいくらなんでもおかしいので、ffmpegのオプションを見直してみてみますと、以下のようになりました。
ffmpeg  -f dshow -i audio="ステレオ ミキサー"  -audio_buffer_size 10 -probesize 32 -analyzeduration 0 -start_time_realtime -10 -f mpegts tcp://224.0.0.1:1234?listen
ffmpegはエンコーディング目的なソフトなのでなるべく読み込んでから一気に処理して結果を効率よく出そう、という設計方針なので、それに敢えて逆らってとにかくバッファリングを極力減らしてみました。

各オプションの詳細はffmpegのマニュアルをご覧いただくとして、udpだと遅延が1秒未満と、結構いい線いくのですが、Android機のudp受信状況が思わしくない。
結局tcpにせざるを得ないわけですが、どういうわけかtcpのほうが遅延時間が伸びるのです。

いろいろ試してみると、どうもffmpegはconnect前のデータから順に送信しようとするらしく、接続した時点からの音声を送るということができないようで、それが遅延に見えるという事のようでした。
確かにffmpegの準備が整ったらすかさずvlcから接続すると遅延時間が短くなります。

・・・なるほど。

こうなったら自前でキャプチャした音声をandroidに送って再生させたらどうなるんだろう?!

どうでもいい深みに入ってまいりました。
次回から本題です。

0 件のコメント:

コメントを投稿