PaPeRo iのカメラとマイクでリアルタイムモニタ

 PaPeRo iのカメラでリアルタイムモニタ Golang編に機能を追加してカメラ画像と同時にPaPeRo iのマイク音声も同時にWebブラウザでモニタできるようにしてみました。

構成

 カメラデータには専用のWebSocketコネクションを使用していましたが、画像と音声の同期は考えず、もう一つWebSocketコネクションを追加して音声データ専用で使用することにします。

音声データの取得

 PaPeRo iのマイク入力の音声データをリアルタイムで取得するにはコマンド

/usr/bin/arecord -

を使用します。標準出力から8kHzサンプリング/8bitモノラル音声データが取得できます。Golangからは、

ctx := context.Background()
cmd := exec.CommandContext(ctx, "/usr/bin/arecord", "-")
outpipe, err := cmd.StdoutPipe()
rbuf := make([]byte, 800)
for {
    n, err := io.ReadFull(outpipe, rbuf)
    ...

としています。音声データは一定の固まりごとにまとめて送るのが良いので、そのためio.ReadFull()を使用してバッファサイズ一杯のデータを取得しています。これで0.1秒分の800バイトの音声データが取得できます。カメラデータは画像が取得できたイベントを受けたらWebSocketへ送信する処理を行えば良かったのですが、音声の場合逐次データを読み出して一定量に達したところでWebSocketへ送信する必要があるので、音声データ読み出し専用のgoroutineが必要になります。
 WebSocketへの送信処理自体はカメラデータと同じです。0.1秒ごとに読み出した音声データを無加工でブラウザへ送信します。

音声の再生

 ブラウザ側JavaScriptではWebSocket.binaryTypeを、画像は’blob’でしたが音声は’arraybuffer’とします。受信した0.1秒分の音声データブロックを必要なFloat32Arrayへの変換を行ってブラウザで音声再生するのは意外に簡単な記述で実現できます。

wsaudio.onmessage = function(event){
  var ui = new Uint8Array(event.data)
  var fa = ui8ary2f32ary(ui);
  playBlock(fa);
}
var acx = new (window.AudioContext||window.webkitAudioContext);
var delaysec = 0;
var nexttime = 0;
// convert arecord ui8array to float32array
function ui8ary2f32ary(inbuf) {
  var sz = inbuf.length;
  var res = new Float32Array(inbuf.length);
  for (var j = 0; j < sz; j++) {
    res[j] = (inbuf[j] - 128) / 128.0;
  }
  return res;
}
function playBlock(adat) {
  var abuf = acx.createBuffer(1, adat.length, 8000);
  var asrc = acx.createBufferSource();
  var curtime = acx.currentTime;

  abuf.getChannelData(0).set(adat);
  asrc.buffer = abuf;
  asrc.connect(acx.destination);

  if (curtime < nexttime) {
    asrc.start(nexttime);
    nexttime += abuf.duration;
  } else {
    asrc.start(curtime + delaysec);
    nexttime = curtime + delaysec + abuf.duration;
  }
}

参考URL: WebAudio+WebSocketでブラウザへの音声リアルタイムストリーミングを実装する

USBが切れてしまう問題

 試してみると、音声は再生されるのですが暫くすると音が止まってしまいます。コマンドラインから実行したarecordも同じタイミングで停止してしまいます。ログに、

Nov  9 10:28:01 aterm user.debug kernel: [ 2779.082563] hub 3-1.2:1.0: state 7 ports 4 chg 0000 evt 0008
Nov  9 10:28:01 aterm user.debug kernel: [ 2779.082824] hub 3-1.2:1.0: port 3 enable change, status 00000101
Nov  9 10:28:01 aterm user.err kernel: [ 2779.083058] hub 3-1.2:1.0: port 3 disabled by hub (EMI?), re-enabling...
Nov  9 10:28:01 aterm user.debug kernel: [ 2779.090330] hub 3-1.2:1.0: port 3, status 0101, change 0002, 12 Mb/s
Nov  9 10:28:01 aterm user.info kernel: [ 2779.090382] usb 3-1.2.3: USB disconnect, device number 56
Nov  9 10:28:01 aterm user.debug kernel: [ 2779.095996] usb 3-1.2.3: unregistering device
Nov  9 10:28:01 aterm user.debug kernel: [ 2779.096014] usb 3-1.2.3: unregistering interface 3-1.2.3:1.0
Nov  9 10:28:01 aterm user.debug kernel: [ 2779.097569] usb 3-1.2.3: unregistering interface 3-1.2.3:1.1
Nov  9 10:28:01 aterm user.debug kernel: [ 2779.097805] usb 3-1.2.3: unregistering interface 3-1.2.3:1.2
Nov  9 10:28:01 aterm user.debug kernel: [ 2779.098127] usb 3-1.2.3: unregistering interface 3-1.2.3:1.3
Nov  9 10:28:01 aterm user.debug kernel: [ 2779.131871] usb 3-1.2.3: usb_disable_device nuking all URBs

等と出ており、どうもUSBの切断・再接続のシーケンスが不定期に走ってしまうようです。アプリでの回避は難しそうなので、撮影レートをいくら下げてもこの現象が頻発するSXGAはあきらめ、VGAカメラ(ffmpeg使用)とし、データが途切れたらタイムアウトを検出してarecordコマンドを再実行する様にしていますが、この現象が発生すると2~3秒間音声が途切れてしまいます。

実行結果

 ソースとPaPeRo i用ビルド済みバイナリはこちらにあります。local/paperocamera/paperocameraをパペロに転送して任意の場所で実行し、パペロのIP:8865/cameraをブラウザで開き、パペロの座布団向かって右ボタンを押すとカメラ画像が表示されマイク音声が流れます。
 PaPeRo iのCPU使用率は44%程度(音声追加により+4%程度)、音声の遅延は約250msecとなりました。


0