Raspberry Piオーディオ:音を鳴らしているか検出したい

今更ながら最近、Raspberry Piを触っていました。

Raspberry Pi zero whに、pHatボードのDACを取り付けて、アンプと繋げて音楽を楽しむということをしていました。

MPDで音楽を再生したりとか、Airplayレシーバーにしたりとか、色々試していたのですが一点苦労したとことが・・。それは「音を出しているときはアンプをオンにしたい、音が出なくなったらアンプをオフにしたい。自動で」ということでした。

アンプをRaspberry Piから操作する方法は、赤外線を使うなりRS232ポートやLAN経由で操作するなり、アンプに応じて色々方法はあると思うので割愛しますが、問題は「音が出ているか判定する部分」。もちろん音を出す方法が一種類ならば対応は簡単そうですが、いろいろなソースから音を出すことを考えると、できるだけ根っこの部分で判定をしたいです。

それで悪戦苦闘の末いきついたのが、「DACボードと通信しているGPIOの生データを読んで判定する」という方法です。あまりいないような気はするのですが、もしかしたら同じようなことをしたいと思っているかたがいるかもしれないので、方法について、記載してみます。

基本的な考え方は、DACボードに信号を送って音を出しているので、音がでていないときは信号のやりとりがない=GPIOのピンの各電圧は一定、音を出しているときは信号のやりとりがあるのでGPIOのピンの電圧に変化があるはず、、です。

まずは、音を出しているときに変化があるGPIOのピンを見つける必要があります。

コマンドラインで、

gpio readall

とうつことで、各ピンので夏をみることができますので、音を出している状態で何度もコマンドを打って変化しているピンを見つけます(原始的)

そのうえで、そのピンの電圧を監視するプログラムを書きます。

C言語でメモリをマッピングして読み込む形になります。

まずは、GPIOの値が、物理メモリ空間のどこにマッピングされているか仕様書をみて調べます。

Raspberry Pi hardware - Raspberry Pi Documentation

から、Raspberry pi zeroならばBCM2835みたいなので、BCM2835のPeripheral specificationをみてみます。

この仕様書を読み解くのに少し苦労したのですが、まずは90ページ目あたりの表をみると

0x7E200034 GPLEV0 GPIO Pin Level

0x7E200038 GPLEV1 GPIO Pin Level

このあたりをみれば良さそうです。96ページの説明を見ると各GPIOのピンの電圧がビットごとに入っているみたいですね。

で、問題は0x7E200034って何と言うところなのですが・・・。

ここで仕様書のページ6 BCM2835 ARM Periphreをみてみます。

この、0x7E200034というようなアドレスはこの表の

VC CPU Bus Addressにあたるようです。

0x7E000000からがARM Physical Addressの0x20000000から割り振られるようです。

つまり、0x7E200034は、ARM Physical Addressの0x20200034にあたるようです。

ここからはC言語的な話ですが、ARM Physical Addresは直接アクセスできず、mmapマッピングしてあげる必要があります。

以下、サンプルプログラムになります。GPIOの21番のピンを監視しています。無音状態から音がなったことを検知すると、/home/pi/on.shを実行します。また、30秒以上無音(ピンに変化なし)が続くと、/home/pi/off.shを実行します。

注意ですが、このプログラムを実行するにはルート権限が必要です。起動時にかならず実行されるようにしておくことで、やりたかったことが実現できるようになりました。

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#define DEVMEM "/dev/mem"

int main() {
  int count;
  int fd;
  volatile unsigned char *iomap;
  fd = open(DEVMEM, O_RDONLY);
  if( fd < 0 )
  {
     printf("Failed Open Mem\n");
     exit(1);
  }
  iomap = mmap(0, 0xb0, PROT_READ, MAP_SHARED, fd, 0x20200000);
  if(iomap == MAP_FAILED) {
    printf("mmap failed\n");
    exit(1);
  }  

  int state = -1;
  int seqcount = 0;
  while(1)
  {
     int bit = 1 & ((*(unsigned int*)(iomap + 0x34)) >> 21);
     if( bit )
     {
       if( state != 1 )
       {
          system("sh /home/pi/on.sh");
       }
       state = 1;
       seqcount = 0;
     }
     else
     {
        seqcount++;
        if( seqcount >= 100 * 30 && state != 0) // 30s
        {
          system("sh /home/pi/off.sh");
          state = 0;
        }
     }
     usleep(10000);
  }
}