그래픽 & 멀티미디어
이제 사운드 카드로 소리를 담아 내는 방법을 알게 되었으니 파일로 저장하는 방법에 대해 알아 보겠습니다. 가장 흔히 사용하는 wav 파일 형식을 이용하겠습니다. 사실 이 방법도 문서를 통하여 학습하기 보다는 Warren W. Gay라는 분이 만든 http://www.hitsquad.com/smm/programs/WavPlay/의 WavPlay 1.0을 분석하여 알게되었습니다.
그러다 보니 역시 부족한 부분과 잘못된 부분이 있을 것으로 생각됩니다. 혹 틀린 부분이나 부족한 부분이 있더라도 양해를 부탁드리며 그냥 지나치지 마시고 지적해주시고 알려 주시면 감사하겠습니다.
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 파일을 출력할 때에는 분홍색 부분만 스트럭쳐를 구성해서 헤더 부분을 읽어 내었는데요, 저장하기 위해서는 모든 내용을 스트럭쳐로 구성하겠습니다. 역시 이부분은 http://www.hitsquad.com/smm/programs/WavPlay/에 올려진 WavPlay 1.0 의 소스 부분에서 이름만 조금 수정했습니다.
typedef struct { char RiffID [4] ; u_long RiffSize ; char WaveID [4] ; char FmtID [4] ; u_long FmtSize ; u_short wFormatTag ; u_short nChannels ; u_long nSamplesPerSec ; u_long nAvgBytesPerSec ; u_short nBlockAlign ; u_short wBitsPerSample ; char DataID [4] ; u_long nDataBytes ; } wave_header_t; static wave_header_t wave_header = { { 'R', 'I', 'F', 'F' }, 0, { 'W', 'A', 'V', 'E' }, { 'f', 'm', 't', ' ' }, 16, PCM_WAVE_FORMAT, 0, 0, 0, 0, 0, { 'd', 'a', 't', 'a' }, 0 };
어차피 Wave 파일을 저장할 것이므로 Wave를 위한 초기값을 지정했습니다.
Wave 파일로 저장하기 위해서는 Wave 파일을 어떻게 만들었는지 wav의 속성을 담은 헤더 부분을 먼저 저장해야 겠습니다. 그래서 녹음 을 스테레오로 녹음했는지 모노로 녹음했는지에 대한 정보나 비트 레이트, 샘플링 비트 수 같은 정보를 담아서 헤더 정보로 먼저 저장합니다.
당연히 헤더 정보는 미리 사운드카드에 설정한 값이 되겠지요. 예제에서도 먼저 마이크로 녹음하는 방법을 모노에, 8000hz, 16비트 녹음으로 설정했습니다.
soundcard_set_stereo( 0); soundcard_set_data_bit_size( 16); soundcard_set_bit_rate( 8000);
그러므로 이와 같이 wave 파일의 헤더 정보를 저장합니다.
soundcard_write_wav_header( fd_wavfile, 1, 8000, 16, 10 * 8000);
인수로 10 * 8000한 이유는 10초간의 녹음 파일임을 말합니다. 이제 sound_write_wav_header() 함수의 내용을 보겠습니다.
int soundcard_write_wav_header( int _fd, int _channels, u_long _samplerate, int _sampbits, u_long _samples) { u_long databytes ; u_short blockalign ; if ( _fd < 0 ) printx( "bad file descriptor?"); _sampbits = (_sampbits == 16) ? 16 : 8 ; blockalign = ( (_sampbits == 16) ? 2 : 1) * _channels ; databytes = _samples * (u_long) blockalign ; wave_header.RiffSize = sizeof ( wave_header_t) + databytes - 8 ; wave_header.wFormatTag = PCM_WAVE_FORMAT ; wave_header.nChannels = _channels ; wave_header.nSamplesPerSec = _samplerate ; wave_header.nAvgBytesPerSec = _samplerate * (u_long) blockalign ; wave_header.nBlockAlign = blockalign ; wave_header.wBitsPerSample = _sampbits ; wave_header.nDataBytes = databytes; if ( sizeof ( wave_header_t) != write (_fd, &wave_header, sizeof ( wave_header_t))) printx( "write header errror"); return 0 ; };
대부분이 wave 파일의 헤더에 맞추어 스트럭쳐의 내용을 구성하고 스트럭쳐를 그대로 파일에 저장했습니다. 주의해서 보실 것은 인수로 받은 _samples는 녹음 시간(초)* 샘플링 비트 레이트로서 Hz 값입니다. 헤더 정보에는 사운드 부분의 바이트 개수가 들어 가기 때문에 _samples에 스테레오면 2배를 모노이면 _samples 값으로 사운드 데이터 부분의 바이크 개수를 계산하고 헤더에 저장합니다.
int soundcard_write_wav_header( int _fd, int _channels, u_long _samplerate, int _sampbits, u_long _samples) { _sampbits = (_sampbits == 16) ? 16 : 8 ; blockalign = ( (_sampbits == 16) ? 2 : 1) * _channels ; databytes = _samples * (u_long) blockalign ; : wave_header.nDataBytes = databytes; if ( sizeof ( wave_header_t) != write (_fd, &wave_header, sizeof ( wave_header_t))) : };
헤더를 저장했으므로 이전 방법에 따라 마이크로 부터 사운드 값을 입력받아 그대로 파일로 저장하면 됩니다.
soundcard_write_wav_header( fd_wavfile, 1, 8000, 16, 10 * 8000); while( TRUE) { : read_size = read( fd_soundcard, sound_buff, sound_buff_size); write( fd_wavfile, sound_buff, read_size); : }
전체 소스를 올립니다. 컴파일하신 후에 실행하시면 test.wav 파일이 생성됩니다. 생성된 파일을 이전 시간의 wav_player 프로그램으로 재생해 보십시오. 예제라서 10초간 녹음하는 부분을 단순하게 처리했습니다. 이점 양해를 부탁드립니다. ^^
// 이 소스는 http://www.hitsquad.com/smm/programs/WavPlay/ 에서 구한 // WavPlay 1.0 by Warren W. Gay VE3WWG 의 소스를 참고하여 작성되었습니다. #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <stdlib.h> #include <stdarg.h> #include <time.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_FORMAT -13 // WAV 포맷 정보가 없음 #define TRUE 1 #define DSP_DEVICE_NAME "/dev/dsp" #define MIXER_DEVICE_NAME "/dev/mixer" #define PCM_WAVE_FORMAT 1 typedef struct { char RiffID [4] ; u_long RiffSize ; char WaveID [4] ; char FmtID [4] ; u_long FmtSize ; u_short wFormatTag ; u_short nChannels ; u_long nSamplesPerSec ; u_long nAvgBytesPerSec ; u_short nBlockAlign ; u_short wBitsPerSample ; char DataID [4] ; u_long nDataBytes ; } wave_header_t; static wave_header_t wave_header = { { 'R', 'I', 'F', 'F' }, 0, { 'W', 'A', 'V', 'E' }, { 'f', 'm', 't', ' ' }, 16, PCM_WAVE_FORMAT, 0, 0, 0, 0, 0, { 'd', 'a', 't', 'a' }, 0 }; static int fd_soundcard = -1; static int fd_mixer = -1; // volume 제어용 파일 디스크립터 //------------------------------------------------------------------------------ // 설명 : 메시지를 출력하고 프로그램 종료 // 인수 : 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); } //------------------------------------------------------------------------------ // 설명 : 파일에 Wave 파이의 헤더를 출력 // 인수 : _fd : Wave 파일의 디스크립터 // _channels : 1 = 모노 // 2 = 스테레오 // _samplerate: 비트 레이트 // _sampbits : 16 또는 8 // _samples : 녹음 tlrks(초) * _samplerate // ... : 형식에 필요한 인수 //------------------------------------------------------------------------------ int soundcard_write_wav_header( int _fd, int _channels, u_long _samplerate, int _sampbits, u_long _samples) { u_long databytes ; u_short blockalign ; if ( _fd < 0 ) printx( "bad file descriptor?"); _sampbits = (_sampbits == 16) ? 16 : 8 ; blockalign = ( (_sampbits == 16) ? 2 : 1) * _channels ; databytes = _samples * (u_long) blockalign ; wave_header.RiffSize = sizeof ( wave_header_t) + databytes - 8 ; wave_header.wFormatTag = PCM_WAVE_FORMAT ; wave_header.nChannels = _channels ; wave_header.nSamplesPerSec = _samplerate ; wave_header.nAvgBytesPerSec = _samplerate * (u_long) blockalign ; wave_header.nBlockAlign = blockalign ; wave_header.wBitsPerSample = _sampbits ; wave_header.nDataBytes = databytes; if ( sizeof ( wave_header_t) != write (_fd, &wave_header, sizeof ( wave_header_t))) printx( "write header errror"); return 0 ; }; //------------------------------------------------------------------------------ // 설명 : 사운드 출력을 위한 버퍼 크기를 구한다. // 반환 : 사운드 출력 버퍼 크기 //------------------------------------------------------------------------------ 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 ); } //-------------------------------------------------------------------- // 설명: 사운드카드 사용을 종료하기 위해 닫기 //-------------------------------------------------------------------- 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 ) { time_t tm_start; time_t tm_check; int fd_soundcard; int fd_wavfile; char *sound_buff; // 사운드 출력을 위한 버퍼 int sound_buff_size; int read_size; fd_soundcard = soundcard_open( O_RDONLY ¦ O_NONBLOCK); 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, 120, 120); soundcard_set_volume( SOUND_MIXER_WRITE_MIC , 80, 80); soundcard_set_volume( SOUND_MIXER_WRITE_IGAIN , 60, 60); soundcard_set_stereo( 0); soundcard_set_data_bit_size( 16); soundcard_set_bit_rate( 8000); sound_buff_size = soundcard_get_buff_size(); printf( "size=%dn", sound_buff_size); sound_buff = ( char *)malloc( sound_buff_size); fd_wavfile = open( "./test.wav", O_WRONLY ¦ O_CREAT ¦ O_TRUNC, 0644); if ( 0 > fd_wavfile) printx( "create wave file error"); soundcard_write_wav_header( fd_wavfile, 1, 8000, 16, 10 * 8000); time( &tm_start); while( TRUE) { time( &tm_check); if ( 10.0 <= difftime( tm_check, tm_start)) break; read_size = read( fd_soundcard, sound_buff, sound_buff_size); write( fd_wavfile, sound_buff, read_size); } close( fd_wavfile); soundcard_close(); return 0; }