その後のその後

iOSエンジニア 堤 修一のブログ github.com/shu223

Core Bluetooth / BLEで音声データをやりとりする

以前書いた2つの記事でわかったことは、BLEは少量データ/低頻度での用途に特化することで省電力を実現した規格なので、音声データをやり取りするような用途にはそもそも向かない、ということでした。

ただ、MFiなし、WiFiもなしでデバイスとiOSアプリを無線通信させたい場合、iOSの現状の公開APIで可能な範囲ではやはりCoreBluetooth/BLE一択になってくるので、どうにかならないものかと。


そんなわけで、いろいろと調べたり聞いてみたり試行錯誤してみたことを書いておきます。


(2015.8追記)この記事は古く、書いた当時はBLEについての知識も乏しかったので内容には多分に誤りが混じっている可能性があります。

Bluetooth4.0イヤホンはどうやって音声データを流してるのか

疑問だったのは、自分がいま使ってるイヤホンは、Bluetooth4.0であることでした。



自分は Bluetooth4.0 = BLE だと思っていたので、何でBLEで音楽聴けてるんだろう?と。ビットレートをそこまで落としてる感じもしないし。


というわけで、FacebookのWF-BTLEグループで、次のような質問をしました。

BLEのプロファイルについてよくわからない点があるので、質問させてください。


BLEは『少量のデータを低頻度でやりとりする用途に、ハードウェアの低コスト化と、コイン型電池で1〜2年間の連続動作ができる超低消費電力に特化』(こちらの記事より引用)した規格であると認識しています。


なので、BLEのGATTベースのプロファイルは、Heart Rateや、Battery Serviceなど、少量データ・低頻度で済む用途のものが定義されているし、GATTの規格自体も大きいデータを流しっぱなしにするようにはなっていない(アトリビュートのvalueは512バイトまで)と。


ただよくわからないのが、世の中にはBluetooth4.0用のヘッドセットなども出ている点です。


上記ヘッドセットのページにはプロファイルにHFPを使用しているようなことが書いてあります。私の認識ではBLE機器ではGATTベースのプロファイルしか使えず、HFPなどは3.0以前の従来規格でしか使用できない、というものなのですが、そんなことないのでしょうか?もしくは、フレームレートを抑える等の工夫をしてGATTの規格内でHFP相当の通信ができるようにしている、ということなのでしょうか?


見当違いのことを言ってるかもしれませんが、識者のみなさま、ご教示いただけると幸いです。


ちなみにこの質問をする意図としては、iOSのCoreBluetoothで、デバイスと音声データのやり取りをしたい(音質は5kbpsぐらいまで落とせる)、というものになります。BLEはそもそもそういう用途の規格ではないとは思いますが、MFiなし、WiFiもなしでデバイスとiOSアプリを無線通信させたい場合、やはりCoreBluetooth/BLE一択になってくるので。。


そして、回答をいくつかいただきました。(掲載許可は得ていないので)原文を載せるのは避け、要約すると、

  • Bluetooth4.0は、3.0までの技術に、Low Energyの技術を統合したもの。なので、3までの技術も使える
    • 両方に対応したものをデュアルモードと呼ぶ
  • ただ、Core Bluetoothでは、Low Energyの部分しか制御できない
  • BLEを音声通信的なものに利用するのは、あり。CoreAudioでBluetothのヘッドセットへルート切り替えが、最近はできるようになったはず。
  • 参考リンク

という明快な回答をいただきました。


なるほど、まず大前提として、「Bluetooth 4.0 = BLE」というのが大きな勘違いでした *1


「デュアルモード」というのは知ってましたが、それは3.0にも対応している、という意味だと思っていて、4.0の仕様が3.0の仕様も含んでいる、ということだとは知りませんでした。


で、音楽を聴けるヘッドセットやイヤホンは、3.0までの技術を使っていて、それはMFiなしで使えるCoreBluetoothでは制御できないと。


また「Bluetoothを扱いたければCoreBluetoothだ!」 *2 とばかり思っていたので、オーディオのルート切り替えは盲点でした。

オーディオセッションによるBluetoothデバイスへのルート切り替え

そういえば、ルート切り替えの定義でBluetoothなんちゃらっての見たことあるな、というのはなんとなく記憶にあったので、改めて見てみると、

kAudioSessionInputRoute_BluetoothHFP
kAudioSessionOutputRoute_BluetoothHFP
kAudioSessionOutputRoute_BluetoothA2DP

というのがありました。


これができれば、HFPプロファイルに対応したデバイスから音声データを受け取ってそのままAudio Unitとかで波形処理できるので、なかなか良さそうです。iOS7からdeprecatedになってましたが。。

Core Bluetoothでどうにかしてみる

上述したようにオーディオセッションでルートを切り替えることで音声データのやりとりができそう、とわかったものの、デバイス側がいまのところGATTベースのプロファイルにしか対応してないので、結局Core Bluetoothでどうにかすることにしました。


※ペリフェラル側でS/N比などをみて音声データを切って送信する前提です。


[ペリフェラル側]

- (void)peripheralManager:(CBPeripheralManager *)peripheral
    didReceiveReadRequest:(CBATTRequest *)request
{
    // 音声バッファの続きを渡す
    request.value = [self soundDataFromCurrentIndex];
    
    [peripheral respondToRequest:request
                      withResult:CBATTErrorSuccess];
}


上記コード内で呼んでいる soundDataFromCurrentIndex というメソッドでは128バイトずつ *3 音声バッファのデータを渡すようにしています。


[セントラル側]

- (void)                 peripheral:(CBPeripheral *)peripheral
    didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
                              error:(NSError *)error
{
    NSData *data = characteristic.value;
    NSUInteger bytes = data.length;

    [self.receivedData appendData:data];
        
    // 次のデータを読み込む
    if (bytes >= kMaxBytes) {

        [cbPeripheral readValueForCharacteristic:self.characteristic];
    }
    // 受信完了
    else {
        
        // receivedDataに対して処理を行う
    }
}


全部読み終わるまでreadし続ける、という(とくにTips要素もない)普通の処理です。*4

試してみた結果

上記方法で試しに 15kB の wavデータ(16bit, 16kHz)を送ってみると、20秒ちょいかかりました。先日計測した速度とだいたい同じぐらいなので妥当そうですが、送ったのは再生時間が1秒にも満たないデータなので、リアルタイムにはほど遠いレベルです。


5kbpsぐらいの通信速度である程度リアルタイムに送るには、1秒当たり600バイトぐらいに収める必要があるので、ビットレートを落とすだけじゃなく、いったんaacとかの圧縮フォーマットにエンコードして送る必要がありそうです。

*1:これ何度か気になって調べたうえで勘違いしてたので、「Bluetooth 4.0 = BLE」と間違った解説してるページがあったんじゃないかと。

*2:MultipeerConnectivityもありますがあれはiOS to iOSなので今回の目的には合わない。

*3:256以上だと挙動がおかしかった(送信データと受信データとサイズが合わなかったり)ので、何らかの制約があるのかなと。

*4:CBATTRequest に offset というプロパティがあって、もしかしたらこういうケースで使うものなのかもしれませんが、誰がどうそのoffsetを管理するのかわからなかったので結局使用しませんでした。