C言語で音声をキャプチャ(Linux環境)

2017-05-03

Linuxマシンで、C言語でALSAのライブラリを使って音を取ってくる。マイクの音を取ってもいいが、例えば音楽を流しつつその音を解析したりしたいのでPulseaudioに入っているpacmdを使って、オーディオ出力のモニタから音を取ってくるように設定する。C言語のコードはパクる。

パクル

ここにあった。

https://cboard.cprogramming.com/linux-programming/167738-sound-recording-using-alsa-lib-pls-help.html

このコードだと

  • デフォルトのキャプチャデバイスを使う
  • サンプリングレート44100hz
  • 1周期32フレーム
  • 2チャンネル(ステレオ)
  • 符号付き16ビット、リトルエンディアン

の設定で、5秒間ナマのデータを標準出力に吐き出す。

コンパイル、実行して録音できているか確かめる。実行するときは何か音声を再生しながら実行する。


gcc -o sample sample.c -lasound
./sample > data
aplay -t raw -f S16_LE --c2 r44100 data

                              

aplayでなにも聞こえなかったら録音できていないので、pacmd、またはpavucontrolで、音声出力のモニタを録音させるよう設定する。今回はPulseaudioについてくるpacmdをつかった。

pacmd起動=>ソースをリスト


pacmd
list-sources

                              

いろいろ出てくるので name: <[音が出てくる出力先].monitor>を探してインデックスを確認しておく、今回は1。

Screenshot

次にソースの出力先(?)のインデックスを確認しておく。ソースの出力先とはつまり今回走らせるプログラムのことなのでプログラムを実行しつつコマンドを実行する。

ターミナルを2つ開いて

実行=>リスト

録音開始(ターミナル1)


./sample

                              

リスト(ターミナル2)


list-source-outputs

                              

また1。application.process.binary="sample"が見える。

Screenshot

そしたらさっき確認したソース(インデックス1)を出力先(インデックス1)に割り当てる。プログラムを実行しつつ。


move-source-output 1 1

                              

これで録音できる。

ということをやったんだけどデフォルトのソースを設定したら1発で完了。


move-source-output 1 1

                              

波形を確認したい

aplayで耳で確認したけど、目でも確認したい。少しコード変えて(for文入れた)、標準出力に数字を書き出す。 (コメント省いた)


#include <stdio.h>
/* Use the newer ALSA API */
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>

int main() {
     long loops;
     int rc;
     int size;
     snd_pcm_t *handle;
     snd_pcm_hw_params_t *params;
     unsigned int val;
     int dir;
     snd_pcm_uframes_t frames;
     char *buffer;

     rc = snd_pcm_open(&handle, "pulse",SND_PCM_STREAM_CAPTURE, 0);
     if (rc < 0) {
          printf("unable to open pcm device: %s\n", snd_strerror(rc));
          return 0;
     }

     snd_pcm_hw_params_alloca(&params);
     snd_pcm_hw_params_any(handle, params);

     snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
     snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);                                 
     snd_pcm_hw_params_set_channels(handle, params, 2);                                 

     val = 44100;                                                     
     snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);
     frames = 32;
     snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);

     rc = snd_pcm_hw_params(handle, params);
     if (rc < 0) {
          printf("unable to set hw parameters: %s\n", snd_strerror(rc));
          return 0;
     }

     snd_pcm_hw_params_get_period_size(params, &frames, &dir);
     size = frames * 4; /* 2 bytes/sample, 2 channels */
     buffer = (char *) malloc(size);

     snd_pcm_hw_params_get_period_time(params, &val, &dir);
     loops = 5000000 / val;
     int i;
     int count = 1;

     while (loops > 0) {
          loops--;
          rc = snd_pcm_readi(handle, buffer, frames);
          if (rc == -EPIPE) {
               /* EPIPE means overrun */
               printf("overrun occurred\n");
               snd_pcm_prepare(handle);
          } else if (rc < 0) {
               printf("error from read: %s\n",
                         snd_strerror(rc));
          } else if (rc != (int)frames) {
               printf("short read, read %d frames\n", rc);
          }

          for(i = 0; i < size; i+=4)
          {
               int data = (buffer[i+1] << 8) | (buffer[i] << 0);
               printf("%d %d\n", count,  data);
               count++;
          }

     }

     snd_pcm_drain(handle);
     snd_pcm_close(handle);
     free(buffer);

     return 0;
}

                              

1チャンネル分だけ書きだす。1khzの正弦波を録音してファイルにリダイレクト。xの範囲を適当に取ってgnuplotで描画。

Picture