그래픽 & 멀티미디어
이번에는 wav 파일을 출력해 보겠습니다. wav 파일을 출력하기 위해서는 파일의 앞 부분에 있는 헤더파일의 내용을 확인하여 사운드 카드의 출력을 스테레오나 모노를 설정해 주어야 하고, 녹음된 비트 레이트와 비트 크기에 맞추어 설정해 주어야 합니다. 이를 위해 이전 예제에서 int soundcard_open_file( char *_filename) 함수를 추가했습니다.
_filename은 출력할 wav 파일 이름이 되며, soundcard_open_file()은 _filename의 헤더를 읽어 들여서 사운드 카드의 출력 방법을 설정합니다. 즉, 헤더파일의 내용에 따라 사운드카드를 설정할 뿐 재생은 하지 않습니다. ^^
그러므로 wav를 출력하려면 아래와 같은 순서로 처리하면 됩니다.
- 사운드카드를 open한 후
- wav 파일의 헤더 내용으로 사운드 카드를 설정하고,
- wav의 데이터 부분을 읽어서 사운트 카드로 출력
int main( int argc, char **argv ) { fd_soundcard = soundcard_open( O_WRONLY); // 사운드카드 open soundcard_set_volume( SOUND_MIXER_WRITE_ALTPCM, 100, 100); // 볼륨 조절 fd_wav_file = soundcard_open_file( argv[1]); // 출력할 wave 파일 open while( 1) { read( fd_wav_file, &sound_buff[0], soundcard_get_buff_size()); // wave 파일 내용을 읽어서 write( fd_soundcard, &sound_buff[0], read_size ); // 사운드카드로 출력 } : }
자, 사용하는 방법은 알았고, 새로 추가된 soundcard_set_volume() 함수의 내용을 보겠습니다. 이 함수에서 wave 파일의 헤더부분을 처리하므로 wave의 헤더 구조를 먼저 알아야 겠습니다.
필드 이름 | 필드 값 | 크기 (bytes) |
Chunk ID | 'RIFF' | 4 |
Chunk size | 4 | |
Format | 'WAVE' | 4 |
SubChunk 1 ID | 'fmt ' | 4 |
SubChunk 1 Size | 4 | |
Audio Format | 1 | 2 |
Num Channels | 2 | |
Sample Rate | 4 | |
Byte Rate | 4 | |
Block Align | 2 | |
Bits Per Sample | 2 | |
SubChunk 2 ID | 'data' | 4 |
SubChunk 2 Size | 4 | |
data | SubChunk 2 Size |
이제 이 포맷 정보를 바탕으로 wave의 헤더 파일을 읽어 들이겠습니다. 헤더 정보 중에 Wave 정보만 구하기 위해 위의 표에서 분홍색으로 표시한 부분을 스트럭쳐로 구성했습니다.
typedef struct { unsigned char chunk_id[4]; unsigned long size; unsigned short tag; unsigned short channels; unsigned long rate; unsigned long avr_samples; unsigned short align; unsigned short data_bit; } wav_info_t ;
아래는 soundcard_open_file() 함수로 소스에서 설명에 필요한 내용만 뽑고, 주석으로 설명을 달았습니다.
int soundcard_open_file( char *_filename) { memset( buff, 0 , BUFF_SIZE); // 헤더 부분을 읽어 들입니다. read_size = read( fd_wavfile, buff, BUFF_SIZE); // wave 파일은 헤더 부분에 RIFF와 WAVE라는 문자열을 가지고 있습니다. // 이 문자열이 없다면 다른 포맷으로 생각하고 진행을 중지합니다. if ( 0 == memmem( buff, BUFF_SIZE, "RIFF", 4 )) // "RIFF" 문자열이 있는가를 검사한다. if ( 0 == memmem( buff, BUFF_SIZE, "WAVE", 4 )) // "WAVE" 문자열이 있는가를 검사한다. // 헤더 부분 중에 스테레오 여부, 비트 레이트 등의 wave 파일에 대한 정보는 // "fmt " 문자열 다음에 있습니다. 그러므로 이후의 값으로 wav_info 스트럭쳐 크기로 // 읽어 들여 wav의 설정값을 읽어 들입니다. p_wav_info = (wav_info_t *)memmem( buff, BUFF_SIZE, "fmt ", 4 ); // 포맷 정보를 구한다. if ( NULL == p_wav_info) memcpy( &wav_info, p_wav_info, sizeof(wav_info_t) ); // wave 파일의 실제 사운드부분은 헤더에서 "data" 문자열과 4바이트의 // 데이터 바이트 크기 다음부터 시작합니다. // 그러므로 실제 데이터의 시작 위치는 헤더를 읽어 들인 buff에서 "data" 문자열이 // 시작하는 주소에서 "data"와 4바이트의 숫자 데이터 다음이므로 // +8을 한 위치가 되겠습니다. ptr = (unsigned char *)memmem( buff, BUFF_SIZE, "data", 4 ); ptr += 8; // 이렇게 buff에서 ( "data" 문자열의 시작 위치 + 8)을 뺀 값은 파일에서 // 실제 사운드 데이터의 시작 부분이므로 이 값을 data_offset으로 기억해 두면 // 음악을 재생 후에 data_offset으로 되감기를 할 수 있습니다. data_offset = (unsigned long)( ptr - buff ); lseek( fd_wavfile, data_offset , SEEK_SET ); soundcard_set_stereo ( wav_info.channels >= 2 ? 1 : 0); soundcard_set_data_bit_size( wav_info.data_bit ); soundcard_set_bit_rate ( wav_info.rate ); return fd_wavfile; }
이와 같은 내용으로 구성한 전체 소스 내용입니다.
#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <stdlib.h> #include <stdarg.h> #include <sys/ioctl.h> #include <linux/soundcard.h> #define SCERR_NO_PROC_DEVICE -1 // /proc/device 가 존재하지 않는다. #define SCERR_DRIVER_NO_FIND -2 // /proc/device 에서 드라이버를 찾지 못함 #define SCERR_NO_SOUNDCARD -3 // 사운드 카드가 없음 #define SCERR_NO_MIXER -4 // 믹서가 없음 #define SCERR_OPEN_SOUNDCARD -5 // 사운드 카드를 여는데 실패 했다. #define SCERR_OPEN_MIXER -6 // 사운드 카드를 여는데 실패 했다. #define SCERR_PRIVILEGE_SOUNDCARD -7 // 사운드 카드를 사용할 권한이 없음 #define SCERR_PRIVILEGE_MIXER -8 // 미서를 사용할 권한이 없음 #define SCERR_NO_FILE -10 // 파일이 없음 #define SCERR_NOT_OPEN -11 // 파일이 열 수 없 #define SCERR_NOT_WAV_FILE -12 // WAV 파일이 아님 #define SCERR_NO_wav_info -13 // WAV 포맷 정보가 없음 #define BUFF_SIZE 1024 #define DSP_DEVICE_NAME "/dev/dsp" #define MIXER_DEVICE_NAME "/dev/mixer" #define TRUE 1 typedef struct { unsigned char chunk_id[4]; unsigned long size; unsigned short tag; unsigned short channels; unsigned long rate; unsigned long avr_samples; unsigned short align; unsigned short data_bit; } wav_info_t; static int fd_soundcard = -1; static int fd_mixer = -1; // volume 제어용 파일 디스크립터 static unsigned long data_offset = 0; // wav 플레이용 파일 오프셋 //------------------------------------------------------------------------------ // 설명 : 메시지를 출력하고 프로그램 종료 // 인수 : format : 출력할 문자열 형식 예) "%s%d" // ... : 형식에 필요한 인수 //------------------------------------------------------------------------------ void printx( const char *_format, ...) { char *bar = "---------------------------------------------------------"; va_list parg; int count; printf( "%s:ERRORn", bar); va_start( parg,_format); count = vprintf(_format, parg); va_end( parg); printf( "n%sn", bar); exit( -1); } //------------------------------------------------------------------------------ // 설명 : 사운드 출력을 위한 버퍼 크기를 구한다. // 반환 : 사운드 출력 버퍼 크기 //------------------------------------------------------------------------------ int soundcard_get_buff_size( void) { int size = 1024; if ( 0 <= fd_soundcard) { ioctl( fd_soundcard, SNDCTL_DSP_GETBLKSIZE, &size); if ( size < 1024 ) size = 1024; } return size; } //------------------------------------------------------------------------------ // 설명 : 사운드 카드 출력을 스테레오로 설정 // 인수 : _enable - 1 : 스테레오 // 0 : 모노 //------------------------------------------------------------------------------ void soundcard_set_stereo( int _enable) { if ( 0 <= fd_soundcard) ioctl( fd_soundcard, SNDCTL_DSP_STEREO, &_enable); } //------------------------------------------------------------------------------ // 설명 : 사운드 카드 출력 비트 레이트를 지정 // 인수 : _rate - 설정할 비트 레이트 //------------------------------------------------------------------------------ void soundcard_set_bit_rate( int _rate) { if ( 0 <= fd_soundcard) ioctl( fd_soundcard, SNDCTL_DSP_SPEED, &_rate ); } //------------------------------------------------------------------------------ // 설명 : 출력 사운드의 비트 크기 지정 // 인수 : _size - 사운드 데이터 비트 크기 //------------------------------------------------------------------------------ void soundcard_set_data_bit_size( int _size) { if ( 0 <= fd_soundcard) ioctl( fd_soundcard, SNDCTL_DSP_SAMPLESIZE, &_size); } //------------------------------------------------------------------------------ // 설명 : sound card 볼륨 지정 // 인수 : _channel - 변경할 채널 번호 // 헤드폰 볼륨 : SOUND_MIXER_WRITE_VOLUME // PCM 볼륨 : SOUND_MIXER_WRITE_PCM // 스피커 볼륨 : SOUND_MIXER_WRITE_SPEAKER // 라인 볼륨 : SOUND_MIXER_WRITE_LINE // 마이크 볼륨 : SOUND_MIXER_WRITE_MIC // CD 볼륨 : SOUND_MIXER_WRITE_CD // PCM2 볼륨 : SOUND_MIXER_WRITE_ALTPCM 주) ESP-MMI의 스피커 음량 // IGain 볼륨 : SOUND_MIXER_WRITE_IGAIN // 라인 1 볼륨 : SOUND_MIXER_WRITE_LINE1 // _left : 볼륨의 좌측 값 또는 하위 값 // _right: 볼륨의 우측 값 또는 상위 값 //------------------------------------------------------------------------------ void soundcard_set_volume( int _channel, int _left, int _right ) { int volume; if ( 120 < _left ) _left = 120; if ( 120 < _right ) _right = 120; volume = 256 *_right +_left; ioctl( fd_mixer, _channel, &volume ); } //-------------------------------------------------------------------- // 설명: wav 파일을 재생하기 위해 열기 // 상세: wav 파일만 사용이 가능 // 인수: _filename : wav 파일 이름 // 반환: 0 < 파일 열기에 성공 // 0 > 파일 열기 실패 및 에러 코드 //-------------------------------------------------------------------- int soundcard_open_file( char *_filename) { int fd_wavfile; int read_size; unsigned char buff[BUFF_SIZE+5]; wav_info_t *p_wav_info; wav_info_t wav_info; unsigned char *ptr; if ( 0 != access(_filename, R_OK ) ) // 파일이 없음 return SCERR_NO_FILE; fd_wavfile = open(_filename, O_RDONLY ); if ( 0 > fd_wavfile) // 파일 열기 실패 return SCERR_NOT_OPEN; // 헤더 부분 처리 memset( buff, 0 , BUFF_SIZE); read_size = read( fd_wavfile, buff, BUFF_SIZE); if ( 0 == memmem( buff, BUFF_SIZE, "RIFF", 4 )) // "RIFF" 문자열이 있는가를 검사한다. return SCERR_NOT_WAV_FILE; if ( 0 == memmem( buff, BUFF_SIZE, "WAVE", 4 )) // "WAVE" 문자열이 있는가를 검사한다. return SCERR_NOT_WAV_FILE; p_wav_info = (wav_info_t *)memmem( buff, BUFF_SIZE, "fmt ", 4 ); // 포맷 정보를 구한다. if ( NULL == p_wav_info) return SCERR_NO_wav_info; memcpy( &wav_info, p_wav_info, sizeof(wav_info_t) ); // printf( "CHANNEL = %dn", (int) wav_info.channels ); // printf( "DATA BITS = %dn", (int) wav_info.data_bit); // printf( "RATE = %dn", (int) wav_info.rate); ptr = (unsigned char *)memmem( buff, BUFF_SIZE, "data", 4 ); ptr += 8; data_offset = (unsigned long)( ptr - buff ); lseek( fd_wavfile, data_offset , SEEK_SET ); // 파일 읽기 위치를 데이터 위치로 이동 시킨다. soundcard_set_stereo ( wav_info.channels >= 2 ? 1 : 0); // sound 스테레오 모드 활성화 soundcard_set_data_bit_size( wav_info.data_bit ); // sound 데이터 크기 지정 soundcard_set_bit_rate ( wav_info.rate ); // sound 비트 레이트 지정 return fd_wavfile; } //-------------------------------------------------------------------- // 설명: 사운드카드 사용을 종료하기 위해 닫기 //-------------------------------------------------------------------- void soundcard_close( void) { close( fd_soundcard); close( fd_mixer ); } //-------------------------------------------------------------------- // 설명: 사운드카드 사용을 위한 열기 // 인수: _mode - PLAYER : 재생 전용 // 인수: _mode - RECODER : 녹음 전용 // 인수: _mode - AUDIO : 재생과 녹음 모두 실행 // 반환: 사운드카드 파일 디스크립터 //-------------------------------------------------------------------- int soundcard_open(int _mode) { char *dsp_devname = DSP_DEVICE_NAME; char *mixer_devname = MIXER_DEVICE_NAME; // 사운드 카드 장치 열기 if ( 0 != access( dsp_devname, W_OK ) ) return SCERR_PRIVILEGE_SOUNDCARD; fd_soundcard = open( dsp_devname,_mode); if ( 0 > fd_soundcard) return SCERR_OPEN_SOUNDCARD; // MIXER 열기 if ( 0 != access( mixer_devname, W_OK ) ) return SCERR_PRIVILEGE_MIXER; fd_mixer = open( mixer_devname, O_RDWR¦O_NDELAY ); if ( 0 > fd_soundcard) return SCERR_OPEN_MIXER; return fd_soundcard; } //-------------------------------------------------------------------- // 설명: 사운드 카드를 이용하여 10초간 녹음하여 test.wav로 저장한다. //-------------------------------------------------------------------- int main( int argc, char **argv ) { int fd_soundcard; int fd_wav_file; char *sound_buff; // 사운드 출력을 위한 버퍼 int sound_buff_size; int read_size; if ( 2 > argc) printx( "usage: ./exe [wave file name]"); fd_soundcard = soundcard_open( O_WRONLY); switch( fd_soundcard) { case SCERR_NO_PROC_DEVICE : printx( "/proc/device가 없음"); case SCERR_NO_SOUNDCARD : printx( "사운드 카드가 없음"); case SCERR_NO_MIXER : printx( "믹서가 없음"); case SCERR_PRIVILEGE_SOUNDCARD : printx( "사운드 카드를 사용할 권한이 없음"); case SCERR_PRIVILEGE_MIXER : printx( "믹서 장치를 사용할 권한이 없음"); case SCERR_OPEN_SOUNDCARD : printx( "사운드 카드를 열 수 없음"); case SCERR_OPEN_MIXER : printx( "믹서 장치를 열수 없음"); } soundcard_set_volume( SOUND_MIXER_WRITE_ALTPCM, 100, 100); soundcard_set_volume( SOUND_MIXER_WRITE_MIC , 80, 80); soundcard_set_volume( SOUND_MIXER_WRITE_IGAIN , 60, 60); sound_buff_size = soundcard_get_buff_size(); sound_buff = ( char *)malloc( sound_buff_size); fd_wav_file = soundcard_open_file( argv[1]); // 출력할 wave 파일을 열기 switch( fd_wav_file) { case SCERR_NO_FILE : printx( "WAV 파일이 없음"); case SCERR_NOT_OPEN : printx( "WAV 파일을 열 수 없음"); case SCERR_NOT_WAV_FILE : printx( "WAV 파일이 아님"); case SCERR_NO_wav_info : printx( "WAV 정보가 없음"); } while( 1) { read_size = read( fd_wav_file, &sound_buff[0], soundcard_get_buff_size()); if( 0 < read_size) { write( fd_soundcard, &sound_buff[0], read_size ); } else break; } close( fd_wav_file); soundcard_close(); return 0; }
컴파일 하신 후, 실행파일 다음에 wav 파일을 추가하여 실행하시면 되겠습니다. 컴파일하여 만든 실행 파일 이름이 wav_player 라면 아래와 같이 실행하시면 됩니다. 저는 MS Windows의 WINDOWS 디렉토리에 있는 waltz.wav를 출력해 보았습니다.
]$ ./wav_player waltz.wav
다음 시간에는 마이크를 이용하여 wave 파일로 녹음해 보겠습니다.