言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

ALSA プログラミング

ALSA プログラミングを始めてみました。注意すべき点は以下の通り。

  • Fedora Core の場合ドキュメントは /usr/share/doc/alsa-lib-devel-1.0.11/doxygen/html/index.html にイ ンストールされるが、激しく検索しにくい。関数に似た名前のファイルを Files タブから探す。
  • snd_pcm_hw_params_t : デバイスを表す。初期設定時に使う。
  • snd_pcm_t : サンプリング音源を表す。音を発生する時にも使う。
  • periods : 区間とは、遅延を減らすためバッファを区切る数。一度に送信するのはバッファサイズ / periods となる。
  • snd_pcm_hw_params_set_buffer_size で必要になるバッファの大きさは、periodsize と periods と チャンネル数で決まる。
  • frame : フレームとは長さの単位で、16 ビットステレオなら 4 バイトが 1 フレームとなる

他にも山ほど機能があるようですが、単純にファミコンのような音を鳴らすプログラムは次のような物です。長 く感じるのは関数名が冗長だからで、大変単純です。

/*
 * ALSA の矩形波発生テストプログラム。ライセンスは http://www.suse.de/~mana/LICENSE になります。
 * http://www.suse.de/~mana/alsa090_howto.html を簡単にしたもの。
 * $ ./alsaRect 440 のようにして実行
 * コンパイルの仕方は
 * $ cc -g -o alsaRect -lasound alsaRect.c
 */

#include <alsa/asoundlib.h>

/* エラー処理 */
#define CALL(result) handleError(result, __LINE__);
void handleError (int result, int line)
{
  if (result >= 0) return;

  fprintf(stderr, "Error line: %d\n", line);
  exit(-1);
}

/* メイン処理 */
int main(int argc, char * argv[])
{
  if (argc <= 1) {
    printf("usage: %s frequency\n", argv[0]);
    exit(-1);
  }

  snd_pcm_t *pcm_handle; /* サンプリング音源デバイス */
  snd_pcm_hw_params_t *hwparams; /* the hardware */
  char *pcm_name; /* Name of the PCM device */

  pcm_name = strdup("plughw:0,0"); /* デバイス指定はおまかせで */
  snd_pcm_hw_params_alloca(&hwparams); /* パラメータ構造体の初期化 */

  CALL(snd_pcm_open(&pcm_handle, pcm_name, SND_PCM_STREAM_PLAYBACK, 0)); /* 再生モードで開く */
  CALL(snd_pcm_hw_params_any(pcm_handle, hwparams)); /* これもパラメータ構造体の初期化 */

  int rate = 44100; /* 希望するサンプルレート */
  int exact_rate;   /* 実際ののサンプルレート */
  int periods = 2;  /* periods 遅延を防ぐための区間の数 */
  snd_pcm_uframes_t periodsize = 8192; /* 一度に処理するバッファのバイト数 */
  exact_rate = rate;

  CALL(snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)); /* 左右順 番に書き込み */
  CALL(snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE)); /* 16 ビットリトルエンディアン */
  CALL(snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_rate, 0)); /* 実際のサンプルレー ト設定取得 */
  CALL(snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2)); /* ステレオ設定 */
  CALL(snd_pcm_hw_params_set_periods(pcm_handle, hwparams, periods, 0)); /* 区間数設定 */
  CALL(snd_pcm_hw_params_set_buffer_size(pcm_handle, hwparams, (periodsize * periods) / 4)); /* バッ ファサイズをフレーム数で設定 */
  CALL(snd_pcm_hw_params(pcm_handle, hwparams)); /* よくわからんが色々設定 */

  /* 遅延は以下の式で求まるらしい
   * フレームとは長さの単位で、16 ビットステレオなら 4 バイトが 1 フレームとなる
   * latency = periodsize * periods / (rate * bytes_per_frame)
   * 遅延 = バッファのバイト数 * 区間数 / (サンプルレート * フレームあたりバイト数)
   */

  unsigned char *data;
  int pcmreturn, l1, l2;
  short s1, s2;
  int frames;
  int frequency;
  int amplitude;
  int step = 0;
  frequency = atoi(argv[1]);
  amplitude = exact_rate / frequency;

  data = (unsigned char *)malloc(periodsize); /* periodsize バイト分バッファを確保 */
  frames = periodsize / 4; /* フレーム数はバイト数 / 4 */
  while (step < (exact_rate * 0.5)) { /* サンプルレート * 0.5 という事は 0.5 秒 */
    for(l2 = 0; l2 < frames; l2++) {
      step++;
      s1 = ((step % (amplitude / 1)) > (amplitude / 2)) ? -5000 : 5000;
      s2 = ((step % (amplitude / 2)) > (amplitude / 4)) ? -5000 : 5000;
      data[4*l2+0] = (unsigned char)s1;
      data[4*l2+1] = s1 >> 8;
      data[4*l2+2] = (unsigned char)s2;
      data[4*l2+3] = s2 >> 8;
    }
    while ((pcmreturn = snd_pcm_writei(pcm_handle, data, frames)) < 0) {
      snd_pcm_prepare(pcm_handle);
      fprintf(stderr, "<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>\n");
    }
  }

  //  snd_pcm_drop(pcm_handle); /* デバイスを停止して残りは捨てる */
  snd_pcm_drain(pcm_handle); /* 音が全部流れたらデバイスを終了 */
}