🎙️

音声コーデックLyraをビルドして試してみた

2022/12/11に公開

先日の時雨堂さんのオンラインイベントで、新しい音声コーデック Lyra によるWebRTCのリアルタイム通信を体験させていただきました。音質は高いわけではないが他のWeb会議システムと同じくらいで、音声通信のビットレートのグラフをみるとあまりの低さに驚きました。
それで興味を持ったのでLyraを自分でも試してみることにしました。

Lyra コーデック

https://opensource.googleblog.com/2022/09/lyra-v2-a-better-faster-and-more-versatile-speech-codec.html

Google が開発したspeech(通話)用のコーデックです。機械学習を応用することで今までにないほどの低ビットレートを実現しています。
TensorFlow Lite を使用しています。
https://www.tensorflow.org/lite

ビルド

ビルドするにはx86_64のLinuxを使いました。Ubuntu 22.04
arm64のLinuxではビルドできません。またメモリが1GBしかない無料の仮想マシンでもビルドできませんでした。
今回使ったのは古いMacbook Air 2011年モデルにUbuntu 22.04 デスクトップをインストールしたものです。CPU4コア。メモリ4GBです。

bazel のインストール

$ sudo apt install apt-transport-https curl gnupg
$ curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor >bazel-archive-keyring.gpg
$ sudo mv bazel-archive-keyring.gpg /usr/share/keyrings
$ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/bazel-archive-keyring.gpg] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
$ sudo apt update && sudo apt install bazel

numpy のインストール

$ sudo apt install python3-numpy

ソースコードの取得

$ git clone https://github.com/google/lyra.git
$ cd lyra

最新のタグを確認して、それをcheckoutする

$ git tag
$ git checkout -b w1.3.1 v1.3.1

ビルド

$ bazel build -j 1 -c opt lyra/cli_example:encoder_main
$ bazel build -j 1 -c opt lyra/cli_example:decoder_main

ビルドに使ったのが4GBしかメモリがないマシンだったのでコンパイラを並列で動かすとメモリ不足でハングアップしてしまいました。それで -j 1 オプションをつけてジョブの並列度を1に固定しました。bazel はデフォルトでは論理CPUの個数分のジョブを起動するようになっています。

インストール

実行に必要なファイルだけを ~/lyra-v1.3.1ディレクトリにコピーしました。

$ mkdir ~/lyra-v1.3.1
$ install -s bazel-bin/lyra/cli_example/encoder_main bazel-bin/lyra/cli_example/decoder_main ~/lyra-v1.3.1/
$ mkdir ~/lyra-v1.3.1/lyra
$ cp -r lyra/model_coeffs ~/lyra-v1.3.1/lyra/
$ cd ~/lyra-v1.3.1/

サイズはこんな感じ。

$ size encoder_main decoder_main 
   text	   data	    bss	    dec	    hex	filename
5317982	  40776	  80568	5439326	 52ff5e	encoder_main
5400289	  41764	  80888	5522941	 5445fd	decoder_main
$ ls -lRh
.:
total 11M
-rwxr-xr-x 1 koba koba 5.2M Dec 11 11:41 decoder_main
-rwxr-xr-x 1 koba koba 5.2M Dec 11 11:41 encoder_main
drwxrwxr-x 3 koba koba 4.0K Dec 11 11:42 lyra

./lyra:
total 4.0K
drwxrwxr-x 2 koba koba 4.0K Dec 11 11:42 model_coeffs

./lyra/model_coeffs:
total 3.5M
-rw-rw-r-- 1 koba koba    2 Dec 11 11:42 lyra_config.binarypb
-rw-rw-r-- 1 koba koba 1.5M Dec 11 11:42 lyragan.tflite
-rw-rw-r-- 1 koba koba 322K Dec 11 11:42 quantizer.tflite
-rw-rw-r-- 1 koba koba 1.7M Dec 11 11:42 soundstream_encoder.tflite
-rw-rw-r-- 1 koba koba   44 Dec 11 11:42 test_playback.wav

実行ファイルがそれぞれ約5MB、モデルのデータの合計が約3.5MBですね。

依存している共有ライブラリは基本的なものだけです。

$ ldd ./encoder_main 
	linux-vdso.so.1 (0x00007fff2fab1000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f9ec7597000)
	libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f9ec736d000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f9ec734d000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9ec7125000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f9ec7bc3000)
$ ldd ./decoder_main 
	linux-vdso.so.1 (0x00007ffe3c2b3000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fc1f6d6a000)
	libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fc1f6b40000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fc1f6b20000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc1f68f8000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fc1f73aa000)

動かしてみる

テストデータの作り方

$ arecord -vv -c 1 -r 16000 -f S16_LE -d 30 a1.wav

マイクに向かってなんか話して、-d で指定した秒数経過すると止まる。
これでサンプリングレート16kHz モノラル、符号付き16bitリトルエンディアンのwavが作れる。
-vv をつけるとVUメーターが表示される。

テストデータの再生

$ aplay -vv a1.wav

エンコードして、それをデコードする

$ ./encoder_main --input_path ./testdata/a1.wav --output_dir ./testdata/ --bitrate 3200
INFO: Created TensorFlow Lite XNNPACK delegate for CPU.
WARNING: Logging before InitGoogleLogging() is written to STDERR
I20221211 12:18:31.726536 29974 encoder_main_lib.cc:91] Elapsed seconds : 1
I20221211 12:18:31.726647 29974 encoder_main_lib.cc:92] Samples per second : 291669
$ ./decoder_main --encoded_path ./testdata/a1.lyra --output_dir ./testdata/ --bitrate 3200
INFO: Created TensorFlow Lite XNNPACK delegate for CPU.
WARNING: Logging before InitGoogleLogging() is written to STDERR
I20221211 12:20:01.729132 29986 decoder_main_lib.cc:138] Elapsed seconds : 0
I20221211 12:20:01.729241 29986 decoder_main_lib.cc:139] Samples per second : 522935

bitrateで選択できるのは3200, 6000, 9200の3種類。
デコードするときにはエンコード時のbitrateを指定する必要がある。そうでないと正しくデコードできない。(つまり.lyraファイルにはビットレートの情報が含まれていない)
30秒分のデータのエンコードにかかった時間は1秒で、デコードは1秒未満。リアルタイム処理をするのに十分な速さです。ビットレートを変えてもこの傾向は同じでした。

4つの30秒のテストデータを作り、それぞれ3種類のビットレートでエンコード、デコードしてみました。

  • ひたすら話し続けるもの
  • 時々話を休止するもの(会話をしているときの片方を想定)
  • ボーカルなしの楽曲
  • ボーカル付きの楽曲
$ ls -lR testdata/
testdata/:
total 3772
-rw-r--r-- 1 koba koba 960044 Dec 11 12:10 a1.wav
-rw-r--r-- 1 koba koba 960044 Dec 11 12:10 a2.wav
-rw-r--r-- 1 koba koba 960044 Dec 11 12:13 a3.wav
-rw-r--r-- 1 koba koba 960044 Dec 11 12:14 a4.wav
drwxrwxr-x 2 koba koba   4096 Dec 11 12:32 br3200
drwxrwxr-x 2 koba koba   4096 Dec 11 12:36 br6000
drwxrwxr-x 2 koba koba   4096 Dec 11 12:44 br9200

testdata/br3200:
total 3808
-rw-rw-r-- 1 koba koba  12000 Dec 11 12:18 a1.lyra
-rw-rw-r-- 1 koba koba 960044 Dec 11 12:20 a1_decoded.wav
-rw-rw-r-- 1 koba koba  12000 Dec 11 12:22 a2.lyra
-rw-rw-r-- 1 koba koba 960044 Dec 11 12:22 a2_decoded.wav
-rw-rw-r-- 1 koba koba  12000 Dec 11 12:23 a3.lyra
-rw-rw-r-- 1 koba koba 960044 Dec 11 12:23 a3_decoded.wav
-rw-rw-r-- 1 koba koba  12000 Dec 11 12:23 a4.lyra
-rw-rw-r-- 1 koba koba 960044 Dec 11 12:23 a4_decoded.wav

testdata/br6000:
total 3856
-rw-rw-r-- 1 koba koba  22500 Dec 11 12:35 a1.lyra
-rw-rw-r-- 1 koba koba 960044 Dec 11 12:35 a1_decoded.wav
-rw-rw-r-- 1 koba koba  22500 Dec 11 12:35 a2.lyra
-rw-rw-r-- 1 koba koba 960044 Dec 11 12:35 a2_decoded.wav
-rw-rw-r-- 1 koba koba  22500 Dec 11 12:36 a3.lyra
-rw-rw-r-- 1 koba koba 960044 Dec 11 12:36 a3_decoded.wav
-rw-rw-r-- 1 koba koba  22500 Dec 11 12:36 a4.lyra
-rw-rw-r-- 1 koba koba 960044 Dec 11 12:36 a4_decoded.wav

testdata/br9200:
total 3904
-rw-rw-r-- 1 koba koba  34500 Dec 11 12:44 a1.lyra
-rw-rw-r-- 1 koba koba 960044 Dec 11 12:44 a1_decoded.wav
-rw-rw-r-- 1 koba koba  34500 Dec 11 12:43 a2.lyra
-rw-rw-r-- 1 koba koba 960044 Dec 11 12:43 a2_decoded.wav
-rw-rw-r-- 1 koba koba  34500 Dec 11 12:43 a3.lyra
-rw-rw-r-- 1 koba koba 960044 Dec 11 12:43 a3_decoded.wav
-rw-rw-r-- 1 koba koba  34500 Dec 11 12:42 a4.lyra
-rw-rw-r-- 1 koba koba 960044 Dec 11 12:42 a4_decoded.wav

4種類の性質の異なる音声データを処理したのですが、エンコードしたときのデータサイズが4つともきっちり同じでした。そしてこれらはそれぞれ指定したビットレートに正確に一致していました。

今までオーディオコーデックとしてMP3, AAC, OPUSをさわってきましたが、ビットレートの値は上限下限の範囲内で自由に指定できましたし、入力されるデータによってエンコード結果のサイズは変わっていたと思います。音楽を対象としていたので、サンプリングレート48kHz ステレオで128kbpsくらいが普通でした。
Lyraは通話専用でサンプリングレート16kHz モノラルとはいえ、ビットレートの指定が3.2kbps, 6kbps, 9.2kbpsの3通りなのは桁違いに小さいです。

デコードしたものを再生してみて、ビットレート 3.2kbpsは少しノイジーに感じました。6kbpsと9.2kbpsはそこまで違いを感じなかったので、6kbpsを使うのが良さそうです。

通話に特化したコーデックなので音楽を入れた結果は重厚感のないへなへなな音になりました。まったく音楽としては楽しめない音です。ボーカル曲だと伴奏がへなへなになりましたがボーカルの歌詞は聞き取れました。

ひとつのテストデータをマイクにしゃべって録音しているときに偶然スマフォからチーンという通知音がしました。それをLyraでエンコード、デコードして聞くとその通知音が完全に消えていました。人の声に含まれない成分は除去されるようです。

DTXを試す

DTX (Discontinuous Transmission) は無音のときにはデータを送らない(あるいは無音だったことを示すデータサイズ0のパケットを送る)ことで通信量を削減する機能です。
会話を想定したテストデータについて、--enable_dtx をつけてエンコードしてみました。

$ ./encoder_main --input_path ./testdata/a2.wav --enable_dtx --output_dir ./testdata/br6000dtx --bitrate 6000
$ ./decoder_main --encoded_path ./testdata/br6000dtx/a2.lyra --output_dir ./testdata/br6000dtx --bitrate 6000

その結果はこちら。

$ ls -l br6000dtx/
total 524
-rw-rw-r-- 1 koba koba  12255 Dec 11 15:39 a2.lyra
-rw-rw-r-- 1 koba koba 522924 Dec 11 15:39 a2_decoded.wav
$ ffprobe br6000dtx/a2_decoded.wav
  ...
Input #0, wav, from 'br6000dtx/a2_decoded.wav':
  Duration: 00:00:16.34, bitrate: 256 kb/s
  Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 16000 Hz, 1 channels, s16, 256 kb/s

無音の部分が切り詰められて、30秒分あったはずの音声が16.34秒に縮まっていました。
このサンプルプログラムはタイムスタンプを扱っていないのでこのようになったのだと思います。
wavのフォーマットはタイムスタンプ無しに単純にPCMデータが並んでいるだけなので。

リアルタイム通信でDTXを有効にする場合にはデコード側でタイムスタンプをみながら無音部分に無音(正確には環境音:comfort_noise)を埋める処理をする必要があります。
時雨堂さんではそれを含めて実装済みでした。

** 2022/12/12 追記 **
DTX有効時に受信側にデータが来ていないときにconfort_noiseを生成する機能はLyraのデコーダに備わっていました。しかしcli_exampleの実装ではタイムスタンプを考慮していないのでそれが発動しないだけのようでした。

参考

https://zenn.dev/voluntas/scraps/d1676e69045695
Lyraを体験したい方は時雨堂さんのSora laboでできます
https://zenn.dev/shiguredo/articles/shiguredo-webrtc-sfu-sora-lyra

Discussion