이번에는 wav 파일을 출력해 보겠습니다. wav 파일을 출력하기 위해서는 파일의 앞 부분에 있는 헤더파일의 내용을 확인하여 사운드 카드의 출력을 스테레오나 모노를 설정해 주어야 하고, 녹음된 비트 레이트와 비트 크기에 맞추어 설정해 주어야 합니다. 이를 위해 이전 예제에서 int soundcard_open_file( char *_filename) 함수를 추가했습니다.

_filename은 출력할 wav 파일 이름이 되며, soundcard_open_file()은 _filename의 헤더를 읽어 들여서 사운드 카드의 출력 방법을 설정합니다. 즉, 헤더파일의 내용에 따라 사운드카드를 설정할 뿐 재생은 하지 않습니다. ^^

그러므로 wav를 출력하려면 아래와 같은 순서로 처리하면 됩니다.

  1. 사운드카드를 open한 후
  2. wav 파일의 헤더 내용으로 사운드 카드를 설정하고,
  3. 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 파일로 녹음해 보겠습니다.

태그: *하드웨어 *사운드카드 *Wave파일