#include #include #include #include #include #include #include #include #include #define COMMAND_STOP 0 #define COMMAND_PLAY_FIRST 1 #define COMMAND_PLAY_SECOND 2 #define COMMAND_PLAY_BOTH 3 #define COMMAND_EXIT 4 #define BUFSIZE 4096 /* WAVE ファイルの情報を格納する構造体 */ typedef struct { FILE* fp; /* ファイル構造体 */ short is_pcm; /* PCM フォーマットの場合は 1、それ以外は 0 */ short channel; /* モノラルの場合は 1、ステレオの場合は 2 */ int rate; /* サンプリング周波数 */ short bits; /* 量子化ビット数 */ long offset; /* ファイル先頭から PCM データまでのオフセット */ int len; /* PCM データ部の長さ */ int current; /* 現在の再生位置 */ } WAVE; /* 再生に必要なデータをまとめた構造体 */ typedef struct { pthread_t thread_id; int command; int dsp; WAVE wave[2]; short buffer[ BUFSIZE / sizeof( short ) ]; } Context; static Context context; static int read_file( char** fnames, WAVE* wave ); static int setup_dsp( int* fd, WAVE* wave ); static void show_menu( void ); static void* play_thread( void* param ); static void fill_buffer( short* dst, WAVE* wave ); static void mix( short* dst, WAVE* wave ); static void shutdown(); int main( int argc, char** argv ) { pthread_attr_t attr; int ret; if ( argc != 3 ) { fprintf( stderr, "usage : ./wave filename1 filename2\n" ); return 1; } if ( read_file( &( argv[1] ), &( context.wave[0] ) ) != 0 ) { fprintf( stderr, "Failed to read wave files.\n" ); return 1; } if ( setup_dsp( &( context.dsp ), &( context.wave[0] ) ) != 0 ) { fprintf( stderr, "Failed to setup /dev/dsp.\n" ); return 1; } pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); ret = pthread_create( &( context.thread_id ), &attr, play_thread, ( void* )( &context ) ); pthread_attr_destroy( &attr ); if ( ret ) { fprintf( stderr, "Failed to create thread.\n" ); shutdown(); return 1; } show_menu(); pthread_join( context.thread_id, NULL ); shutdown(); return 0; } static int read_file( char** fnames, WAVE* wave ) { char buf[32]; int len; int i; for ( i = 0; i < 2; i ++ ) { if ( ( wave[i].fp = fopen( fnames[i], "r" ) ) == NULL ) { fprintf( stderr, "Failed to open %s\n", fnames[i] ); goto err; } /* * 先頭 4 バイトが "RIFF" であることを確認 * 更に 4 バイトスキップしておく */ fread( buf, 8, 1, wave[i].fp ); if ( strncmp( buf, "RIFF", 4 ) != 0 ) { fprintf( stderr, "Specified file is not RIFF file.\n" ); goto err; } /* 次の 4 バイトが "WAVE" であることを確認 */ fread( buf, 4, 1, wave[i].fp ); if ( strncmp( buf, "WAVE", 4 ) != 0 ) { fprintf( stderr, "Specified file is not WAVE file.\n" ); goto err; } /* fmt チャンクを探す */ while ( 1 ) { fread( buf, 8, 1, wave[i].fp ); len = *( int* )( &buf[4] ); if ( strncmp( buf, "fmt ", 4 ) != 0 ) { if ( fseek( wave[i].fp, len, SEEK_CUR ) == -1 ) { fprintf( stderr, "Failed to find fmt.\n" ); goto err; } } else { break; } } fread( buf, len, 1, wave[i].fp ); wave[i].is_pcm = *( ( short* )( &buf[0] ) ); wave[i].channel = *( ( short* )( &buf[2] ) ); wave[i].rate = *( ( int* )( &buf[4] ) ); wave[i].bits = *( ( short* )( &buf[14] ) ); /* * PCM / 16 bits / Monoral 以外の WAVE データについては * 本プログラムでは対象外 */ if ( wave[i].is_pcm != 1 ) { fprintf( stderr, "WAVE file must be PCM format.\n" ); goto err; } if ( wave[i].bits != 16 ) { fprintf( stderr, "WAVE file must be 16 bits .\n" ); goto err; } if ( wave[i].channel != 1 ) { fprintf( stderr, "WAVE file must be monoral.\n" ); goto err; } /* data チャンクを探す */ while ( 1 ) { fread( buf, 8, 1, wave[i].fp ); len = *( int* )( &buf[4] ); if ( strncmp( buf, "data", 4 ) != 0 ) { if ( fseek( wave[i].fp, len, SEEK_CUR ) == -1 ) { fprintf( stderr, "Failed to find data.\n" ); goto err; } } else { break; } } wave[i].len = len; if ( ( wave[i].offset = ftell( wave[i].fp ) ) == -1 ) { fprintf( stderr, "Failed to find offset of PCM data.\n" ); goto err; } printf( "\n" ); printf( "WAVE file format of %s:\n", fnames[i] ); printf( " rate = %d\n", wave[i].rate ); printf( " channel = %d\n", wave[i].channel ); printf( " bits = %d\n", wave[i].bits ); printf( " offsets = %ld\n", wave[i].offset ); printf( " length = %d\n", wave[i].len ); printf( "\n" ); } if ( wave[0].rate != wave[1].rate ) { fprintf( stderr, "Specified 2 WAVE files " "must have the same sampling rate.\n" ); goto err; } return 0; err: if ( wave[0].fp != NULL ) { fclose( wave[0].fp ); } if ( wave[1].fp != NULL ) { fclose( wave[1].fp ); } return 1; } static int setup_dsp( int* dsp, WAVE* wave ) { int fragment = 0x0004000c; /* size = 4096 bytes, num of fragments = 4. */ int format = AFMT_S16_LE; int rate; int channel; rate = ( int )wave[0].rate; channel = ( int )wave[0].channel; if ( ( *dsp = open( "/dev/dsp", O_WRONLY ) ) == -1 ) { perror( "open()" ); return 1; } if ( ioctl( *dsp, SNDCTL_DSP_SETFRAGMENT, &fragment ) == -1 ) { perror( "ioctl( SNDCTL_DSP_SETFRAGMENT )" ); close( *dsp ); return 1; } if ( ioctl( *dsp, SNDCTL_DSP_SETFMT, &format ) == -1 ) { perror( "ioctl( SNDCTL_DSP_SETFMT )" ); close( *dsp ); return 1; } if ( ioctl( *dsp, SNDCTL_DSP_SPEED, &rate ) == -1 ) { perror( "ioctl( SNDCTL_DSP_SPEED )" ); close( *dsp ); return 1; } if ( ioctl( *dsp, SNDCTL_DSP_CHANNELS, &channel ) == -1 ) { perror( "ioctl( SNDCTL_DSP_CHANNELS )" ); close( *dsp ); return 1; } return 0; } static void show_menu( void ) { char line[4]; int command; while ( 1 ) { printf( "Select menu ...\n" ); printf( " 1. Play first specified wave file only.\n" ); printf( " 2. Play second specified wave file only.\n" ); printf( " 3. Play both wave files simultaneously.\n" ); printf( " 4. Stop playing.\n" ); printf( " 99. Exit.\n" ); printf( "\n" ); printf( "Command ? " ); fgets( line, sizeof( line ), stdin ); sscanf( line, "%d\n", &command ); switch ( command ) { case 1: context.command = COMMAND_PLAY_FIRST; break; case 2: context.command = COMMAND_PLAY_SECOND; break; case 3: context.command = COMMAND_PLAY_BOTH; break; case 4: context.command = COMMAND_STOP; break; case 99: context.command = COMMAND_EXIT; return; } } } static void* play_thread( void* param ) { Context* context = ( Context* )param; while ( context->command != COMMAND_EXIT ) { memset( ( void* )( context->buffer ), 0, BUFSIZE ); switch ( context->command ) { case COMMAND_PLAY_FIRST: fill_buffer( ( void* )( context->buffer ), &( context->wave[0] ) ); break; case COMMAND_PLAY_SECOND: fill_buffer( ( void* )( context->buffer ), &( context->wave[1] ) ); break; case COMMAND_PLAY_BOTH: mix( ( void* )( context->buffer ), &( context->wave[0] ) ); break; case COMMAND_STOP: default: break; } write( context->dsp, ( void* )( context->buffer ), BUFSIZE ); } pthread_exit( NULL ); } static void fill_buffer( short* dst, WAVE* wave ) { int copied = 0; while ( 1 ) { int src_left = wave->len - wave->current; int dst_left = BUFSIZE - copied; int copy_len = src_left > dst_left ? dst_left : src_left; if ( src_left == 0 ) { fseek( wave->fp, wave->offset, SEEK_SET ); wave->current = 0; continue; } if ( dst_left == 0 ) { break; } fread( ( void* )( dst + copied / sizeof( short ) ), copy_len, 1, wave->fp ); wave->current += copy_len; copied += copy_len; } } #define MIN( a, b ) ( ( (a) < (b) ) ? (a) : (b) ) #define MAX( a, b ) ( ( (a) > (b) ) ? (a) : (b) ) static void mix( short* dst, WAVE* wave ) { short src[ BUFSIZE ]; int copied; int dst_index; int i; int j; for ( i = 0; i < 2; i ++ ) { memset( src, 0, BUFSIZE ); copied = 0; while ( 1 ) { int src_left = wave[i].len - wave[i].current; int dst_left = BUFSIZE - copied; int copy_len = src_left > dst_left ? dst_left : src_left; if ( src_left == 0 ) { fseek( wave[i].fp, wave[i].offset, SEEK_SET ); wave[i].current = 0; continue; } if ( dst_left == 0 ) { break; } fread( ( void* )src, copy_len, 1, wave[i].fp ); for ( j = 0; j < copy_len / sizeof( short ); j ++ ) { dst_index = copied / sizeof( short ) + j; if ( src[j] < 0 ) { dst[ dst_index ] = ( short )MAX( (int)dst[ dst_index ] + (int)src[j], (int)SHRT_MIN ); } else { dst[ dst_index ] = ( short )MIN( (int)dst[ dst_index ] + (int)src[j], (int)SHRT_MAX ); } } wave[i].current += copy_len; copied += copy_len; } } } static void shutdown( void ) { fclose( context.wave[0].fp ); fclose( context.wave[1].fp ); close( context.dsp ); } /* End of mix.c */