サウンドプログラミングの勉強を進めます。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という名前のサブディレクトリを作りそこに上記ファイルをダウンロードし解凍した様子。

Screenshot from 2017-07-31 09-47-38

ALSAライブラリのコンパイルとインストールは次のように行います。

$ cd alsa-lib-1.1.4.1
$ ./configure
$ make
$ sudo make install

インストールされたことを確認のため /usr/include/alsaを見るなどします。

Screenshot from 2017-07-31 09-53-34

今回はEclipse CDTでサンプルプログラムをビルドしました。

Cプロジェクトを新規作成しmain()を含むソースコードを書きます。(プロジェクト名は alsa-wav-play にしました。)

Screenshot from 2017-07-31 09-54-57

プロジェクトのプロパティの設定です。C/C++ Builde->SettingsでLibrariesにasoundとmを追加。asoundはALSAライブラリ、mはMathライブラリです。このように設定すると、コンパイル時のコマンドラインが

gcc -lasound -lm ソース.c -o 実行ファイル

のようになります。

Screenshot from 2017-07-31 09-59-13

サンプルプログラムのソースコードはこちらからいただきました。

/* 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

無事に再生できればオッケー。

次はオーディオ入力から入ってきた音を再生してみようと思います。