이번 시간부터 사운드 카드를 이용하여 녹음하거나 재생하는 루틴에 대해 알아 보겠습니다. 리눅스에서는 주변 장치를 가상의 파일 시스템으로 접근할 수 있다는 것을 다시 한번 일깨워 주는 주제가 아닐까 생각합니다.

즉, 사운드 카드에 소리를 출력하려면 사운드 카드를 파일처럼 열기를 하여 쓰기를 하면 되고, 녹음을 위해 소리를 담아 내려면, 역시 열기를 하여 읽기를 하면 됩니다.

백문이 불여일견이라고 예제를 바로 보겠습니다. 이해를 돕고자 예제는 사운드 카드의 마이크로 읽어 들인 소리를 바로 사운드 카드의 스피커로 출력하는 매우 간단한 예제로 구성했습니다. 즉, 마이크의 입력을 바로 스피커로 출력하겠습니다.

이렇게 하기 위해서는 사운드 카드를 사용하기 위한 open() 뿐만 아니라 음질에 대한 설정을 해야 합니다. 예제의 main() 함수를 를 보시면 아래의 함수를 이용하여 스테레오인지 모노인지를 지정하고, 비트 크기와 비트 레이트를 설정하고 있습니다.

   soundcard_set_stereo( 0);
   soundcard_set_data_bit_size( 16);
   soundcard_set_bit_rate( 8000);

또한 출력 음량과 마이크의 음량도 조절해야 하는데, 마이크의 경우 음량 뿐만 아니라 igain 값도 설정해 주어야 합니다. igain 이면 입력 이득이라고 생각됩니다만 정확한 것은 아직 모릅니다. ^^;

   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_MIXER_WRITE_SPEAKER 대신에 SOUND_MIXER_WRITE_ALTPCM를 사용했습니다. 이유는 제가 이 예제를 테스트하는데 ESP-MMI를 이용했기 때문입니다. ESP-MMI에서는 SOUND_MIXER_WRITE_ALTPCM를 이용해야 출력 음량을 조절하실 수 있습니다.

제가 사용한 마이크 같은 경우 극성이 있었습니다. 처음에 극성이 있는 줄도 모르고 테스트했다가 혈압 오르고 시간만 낭비했습니다. 하드웨어를 좀 학습했어야 했는데, 이럴 때에는 너무 아쉽네요. 저 같이 실수하지 마시라고 부분 촬영을 했습니다.

PC에 설치한 리눅스에서 테스트하신 다면 SOUND_MIXER_WRITE_SPEAKER나 다른 상수값을 이용하십시오.

보륨까지 조절했으므로 이제 사운드카드로부터 마이크 값을 읽고( read() )  사운드카드에 연결된 스피커로 출력( write())하면 되겠습니다. 역시 파일처럼 처리하고 있죠. 파일이기 때문에 open부터 하고 있습니다. ^^

   fd_soundcard   = soundcard_open( O_RDWR ¦ O_NONBLOCK);

                        :

   while( TRUE)
   {
      read_size   = read( fd_soundcard, sound_buff, sound_buff_size);
      write( fd_soundcard, sound_buff, read_size);
   }

뭔가 대단할 줄 알았는데 매우 간단하죠. 역시 리눅스의 강력한 파일 시스템 덕분입니다.

이제 전체 소스를 보시겠습니다. 아래의 프로그램을 컴파일 하신 후, 마이크로 음성을 보내면 바로 스피커로 에코 출력이 나옵니다.

// 이 소스는 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 <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"

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);
}

//------------------------------------------------------------------------------
// 설명 : 사운드 출력을 위한 버퍼 크기를 구한다.
// 반환 : 사운드 출력 버퍼 크기
//------------------------------------------------------------------------------
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 )
{
   int      fd_soundcard;
   char    *sound_buff;                                                    // 사운드 출력을 위한 버퍼
   int      sound_buff_size;
   int      read_size;

   fd_soundcard   = soundcard_open( O_RDWR ¦ 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, 100, 100);
   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();
   sound_buff        = ( char *)malloc( sound_buff_size);

   while( TRUE)
   {
      read_size   = read( fd_soundcard, sound_buff, sound_buff_size);
      write( fd_soundcard, sound_buff, read_size);
   }

   soundcard_close();
   return 0;
}

 

태그: *하드웨어 *사운드카드 *ESP-MMI