(DIY) Raspberry Pi 3 でBluetoothオーディオレシーバを作る

Raspberry Pi で無線スピーカーシステムを作りました。

Bluetooth A2DP で音楽を受信するので、Bluetoothスピーカーのように振る舞います。さらに、長いので別記事にする予定ですが、Apple製品から AirPlayで接続できるようにしました。

このシステムは、家のリビングに置いています。

プロジェクタから無線で音を飛ばしたり、iPhoneから音楽をかけたりするために欲しいなと思ったので作りました。

構成

Google で探すと、同じようなことをやっている人の情報が多く見つかりますが、仕様が少しずつ違うので、自分のやり方を模索する必要があり苦労しました。

デバイス接続図

私の場合、以下のようなデバイスを使いました。

  • Raspberry Pi 3
  • OS: Ubuntu 18.04 LTS Server arm64
  • USBサウンドデバイス: Creative
  • Bluetoothトランスミッタ: AUKEY BT-C1

プロジェクターの音質の面からすると、今回の構成はあまり良くないことは確かです。DA変換された信号が、さらにAD変換されて、コーデックで圧縮されて、またDA変換されることになるからです。Bluetoothはアナログラジオではないので、空中を飛んでいる最中に音質が劣化するということはありません。あるのはただコーデックによる非可逆圧縮による損失とか、電波が届かないことによるパケットロスです。音質を求めたときに一般的なやり方は、サウンドシステムでHDMIをパススルーする方法です。

今回は、そんなに上等なシアターを作るつもりは無いし(場所も無いし)、スピーカーも上等なものではないので、目をつぶります。いろんなデバイスから気軽にアクセスできる据え置きスピーカーにしたいというわけ。

Bluetooth

Bluetooth の基本的なことをおさらいしておきます。Bluetooth は、狭い範囲で無線通信するための規格です。Bluetooth 自体は様々なデータをやり取りすることができますが、デバイスの種類ごとにプロトコルが定められています。そのプロトコルを「プロファイル」と呼びます。今回はオーディオデータをやり取りしたいので、A2DP(Advanced Audio Distribution Profile)を使います。一般的なBluetoothイヤホンなんかもA2DPを実装しています。

A2DP はオーディオデータを転送するのですが、このとき、データは圧縮されて転送されます。A2DP では、コーデックを自由に選ぶことができます。接続時にハンドシェイクが行われて、送信側・受信側の両方が対応しているコーデックで実際の転送が行われます。すべての A2DP デバイスは、最低でも SBC(SubBand Codec)の実装が要求されているので、最悪の場合でも SBC で送受信することになります。コーデックによって、可逆圧縮・非可逆圧縮・音質とか、遅延時間とか、バッテリー消費量とかに違いが出ます。代表的なコーデックには以下のものがあります。

  • SBC : 低品質・高遅延
  • AAC : Apple 製品でよく使われている。iOS で再生する場合はこれ
  • aptX : クアルコムのコーデック。Androidでよく使われている。低遅延版の aptX LL がある
  • LDAC : Sony のコーデック。ハイレゾ音源対応

こういったコーデックの中から、使いたい、かつ、使えるコーデックを RPi 上の Ubuntu にインストールしていくことになります。

ソフトウェア

Bluetoothまわりのソフトウェア構成は下記のようにします。これをRPi上のUbuntu上にインストールしていくことになります。

ソフトウェア構成図

Linux で Bluetooth を扱う標準的なソフトウェアが bluez です。bluez では、オーディオの送信機能のことを Audio Source と呼び、受信機能のことを Audio Sink と呼びます。bluez は dBus を使ってプロセス間通信をしていて、Bluetoothプロファイルの実際の処理は、dBus上に接続した別のプログラムが担うことになります。

同じような RPi でオーディオレシーバを作る情報を探すと、PulseAudio を使っているレシピが多く見つかります。それは、pulseaudio-module-bluetoothパッケージを使って PulseAudio と bluez との橋渡しを行って、PulseAudio から音を鳴らすという構成です。今回は PulseAudio を使いたくないので、それ関連のパッケージは一切使いません。代わりに、オープンソースの bluez-alsa を使います。これはその名の通り、bluez とコーデックと alsa との橋渡しをしてくれるもので、直接 ALSA で音を鳴らしてくれるというわけです。bluez-alsa 自体は送受信どちらも可能なソフトウェアです。bluez-alsa はライブラリではなく単体のプロセスとして走らせます。前に述べた通り、bluezが直接A2DPを処理するのではなく、dBus を介して bluez-alsa のプロセスがプロファイルを処理するというわけ。

Ubuntuのセットアップ

https://blog.itoudium.tokyo/post/raspberry-pi-setup/

Bluetoothのセットアップ

まずは、bluetoothコントローラを制御できる状態を目指します。Ubuntuのbluetoothパッケージをインストールします。

apt install bluetooth

このbluetoothパッケージは、前に述べたbluezに依存しているので、これだけでbluezまでインストールされます。これで、Bluetoothコントローラを制御するコマンドラインツール bluetoothctlや、Systemdサービスの bluetooth が一応使えます。

そのままの状態では、RPi内蔵のBluetoothコントローラを使うことができません。以下のPPAを追加します。

https://launchpad.net/~ubuntu-pi-flavour-makers/+archive/ubuntu/ppa

PPAを追加できたら、以下のパッケージをインストール

apt install pi-bluetooth

おそらく再起動が必要です。

これで、bluetoothctlコマンドからRPiのBluetoothを制御できるようになりました。

bluetoothctlコマンドは対話式インターフェースです。詳しくはマニュアルを見てください。うまくいっていれば、以下のコマンドでコントローラの詳細が出ます。

echo show | sudo bluetoothctl

(通常は対話式インターフェースで使うほうが楽です。標準入力からコマンドを受け取ることもできる)

bluez-alsa をセットアップ

bluez-alsa は、導入が少しハードです。ソースコードからビルドする必要があります。依存パッケージが大量にあるので、都度調べてインストールします。また、パッケージの内容が変わっていくこともあるので、すべて”現時点での”情報であることを強調しておきます。

bluez-alsaとその依存パッケージは、自分でソースコードからビルドする必要があります。bluez-alsaは以下のリポジトリにあります。

https://github.com/Arkq/bluez-alsa

bluez-alsaをビルドする前に、依存ライブラリを入れていきます。まず、aacを使いたいのでfdk-aacをビルド・インストールします。

https://github.com/mstorsjo/fdk-aac

次に、apt-Xコーデックを入れますが、注意点があります。

bluez-alsaはapt-Xでの受信(audio sink)には非対応です。openaptxを入れても、できるのは送信(エンコード)だけです。

https://github.com/Arkq/bluez-alsa/issues/142

apt-Xでの受信に対応するには、以下のスレッドの内容が一番簡単です。要約すると、bluez-alsaを、ffmpegからコピーしたapt-Xデコーダに対応させるというものです。

https://github.com/Arkq/bluez-alsa/pull/253

上記スレッドでも議論されていますが、おそらく、このコードがbluez-alsaのメインストリームにマージされることは無いでしょう。

t123yh氏の ffaptx をビルド・インストールします。これをビルドするには、CMake 3.14 以上が必要なのだが、それのRPi用バイナリが見つからない。なので、まずCMakeの新しいバージョンをソースコードからビルドする必要があります。(苦行。)それができたらいよいよffaptxです。

https://github.com/t123yh/ffaptx

次に、本家のbluez-alsaではなく、t123yh氏のbluez-alsaをビルド・インストールします。

https://github.com/t123yh/bluez-alsa

私は以下のようなオプションでビルドしました。

../configure --enable-aac --enable-debug --enable-aptx-sink

bluez-alsaを試しに動かす

ここまで出来れば、bluealsaコマンドと bluealsa-aplayコマンドが使えるようになっているはずです。Audio Sinkとして音を流すには、この2つのプロセスを立ち上げておく必要があります。サービス化する前に、普通にフォアグラウンドで動かして動作確認するのが良いです。

bluealsaでAudio Sinkを有効にするためには、パラメータが必要です。

bluealsa -p a2dp-sink

bluealsa-aplayは以下のようなコマンドで立ち上げます。

bluealsa-aplay -vv 00:00:00:00:00:00

確認のために、bluetoothctlshowコマンドを打ってみます。うまくいっていれば、コントローラの詳細に Audio Sink と表示されます。これで A2DPで受信できるようになりました。

# echo show | sudo bluetoothctl | grep "Audio Sink"
	UUID: Audio Sink                (0000110b-0000-1000-8000-00805f9b34fb)

初期状態では、ほかのデバイスのBluetoothからRPiが見つからないと思います。bluetoothctlで以下のコマンドを使うと検索できるようになります。

discoverable on

iPhoneのBluetooth画面から検索して、接続を試みます。そのままだと接続拒否されますが、bluetoothctlのほうにiPhoneのBluetoothのアドレスが表示されます。以下のコマンドでペアリングします。

trust 信頼したい相手のBluetoothコントローラのアドレス

ペアリングが完了されると、discoverableは自動的にoffに戻ります。

うまくいけば、RPiのスピーカから音が出るはずです。

この段階ではまだBluetoothトランスミッタからはつながりません。また、BluetoothスピーカーではなくRPiのイヤホンジャックから音が出ると思います。

bluez-alsa の遅延を減らす

そのままでは、bluez-alsaはかなり遅延します。bluealsa-aplayのパラメータを変えることで遅延を減らすことができます。以下のスレッドを参考にしました。

https://github.com/Arkq/bluez-alsa/issues/156

私のパラメータは以下のようにしました

bluealsa-aplay --pcm-buffer-time=70000 --pcm-period-time=17505 -vv 00:00:00:00:00:00

bluez-alsa をサービス化する

bluez-alsaをSystemdサービスにします。以下の2つのファイルを作ります。

# /etc/systemd/system/bluealsa.service
[Unit]
Description = bluealsa

[Service]
ExecStart = /usr/bin/bluealsa -p a2dp-sink
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target
# /etc/systemd/system/aplay.service
[Unit]
Description=BlueALSA aplay service
After=bluetooth.service
Requires=bluetooth.service

[Service]
ExecStart=/usr/bin/bluealsa-aplay --pcm-buffer-time=70000 --pcm-period-time=17505 -vv 00:00:00:00:00:00

[Install]
WantedBy=multi-user.target

これで、Systemd(systemctlコマンド)で制御できるようになりました。サービス名はそれぞれ、bluealsaaplayです。

Bluezの設定を調整する

Bluezのグローバルな設定ファイルは /etc/bluetooth/main.confにあります。これをいじっていきます。

device class

まず、Class = の項目を変えます。

Bluetoothには、プロファイルのほかに、そのデバイスの種類が何なのかを開示するためのClassという属性があります。私は今回Bluetoothトランスミッタから接続したかったのですが、製品によっては、特定のClassを持つものにだけ接続しにいく挙動になっているようです。これは製品次第だと思います。

Classはビットフラグになっていて、以下のサイトで計算できます。

http://bluetooth-pentest.narod.ru/software/bluetooth_class_of_device-service_generator.html

Bluetoothトランスミッタから見たらまるでスピーカーに見えるかのように振る舞う必要があります。私は以下のようなClassにしました。

Class = 0x20041C

discoverable timeout

次にDiscoverableTimeoutを設定します。自動的にdiscoverableが無効になる挙動をやめたいので、以下のように設定しました。

DiscoverableTimeout = 0

これで、常に検索可能な状態です。(ただしペアリングは必要なので、勝手に使われる心配は無い。)

ALSAの設定を調整する

RPiはイヤホンジャックを持っていますが、これはノイズがひどくて使い物にならないので、USBオーディオを使うことにします。たいていのUSBオーディオデバイスは、標準的なデバイスドライバで扱うことができます。

以下のコマンドで、オーディオデバイスを一覧表示することができます。

sudo aplay -l

card番号とdevice番号を控えておきます。

以下のファイルに ALSAの設定を記述していきます。無ければ作ります。

# /etc/asound.conf

pcm.!default {
    type hw
    card 1
}

ctl.!default {
    type hw
    card 1
}

これは、デフォルトで card1のdevice0を使うことを意味します。

ALSAは、以下の場所に大元となる設定ファイルがあります。

/usr/share/alsa/alsa.conf

/etc/asound.confは上記ファイルからインクルードされるようになっています。また、ユーザのホームディレクトリに .asoundrcファイルを書くこともできるのですが、今回はヘッドレス運用をするため、使いません。

設定できたら、以下のコマンドで音量調整・兼・どのサウンドデバイスがデフォルトになっているか確認できます。

alsamixer

ALSA自体は、ソフトウェアミキサを構築したり、イコライザを挟んだりすることもできるのですが、bluez-alsaの制約で、bluealsa-aplayが物理的なサウンドデバイスにしか出力できないようになっているとのこと。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です