LoginSignup
3
1

More than 1 year has passed since last update.

M5Stack UnitV2のマイク音声をリアルタイム再生するWebアプリ

Last updated at Posted at 2022-12-23

はじめに

本記事はM5Stack Advent Calendar 2022の24日目です。

今回、M5Stack UnitV2の内蔵マイク音声を、ブラウザ上でリアルタイム再生出来るようにしてみました。
真っ暗な部屋などでは、UnitV2のカメラ映像も真っ黒になってしまいますが、
音声に明るさは関係ないので、夜中の防犯用途などにも使えそうです。

動作中の様子

以下は画像のため音声は聞こえませんが、波形や周波数スペクトルの表示が変化している様子が分かるかと思います。
音が小さい場合、波形の変化が分かりづらいため、ゲイン調整用のスライダーなども実装しています。

Animation.gif

アプリ構成

アプリは以下のような構成です。
マイク音声をUnitV2からPCへWebSocketで送信し、ブラウザ上で再生しています。

image.png

Tips

複数プロセスから同時にマイク音声を取得可能にする

複数人が同時にアクセス出来るよう、バックエンドのサーバはGunicornを使用してマルチプロセスで起動しています。
この際、そのままでは複数プロセスから同時にマイクを使用出来ず、エラーになります。
ALSAのdsnoopプラグインを使用すると、マイクの同時使用が可能になります。

UnitV2上に、以下の内容の/etc/asound.confを作成しておきます。

/etc/asound.conf
pcm.mixin {
    type dsnoop
    ipc_key 5978293
    ipc_key_add_uid yes
    slave {
        pcm "hw:0,0"
        channels 2
    }
}

依存ライブラリのインストール

UnitV2のデフォルト環境を壊したくないので、Pythonの依存ライブラリはvenvの仮想環境にインストールします。
内蔵ストレージは空きがほぼ無いので、SDカード上に作成します。

unitv2% cd /media/sdcard/適当なフォルダ
unitv2% sudo python -m venv venv
unitv2% . venv/bin/activate
(venv) unitv2% sudo python -m pip install fastapi gunicorn uvicorn websockets

この際、注意点がいくつかあります。

  • 仮想環境の作成や、仮想環境へのライブラリインストールはroot権限で行う
  • pipコマンドはpython -m pipの形で実行する

仮想環境の作成時は内部的にchmodが実行されますが、SDカード上のファイルは所有者がrootで固定となるため、
root権限でないとこの処理に失敗し、仮想環境の作成途中でエラーになります。

また、python -m pipではなく単にpipで実行すると、以下のようなエラーが出ます。1

(venv) unitv2% pip install 何か
Traceback (most recent call last):
  File "/media/sdcard/適当なフォルダ/venv/bin/pip", line 5, in <module>
    from pip._internal.cli.main import main
ModuleNotFoundError: No module named 'pip._internal.cli.main'

バックエンド実装(抜粋)

バックエンドはPython+FastAPIで実装しました。
arecordで取得した生のマイク入力を、そのままWebSocketで送信しています。
先ほどのdsnoopの設定を使用するため、arecordの引数に-D mixinを指定しておく必要があります。

server.py
import audioop
import subprocess

from fastapi import FastAPI, Request, WebSocket

CHUNK = 1024 * 1
COMMAND = ['arecord', '-D', 'mixin', '-f', 'S16_LE', '-c', '1', '-r', '48000', '-t', 'raw', '-q', '-']
AUDIO_SOURCE = subprocess.Popen(COMMAND, stdout=subprocess.PIPE)


app = FastAPI()


@app.websocket('/websocket')
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    key = websocket.headers.get('sec-websocket-key', '')
    print(f'WebSocket {key} connected')

    while True:
        if AUDIO_SOURCE.poll() is not None:
            await websocket.close()
            print(f'WebSocket {key} closed')
            break

        AUDIO_SOURCE.stdout.flush()
        buf = AUDIO_SOURCE.stdout.read(CHUNK)
        try:
            await websocket.send_bytes(buf)
        except Exception as e:
            await websocket.close()
            print(f'WebSocket {key} closed: {e}')
            break

フロントエンド実装

WebSocketで受信したマイク入力データを、Web Audio APIを使用して再生しています。2
波形データや周波数データへの変換にも、同じくWeb Audio APIを使用しています。

波形やスペクトル表示部には<svg><path>を使用しました。
Vue.jsで<path>dを更新することで、リアルタイムでの波形描画を実現しています。3

ソースコードは上手く抜粋するのが難しかったので、直接GitHubを参照して下さい。

余談

今回、細かなソースコードの修正や微調整などは直接UnitV2上のVimで行っていましたが、
以前UnitV2用にビルドしたtmuxのおかげで快適に試行錯誤することが出来ました。

tmux.png

UnitV2上でtmuxを動作させる方法については、以下の記事に記載しています。

M5Stack UnitV2でtmuxを動かす

参考資料

ソースコード

今回作成したプログラムのソースコードは以下で公開しています。

  1. とりあえずpython -m pipで動いているので、原因は深追いしていません。

  2. こちらのGistが参考になりました。

  3. こちらの記事が参考になりました。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1