|
○ はじめに
色々な楽器(ボーカルも含めて)を使って 1 つの曲を作ろうとする場合、普通はまず単一楽器のみを録ったトラックを作り、それらを合成して曲に仕上げます(ミックスダウン)。その際、トラックごとに音量や定位(音源の位置)を調整したり、エフェクトをかけたりすることで気持ちよく聞ける音を作ることが出来ます。
ここではそんなにちゃんとした物ではなく、「2 つの音源を単純に合成して 1 つの音を作り出す」低性能ミキサを書いてみたいと思います。ちゃんとした物を作るには、著者の音に関する知識が足りなさ過ぎますので^^;
何故これを書こうと思ったかと言いますと、以前 Linux® で動くゲームを作ろうとした時に効果音を出そうとしたのですが、昔のサウンドドライバは複数の音を同時にデバイスファイルに書き込んでも同時に鳴らしてくれなかったからです。何時だったかは忘れましたが、そのうち同時に書き込めばドライバの方で合成してくれる様になったという記憶がかすかにあるのですが、タイミングの制御が確実ではないことやドライバの動作を調べるのが面倒だったので、結局自前で合成するライブラリを作りました。
その時の経験を残すという意味で、本ページを書いてみました。
○ サンプルプログラム
指定した 2 つの WAVE ファイルについて、片方のみを再生したり 2 つを足し合わせて同時再生するサンプルプログラム mix.c です。はじめに のページから、全てのサンプルプログラムを固めた tar ボールをダウンロードすることもできます。
pthread を使っていますので
gcc -o mix mix.c -lpthread
の様に -lpthread オプションを付けてコンパイルします。
引数に 2 つの WAVE ファイルの絶対パスを指定して実行すると、以下の様なメニューが表示されます。番号を入力して Enter を押すと、指定した内容に従って WAVE ファイルを再生します。
Select menu ... 1. Play first specified wave file only. 2. Play second specified wave file only. 3. Play both wave files simultaneously. 4. Stop playing. 99. Exit. Command ?
指定できる WAVE ファイルは
- PCM フォーマット
- 量子化ビット数 16 ビット
- 1 チャンネル
です。また、2 つの WAVE ファイルは同じサンプリングレートでなければいけません。
--- mix.c ---
1 #include <fcntl.h> 2 #include <limits.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <linux/soundcard.h> 7 #include <pthread.h> 8 #include <sys/ioctl.h> 9 #include <unistd.h> 10 11 12 #define COMMAND_STOP 0 13 #define COMMAND_PLAY_FIRST 1 14 #define COMMAND_PLAY_SECOND 2 15 #define COMMAND_PLAY_BOTH 3 16 #define COMMAND_EXIT 4 17 18 #define BUFSIZE 4096 19 20 21 /* WAVE ファイルの情報を格納する構造体 */ 22 typedef struct { 23 FILE* fp; /* ファイル構造体 */ 24 short is_pcm; /* PCM フォーマットの場合は 1、それ以外は 0 */ 25 short channel; /* モノラルの場合は 1、ステレオの場合は 2 */ 26 int rate; /* サンプリング周波数 */ 27 short bits; /* 量子化ビット数 */ 28 long offset; /* ファイル先頭から PCM データまでのオフセット */ 29 int len; /* PCM データ部の長さ */ 30 int current; /* 現在の再生位置 */ 31 } 32 WAVE; 33 34 35 /* 再生に必要なデータをまとめた構造体 */ 36 typedef struct { 37 pthread_t thread_id; 38 int command; 39 int dsp; 40 WAVE wave[2]; 41 short buffer[ BUFSIZE / sizeof( short ) ]; 42 } 43 Context; 44 45 static Context context; 46 47 48 static int read_file( char** fnames, WAVE* wave ); 49 static int setup_dsp( int* fd, WAVE* wave ); 50 static void show_menu( void ); 51 static void* play_thread( void* param ); 52 static void fill_buffer( short* dst, WAVE* wave ); 53 static void mix( short* dst, WAVE* wave ); 54 static void shutdown(); 55 56 57 int 58 main( int argc, char** argv ) 59 { 60 pthread_attr_t attr; 61 int ret; 62 63 if ( argc != 3 ) { 64 fprintf( stderr, "usage : ./wave filename1 filename2\n" ); 65 return 1; 66 } 67 68 if ( read_file( &( argv[1] ), &( context.wave[0] ) ) != 0 ) { 69 fprintf( stderr, "Failed to read wave files.\n" ); 70 return 1; 71 } 72 73 if ( setup_dsp( &( context.dsp ), &( context.wave[0] ) ) != 0 ) { 74 fprintf( stderr, "Failed to setup /dev/dsp.\n" ); 75 return 1; 76 } 77 78 pthread_attr_init( &attr ); 79 pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); 80 81 ret = pthread_create( &( context.thread_id ), &attr, 82 play_thread, ( void* )( &context ) ); 83 84 pthread_attr_destroy( &attr ); 85 86 if ( ret ) { 87 fprintf( stderr, "Failed to create thread.\n" ); 88 shutdown(); 89 return 1; 90 } 91 92 show_menu(); 93 pthread_join( context.thread_id, NULL ); 94 shutdown(); 95 96 return 0; 97 } 98 99 100 static int 101 read_file( char** fnames, WAVE* wave ) 102 { 103 char buf[32]; 104 int len; 105 int i; 106 107 for ( i = 0; i < 2; i ++ ) { 108 if ( ( wave[i].fp = fopen( fnames[i], "r" ) ) == NULL ) { 109 fprintf( stderr, "Failed to open %s\n", fnames[i] ); 110 goto err; 111 } 112 113 /* 114 * 先頭 4 バイトが "RIFF" であることを確認 115 * 更に 4 バイトスキップしておく 116 */ 117 fread( buf, 8, 1, wave[i].fp ); 118 119 if ( strncmp( buf, "RIFF", 4 ) != 0 ) { 120 fprintf( stderr, "Specified file is not RIFF file.\n" ); 121 goto err; 122 } 123 124 125 /* 次の 4 バイトが "WAVE" であることを確認 */ 126 fread( buf, 4, 1, wave[i].fp ); 127 128 if ( strncmp( buf, "WAVE", 4 ) != 0 ) { 129 fprintf( stderr, "Specified file is not WAVE file.\n" ); 130 goto err; 131 } 132 133 /* fmt チャンクを探す */ 134 while ( 1 ) { 135 fread( buf, 8, 1, wave[i].fp ); 136 len = *( int* )( &buf[4] ); 137 138 if ( strncmp( buf, "fmt ", 4 ) != 0 ) { 139 if ( fseek( wave[i].fp, len, SEEK_CUR ) == -1 ) { 140 fprintf( stderr, "Failed to find fmt.\n" ); 141 goto err; 142 } 143 } 144 else { 145 break; 146 } 147 } 148 149 fread( buf, len, 1, wave[i].fp ); 150 151 wave[i].is_pcm = *( ( short* )( &buf[0] ) ); 152 wave[i].channel = *( ( short* )( &buf[2] ) ); 153 wave[i].rate = *( ( int* )( &buf[4] ) ); 154 wave[i].bits = *( ( short* )( &buf[14] ) ); 155 156 /* 157 * PCM / 16 bits / Monoral 以外の WAVE データについては 158 * 本プログラムでは対象外 159 */ 160 if ( wave[i].is_pcm != 1 ) { 161 fprintf( stderr, "WAVE file must be PCM format.\n" ); 162 goto err; 163 } 164 165 if ( wave[i].bits != 16 ) { 166 fprintf( stderr, "WAVE file must be 16 bits .\n" ); 167 goto err; 168 } 169 170 if ( wave[i].channel != 1 ) { 171 fprintf( stderr, "WAVE file must be monoral.\n" ); 172 goto err; 173 } 174 175 /* data チャンクを探す */ 176 while ( 1 ) { 177 fread( buf, 8, 1, wave[i].fp ); 178 len = *( int* )( &buf[4] ); 179 180 if ( strncmp( buf, "data", 4 ) != 0 ) { 181 if ( fseek( wave[i].fp, len, SEEK_CUR ) == -1 ) { 182 fprintf( stderr, "Failed to find data.\n" ); 183 goto err; 184 } 185 } 186 else { 187 break; 188 } 189 } 190 191 wave[i].len = len; 192 193 if ( ( wave[i].offset = ftell( wave[i].fp ) ) == -1 ) { 194 fprintf( stderr, "Failed to find offset of PCM data.\n" ); 195 goto err; 196 } 197 198 printf( "\n" ); 199 printf( "WAVE file format of %s:\n", fnames[i] ); 200 printf( " rate = %d\n", wave[i].rate ); 201 printf( " channel = %d\n", wave[i].channel ); 202 printf( " bits = %d\n", wave[i].bits ); 203 printf( " offsets = %ld\n", wave[i].offset ); 204 printf( " length = %d\n", wave[i].len ); 205 printf( "\n" ); 206 } 207 208 if ( wave[0].rate != wave[1].rate ) { 209 fprintf( stderr, "Specified 2 WAVE files " 210 "must have the same sampling rate.\n" ); 211 goto err; 212 } 213 214 return 0; 215 216 err: 217 if ( wave[0].fp != NULL ) { 218 fclose( wave[0].fp ); 219 } 220 221 if ( wave[1].fp != NULL ) { 222 fclose( wave[1].fp ); 223 } 224 225 return 1; 226 } 227 228 229 static int 230 setup_dsp( int* dsp, WAVE* wave ) 231 { 232 int fragment = 0x0004000c; /* size = 4096 bytes, num of fragments = 4. */ 233 int format = AFMT_S16_LE; 234 int rate; 235 int channel; 236 237 rate = ( int )wave[0].rate; 238 channel = ( int )wave[0].channel; 239 240 if ( ( *dsp = open( "/dev/dsp", O_WRONLY ) ) == -1 ) { 241 perror( "open()" ); 242 return 1; 243 } 244 245 if ( ioctl( *dsp, SNDCTL_DSP_SETFRAGMENT, &fragment ) == -1 ) { 246 perror( "ioctl( SNDCTL_DSP_SETFRAGMENT )" ); 247 close( *dsp ); 248 return 1; 249 } 250 251 if ( ioctl( *dsp, SNDCTL_DSP_SETFMT, &format ) == -1 ) { 252 perror( "ioctl( SNDCTL_DSP_SETFMT )" ); 253 close( *dsp ); 254 return 1; 255 } 256 257 if ( ioctl( *dsp, SNDCTL_DSP_SPEED, &rate ) == -1 ) { 258 perror( "ioctl( SNDCTL_DSP_SPEED )" ); 259 close( *dsp ); 260 return 1; 261 } 262 263 if ( ioctl( *dsp, SNDCTL_DSP_CHANNELS, &channel ) == -1 ) { 264 perror( "ioctl( SNDCTL_DSP_CHANNELS )" ); 265 close( *dsp ); 266 return 1; 267 } 268 269 return 0; 270 } 271 272 273 static void 274 show_menu( void ) 275 { 276 char line[4]; 277 int command; 278 279 while ( 1 ) { 280 printf( "Select menu ...\n" ); 281 printf( " 1. Play first specified wave file only.\n" ); 282 printf( " 2. Play second specified wave file only.\n" ); 283 printf( " 3. Play both wave files simultaneously.\n" ); 284 printf( " 4. Stop playing.\n" ); 285 printf( " 99. Exit.\n" ); 286 printf( "\n" ); 287 printf( "Command ? " ); 288 289 fgets( line, sizeof( line ), stdin ); 290 sscanf( line, "%d\n", &command ); 291 292 switch ( command ) { 293 case 1: 294 context.command = COMMAND_PLAY_FIRST; 295 break; 296 case 2: 297 context.command = COMMAND_PLAY_SECOND; 298 break; 299 case 3: 300 context.command = COMMAND_PLAY_BOTH; 301 break; 302 case 4: 303 context.command = COMMAND_STOP; 304 break; 305 case 99: 306 context.command = COMMAND_EXIT; 307 return; 308 } 309 } 310 } 311 312 313 static void* 314 play_thread( void* param ) 315 { 316 Context* context = ( Context* )param; 317 318 while ( context->command != COMMAND_EXIT ) { 319 memset( ( void* )( context->buffer ), 0, BUFSIZE ); 320 321 switch ( context->command ) { 322 case COMMAND_PLAY_FIRST: 323 fill_buffer( ( void* )( context->buffer ), 324 &( context->wave[0] ) ); 325 break; 326 327 case COMMAND_PLAY_SECOND: 328 fill_buffer( ( void* )( context->buffer ), 329 &( context->wave[1] ) ); 330 break; 331 332 case COMMAND_PLAY_BOTH: 333 mix( ( void* )( context->buffer ), 334 &( context->wave[0] ) ); 335 break; 336 337 case COMMAND_STOP: 338 default: 339 break; 340 } 341 342 write( context->dsp, ( void* )( context->buffer ), BUFSIZE ); 343 } 344 345 pthread_exit( NULL ); 346 } 347 348 349 static void 350 fill_buffer( short* dst, WAVE* wave ) 351 { 352 int copied = 0; 353 354 while ( 1 ) { 355 int src_left = wave->len - wave->current; 356 int dst_left = BUFSIZE - copied; 357 int copy_len = src_left > dst_left ? dst_left : src_left; 358 359 if ( src_left == 0 ) { 360 fseek( wave->fp, wave->offset, SEEK_SET ); 361 wave->current = 0; 362 continue; 363 } 364 365 if ( dst_left == 0 ) { 366 break; 367 } 368 369 fread( ( void* )( dst + copied / sizeof( short ) ), 370 copy_len, 1, wave->fp ); 371 wave->current += copy_len; 372 copied += copy_len; 373 } 374 } 375 376 377 #define MIN( a, b ) ( ( (a) < (b) ) ? (a) : (b) ) 378 #define MAX( a, b ) ( ( (a) > (b) ) ? (a) : (b) ) 379 380 381 static void 382 mix( short* dst, WAVE* wave ) 383 { 384 short src[ BUFSIZE ]; 385 int copied; 386 int dst_index; 387 int i; 388 int j; 389 390 for ( i = 0; i < 2; i ++ ) { 391 memset( src, 0, BUFSIZE ); 392 copied = 0; 393 394 while ( 1 ) { 395 int src_left = wave[i].len - wave[i].current; 396 int dst_left = BUFSIZE - copied; 397 int copy_len = src_left > dst_left ? dst_left : src_left; 398 399 if ( src_left == 0 ) { 400 fseek( wave[i].fp, wave[i].offset, SEEK_SET ); 401 wave[i].current = 0; 402 continue; 403 } 404 405 if ( dst_left == 0 ) { 406 break; 407 } 408 409 fread( ( void* )src, copy_len, 1, wave[i].fp ); 410 411 for ( j = 0; j < copy_len / sizeof( short ); j ++ ) { 412 dst_index = copied / sizeof( short ) + j; 413 414 if ( src[j] < 0 ) { 415 dst[ dst_index ] = ( short )MAX( 416 (int)dst[ dst_index ] + (int)src[j], 417 (int)SHRT_MIN ); 418 } 419 else { 420 dst[ dst_index ] = ( short )MIN( 421 (int)dst[ dst_index ] + (int)src[j], 422 (int)SHRT_MAX ); 423 } 424 } 425 426 wave[i].current += copy_len; 427 copied += copy_len; 428 } 429 } 430 } 431 432 433 static void 434 shutdown( void ) 435 { 436 fclose( context.wave[0].fp ); 437 fclose( context.wave[1].fp ); 438 close( context.dsp ); 439 } 440 441 442 /* End of mix.c */
○ サンプルプログラムの説明
では、サンプルプログラムについて説明していきます。
- 7 行目
WAVE ファイル再生を別スレッドで行うために pthread を使います。そのため pthread.h をここでインクルードしています。
- 21 〜 32 行目
WAVE ファイル情報を格納するための構造体を定義しています。LECTURE 10 WAVE ファイルを再生する のサンプルプログラムで定義している構造体に、現在の再生位置を表す current メンバを追加しただけです。音を加算する際の始点として使用します。
- 35 〜 45 行目
WAVE ファイル再生スレッドで使用する変数を、一つの構造体にまとめて定義しています。そしてファイルスコープを持つ context 変数を宣言しています。
- 57 〜 97 行目 ( main() )
mix.c の main 関数です。各処理について以下で説明していきます。
- 68 〜 76 行目
68 〜 71 行目で、指定された 2 つの WAVE ファイルを読み込む自作関数 read_file() を実行しています。その後 73 〜 76 行目で、自作関数 setup_dsp() を実行し、/dev/dsp の初期化を行っています。いずれも詳細は後程説明します。
- 78 〜 90 行目
ここでは、WAVE ファイル再生スレッドの生成/実行開始を行っています。81 行目の pthread_create() で、WAVE ファイル再生スレッドを生成します。生成されたスレッドは自動的に実行開始されます。
int pthread_create( pthread_t* thread, pthread_attr_t* attr, void* ( *start_routine )( void* ), void* arg ); thread : 生成されたスレッド ID が格納される attr : スレッドに設定する属性を指定 start_routine : スレッドの実行関数 arg : start_routine 実行時に引数に渡されるポインタ
スレッド生成に成功した場合は 0 を、失敗した場合は EAGAIN を返します。本サンプルでは play_thread() という自作関数をスレッドの実行関数に指定し、前述の context 変数を引数に渡す様に指定しています。
pthread_create() の attr に NULL を指定した場合、生成されたスレッドの終了を別スレッドから pthread_join() にて待つことが出来ます。ここでは NULL を指定せず、同じ内容を pthread_attr_setdetachstate() を使って明示的に指定しています。
int pthread_attr_setdetachstate( pthread_attr_t* attr, int detachstate ); attr : 属性を格納する pthread_attr_t 型変数 detachstate : PTHREAD_CREATE_JOINABLE (pthread_join() で待機可能) か PTHREAD_CREATE_DETACHED (pthread_join() で待機不可)
成功した場合は 0 を、失敗した場合は EINVAL を返します。また、pthread_attr_t 型変数は pthread_attr_init() / pthread_attr_destroy() を使って初期化/解放を行う必要があります。
int pthread_attr_init( pthread_attr_t* attr ); int pthread_attr_destroy( pthread_attr_t* attr );
- 92 〜 94 行目
main() を実行しているスレッドでは、自作関数の show_menu() によってメニュー表示/ユーザ入力の取得を行います。show_menu() が終了した(ユーザが終了を指示した)場合は、pthread_join() にて WAVE 再生スレッドの終了を待機した上で、自作関数の shutdown() を実行して /dev/dsp 等のリソースを解放しています。
- 100 〜 226 行目 ( read_file() )
指定された 2 つの WAVE ファイルの読み込みを行います。内容はほとんど LECTURE 10 WAVE ファイルを再生する のサンプルプログラム wave.c の wave_read_file() と同じですので、具体的な読み込み処理についてはそちらを参照してください。追加事項として、156 〜 173 行目にて PCM / 16 bits / Monoral の WAVE ファイルであるかどうかを確認し、そうでなければ処理失敗としています。また、208 〜 212 行目にて、読み込んだ 2 つの WAVE ファイルのサンプリングレートが一致しない場合も処理失敗としています。
- 229 〜 270 行目 ( setup_dsp() )
これまでのサンプルプログラムと同じ様に ioctl() で /dev/dsp の設定を行っています。LECTURE 10 WAVE ファイルを再生する のサンプルプログラム wave.c と異なり、245 〜 249 行目の ioctl( SNDCTL_DSP_SETFRAGMENT ) でフラグメントの設定を行っています。設定している値は 232 行目の 0x0004000c ですが、これはフラグメントのサイズを 4096 バイトとし、フラグメント数を 4 とする、という意味です( LECTURE 5 リアルタイム性を考慮する を参照してください )。
- 273 〜 310 行目 ( show_menu() )
main() の実行スレッドにて、ユーザ入力を取得するためのメニューを表示します。そして入力に応じて、ファイル冒頭で定義してあるマクロ COMMAND_XXXXX を WAVE 再生スレッド用の context 変数に設定します。ユーザが 99 を入力すると、ループを抜けて関数が終了します。
- 313 〜 346 行目 ( play_thread() )
WAVE ファイル再生スレッドの実行関数です。ループ処理ではまず再生用バッファを初期化し、ユーザ指定のコマンドに応じて処理を行います。322 〜 325 行目では、最初に指定した WAVE ファイルのデータを自作関数 fill_buffer() を使って再生用バッファに転送します。327 〜 330 行目では、2 番目に指定した WAVE ファイルについて同様に fill_buffer() を使って再生用バッファに転送します。fill_buffer() については後程説明します。332 〜 335 行目は、2 つの WAVE ファイルを自作関数 mix() で加算した結果を再生用バッファに転送します。mix() についても後程説明します。
再生用バッファの内容を構築した後、342 行目で /dev/dsp に再生データを書き込んでいます。なお、ユーザによって COMMAND_STOP が指定されている場合も、無音状態のデータを書き込んでいます。
ユーザによって COMMAND_EXIT が指定された場合はループを抜けて、345 行目の pthread_exit() で WAVE ファイル再生スレッドを終了しています。
void pthread_exit( void* retval ); retval: スレッドの終了コード
- 349 〜 374 行目 ( fill_buffer() )
指定バッファ ( dst ) に WAVE ファイルデータを転送します。
WAVE ファイルデータの転送開始位置は wave->current で参照できます。355 〜 357 行目で、WAVE ファイルデータの残りと指定バッファの残りを比較し、一度に転送するバイト長を計算しています。WAVE ファイルデータの終端に到達した場合は、359 〜 363 行目で wave->current = 0; とすることで先頭に戻し、転送長の計算からやり直します。指定バッファ終端に到達している場合は、その時点で終了します(365 〜 367 行目)。
一度の転送長を計算したら、369 行目の fread() で WAVE ファイルデータを指定バッファに転送します。fread() の第一引数(転送先指定)が dst + copied / sizeof( short ) の様になっているのは、dst が short* であるためアドレスが sizeof( short ) ずつ増分されてしまうからです。
- 377 〜 378 行目 ( MIN(), MAX() )
mix() で使用するマクロです。MIN() は小さい方の値を、MAX() は大きい方の値を返します。
- 381 〜 430 行目 ( mix() )
2 つの WAVE ファイルデータの加算を行います。WAVE ファイルデータ → src(関数内の自動変数)→ dst(指定バッファ)の順に転送を行います。まず 1 つめの WAVE ファイルデータで指定バッファを満たしてから、2 つめの WAVE ファイルデータを dst に転送しています。
395 〜 397 行目で一度に転送するバイト長を計算しています。WAVE ファイルデータの終端に到達した場合は、399 〜 403 行目で wave->current = 0; とすることで先頭に戻し、転送長の計算からやりなおします。指定バッファ終端に到達している場合は、その時点で while() を抜けます(405 〜 407 行目。次の WAVE ファイルデータの処理に移るか、加算処理終了のいずれかとなる)。
409 行目で fread() により WAVE ファイルデータを関数内の自動変数 src に転送します。dst ではなくまず src に転送するのは、ファイルから一気に読み込んだ上で加算処理を行いたいからです。411 〜 424 行目が実際の加算処理です。既に dst にあるデータと src を加算し、結果が SHRT_MIN より小さくなる場合は SHRT_MIN に、SHRT_MAX より大きくなる場合は SHRT_MAX で飽和させます(16 ビット符号付フォーマットのため)。
- 433 〜 439 行目 ( shutdown() )
リソース解放処理です。2 つの WAVE ファイルのファイルポインタを解放し、/dev/dsp も解放しています。
以上で mix.c の説明は終わりです。