サウンドプログラミングの勉強を進めます。Linuxでオーディオのプログラミングをするためのライブラリ ALSA (Advanced Linux Sound Architecture) ライブラリを使ってみます。開発環境はMacBook ProにインストールしたUbuntu 16.0.4 LTSでEclipse CDT (Neon)を使いました。
ALSAライブラリはここからダウンロード。最新版のalsa-lib-1.1.4.1.tar.bz2をダウンロードしました。
/tmpディレクトリにalsaという名前のサブディレクトリを作りそこに上記ファイルをダウンロードし解凍した様子。
ALSAライブラリのコンパイルとインストールは次のように行います。
$ cd alsa-lib-1.1.4.1 $ ./configure $ make $ sudo make install
インストールされたことを確認のため /usr/include/alsaを見るなどします。
今回はEclipse CDTでサンプルプログラムをビルドしました。
Cプロジェクトを新規作成しmain()を含むソースコードを書きます。(プロジェクト名は alsa-wav-play にしました。)
プロジェクトのプロパティの設定です。C/C++ Builde->SettingsでLibrariesにasoundとmを追加。asoundはALSAライブラリ、mはMathライブラリです。このように設定すると、コンパイル時のコマンドラインが
gcc -lasound -lm ソース.c -o 実行ファイル
のようになります。
サンプルプログラムのソースコードはこちらからいただきました。
/* ALSA lib を使用して、WAVファイルを再生する */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <alsa/asoundlib.h> /* PCMデフォルト設定 */ #define DEF_CHANNEL 2 #define DEF_FS 48000 #define DEF_BITPERSAMPLE 16 #define WAVE_FORMAT_PCM 1 #define BUF_SAMPLES 1024 /* ChunkID 定義 */ const char ID_RIFF[4] = "RIFF"; const char ID_WAVE[4] = "WAVE"; const char ID_FMT[4] = "fmt "; const char ID_DATA[4] = "data"; /* PCM情報格納用構造体 */ typedef struct { uint16_t wFormatTag; // format type uint16_t nChannels; // number of channels (1:mono, 2:stereo) uint32_t nSamplesPerSec; // sample rate uint32_t nAvgBytesPerSec; // for buffer estimation uint16_t nBlockAlign; // block size of data uint16_t wBitsPerSample; // number of bits per sample of mono data uint16_t cbSize; // extra information } WAVEFORMATEX; /* CHUNK */ typedef struct { char ID[4]; // Chunk ID uint32_t Size; // Chunk size; } CHUNK; /* WAVE ファイルのヘッダ部分を解析、必要な情報を構造体に入れる * ・fp は DATA の先頭位置にポイントされる * ・戻り値は、成功時:データChunk のサイズ、失敗時:-1 */ static int readWavHeader(FILE *fp, WAVEFORMATEX *wf) { char FormatTag[4]; CHUNK Chunk; int ret = -1; int reSize; /* Read RIFF Chunk*/ if((fread(&Chunk, sizeof(Chunk), 1, fp) != 1) || (strncmp(Chunk.ID, ID_RIFF, 4) != 0)) { printf("file is not RIFF Format ¥n"); goto RET; } /* Read Wave */ if((fread(FormatTag, 1, 4, fp) != 4) || (strncmp(FormatTag, ID_WAVE, 4) != 0)){ printf("file is not Wave file¥n"); goto RET; } /* Read Sub Chunk (Expect FMT, DATA) */ while(fread(&Chunk, sizeof(Chunk), 1, fp) == 1) { if(strncmp(Chunk.ID, ID_FMT, 4) == 0) { /* 小さい方に合せる(cbSize をケアするため) */ reSize = (sizeof(WAVEFORMATEX) < Chunk.Size) ? sizeof(WAVEFORMATEX) : Chunk.Size; fread(wf, reSize, 1, fp); if(wf->wFormatTag != WAVE_FORMAT_PCM) { printf("Input file is not PCM¥n"); goto RET; } } else if(strncmp(Chunk.ID, ID_DATA, 4) == 0) { /* DATA Chunk を見つけたらそのサイズを返す */ ret = Chunk.Size; break; } else { /* 知らない Chunk は読み飛ばす */ fseek(fp, Chunk.Size, SEEK_CUR); continue; } }; RET: return ret; } int main(int argc, char *argv[]) { /* 出力デバイス */ char *device = "default"; /* ソフトSRC有効無効設定 */ unsigned int soft_resample = 1; /* ALSAのバッファ時間[msec] */ const static unsigned int latency = 50000; /* PCM 情報 */ WAVEFORMATEX wf = { WAVE_FORMAT_PCM, // PCM DEF_CHANNEL, DEF_FS, DEF_FS * DEF_CHANNEL * (DEF_BITPERSAMPLE/8), (DEF_BITPERSAMPLE/8) * DEF_CHANNEL, DEF_BITPERSAMPLE, 0}; /* 符号付き16bit */ static snd_pcm_format_t format = SND_PCM_FORMAT_S16; int16_t *buffer = NULL; int dSize, reSize, ret, n; FILE *fp = NULL; snd_pcm_t *hndl = NULL; if(argc != 2) { printf("no input file¥n"); } /* WAVファイルを開く*/ fp = fopen(argv[1], "rb"); if (fp == NULL) { printf("Open error:%s¥n", argv[1]); goto End; } /* WAVのヘッダーを解析する */ dSize = readWavHeader(fp, &wf); if (dSize <= 0) { goto End; } /* PCMフォーマットの確認と情報出力を行う */ printf("format : PCM, nChannels = %d, SamplePerSec = %d, BitsPerSample = %d¥n", wf.nChannels, wf.nSamplesPerSec, wf.wBitsPerSample); /* バッファの用意 */ buffer = malloc(BUF_SAMPLES * wf.nBlockAlign); if(buffer == NULL) { printf("malloc error¥n"); goto End; } /* 再生用PCMストリームを開く */ ret = snd_pcm_open(&hndl, device, SND_PCM_STREAM_PLAYBACK, 0); if(ret != 0) { printf( "Unable to open PCM¥n" ); goto End; } /* フォーマット、バッファサイズ等各種パラメータを設定する */ ret = snd_pcm_set_params( hndl, format, SND_PCM_ACCESS_RW_INTERLEAVED, wf.nChannels, wf.nSamplesPerSec, soft_resample, latency); if(ret != 0) { printf( "Unable to set format¥n" ); goto End; } for (n = 0; n < dSize; n += BUF_SAMPLES * wf.nBlockAlign) { /* PCMの読み込み */ fread(buffer, wf.nBlockAlign, BUF_SAMPLES, fp); /* PCMの書き込み */ reSize = (n < BUF_SAMPLES) ? n : BUF_SAMPLES; ret = snd_pcm_writei(hndl, (const void*)buffer, reSize); /* バッファアンダーラン等が発生してストリームが停止した時は回復を試みる */ if (ret < 0) { if( snd_pcm_recover(hndl, ret, 0 ) < 0 ) { printf( "Unable to recover Stream." ); goto End; } } } /* データ出力が終わったため、たまっているPCMを出力する。 */ snd_pcm_drain(hndl); End: /* ストリームを閉じる */ if (hndl != NULL) { snd_pcm_close(hndl); } /* ファイルを閉じる */ if (fp != NULL) { fclose(fp); } /* メモリの解放 */ if (buffer != NULL) { free(buffer); } return 0; }
Eclipseのビルドボタンを押してビルド。問題がなければ実行ファイル(alsa-wav-play)ができます。
プログラムを実行します。(WAVファイルはこちらからダウンロードしたエルガーの『威風堂々』-MP3ファイル-をAudacityでWAVファイルに変換したものです。)
$ ./alsa-wav-play Elgar-Pomp-and-Circumstance-No1.wav
無事に再生できればオッケー。
次はオーディオ入力から入ってきた音を再生してみようと思います。