이제 사운드 카드로 소리를 담아 내는 방법을 알게 되었으니 파일로 저장하는 방법에 대해 알아 보겠습니다. 가장 흔히 사용하는 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;
}