본문 바로가기

3.구현/VC++

ACM 오디오 프로그래밍

ACM 오디오 프로그래밍

작성자: 박재성(ospace114@empal.com)

  • 편의상 평어를 사용하겠습니다. 제발 돌은 던지지 마세요. ㅠ.ㅠ
  • 이자료를 어떤 분이 퍼가실지 모르지만, 퍼가실때에는 출처를 밝혀주세요.

윈도우 환경에서 오디오 프로그래밍은 쉽지가 않다. 쉽지가 않다고 하는 것은 비디오나 이미지에 비해서 어렵다는 이야기이다.
보통 비디오라고 하면 DirectShow 기반으로 개발하며, 간혹 다른 것도 사용하지만..
이미지하면 GDI나 DirectDraw등을 이용하게 된다. 혹은 다른 이미지 라이브러리를 사용...
오디오 역시 DirectShow 기반으로 하면 되지만 ACM)Audio Compression Manager)이라는 SDK가 있다. 이곳에서 오디오 인코딩, 디코딩 및 여러 효과를 적용할 수 있다.

그러나 윈도우 기반 오디오 코덱으로 작업할 수 있는 음질 한계가 있다. 특히 MP3인 경우는 기본 설치된 환경에 MP3 압축은 56KHz이하만 가능하다. WMP10버전 이상을 설치하게 되면 고품질 MP3까지 무료로 인코딩할 수 있다.

개인이라면 무료 인코더/디코더를 사용하거나 불법으로 설치가능하겠지만, 개발하는 입장에서는 라이센스 비용과 범위가 걸리게 된다. 가급적이면 윈도우 내에서 해결하는게 차후 개발에 따른 비용이 감소하기 때문이다. 그리고 무료 인코더/디코더도 라이센스를 자세히 읽어봐야되는데 귀찮아서 일단 차후에 고려하기 했다.

국내에서 오디오관련 프로그래밍을 물오볼수 있는 포럼같은 것이 거의 없다. 예전에 Directshow가 있던것 같은데... 기억이... 지금은 찾아도 찾기 힘드네. ㅡ.ㅡㅋ
데브피아에서 vc 마을에 가면 어느정도 도움이 될 수 있다.

여기서는 ACM를 이용한 오디오 프로그래밍을 다룰 것이다. 기본적인 처리절차를 볼 것이고 나머지 추가 사항을 살펴보겠다.

ACM 변환 과정

아래는 ACM에서 오디오 변환시 사용되는 기본적인 함수들이다. 호출되는 순으로 나열했다.

acmDriverOpen(...); // 원하는 오디오 드라이버 로드 (생략 가능)
acmSuggest(...); // ACM에서 변한시 제안하는 포멧 획득( 생략가능)
acmStreamOpen(...); // 스트림 열기
acmStreamSize(...); // Source 혹은 destionation 버퍼 크기 얻기
acmStreamPrepareHeader(...); // 변환위한 헤더 로드
acmStreamConvert(...); // 변환 (선택적으로 모든 데이터 처리위해 반복가능)
acmStreamClose(...); // 열어진 스트림 닫기
acmDriverClose(...); // 로드된 드라이버 종료 (생략가능)

각각의 함수 설명은 MSDN을 참고하기를 바란다.
위의 함수를 사용한 예제는

http://david.weekly.org/code/mp3acm.html

에서 참고했다.

#include <windows.h>
#include <stdio.h>
#include <assert.h>
#include <mmreg.h>
#include <msacm.h>

#define MP3_BLOCK_SIZE 522
#define SOURCE_MP3 "C:\\audiograbber\\At The Club Last Night\\At_The_Club_Last_Night_-_Haven't_You_Heard.mp3"
#define OUTPUT_PCM_FILE "c:\\dump.pcm"

int g_mp3Drivers = 0;

BOOL CALLBACK acmDriverEnumCallback( HACMDRIVERID hadid, DWORD dwInstance, DWORD fdwSupport ){
  if( fdwSupport & ACMDRIVERDETAILS_SUPPORTF_CODEC ) {
    MMRESULT mmr;

    ACMDRIVERDETAILS details;
    details.cbStruct = sizeof(ACMDRIVERDETAILS);
    mmr = acmDriverDetails( hadid, &details, 0 );

    HACMDRIVER driver;
    mmr = acmDriverOpen( &driver, hadid, 0 );

    int i;
    for(i = 0; i < details.cFormatTags; i++ ){
      ACMFORMATTAGDETAILS fmtDetails;
      ZeroMemory( &fmtDetails, sizeof(fmtDetails) );
      fmtDetails.cbStruct = sizeof(ACMFORMATTAGDETAILS);
      fmtDetails.dwFormatTagIndex = i;
      mmr = acmFormatTagDetails( driver, &fmtDetails, ACM_FORMATTAGDETAILSF_INDEX );
      if( fmtDetails.dwFormatTag == WAVE_FORMAT_MPEGLAYER3 ){
    OutputDebugString( L"Found an MP3-capable ACM codec: " );
    OutputDebugString( details.szLongName );
    OutputDebugString( L"\n" );
    g_mp3Drivers++;
      }
    }
    mmr = acmDriverClose( driver, 0 );
  }
  return true;
}

HACMSTREAM g_mp3stream = NULL;



convertMP3(){

  MMRESULT mmr;

  // try to find an MP3 codec
  acmDriverEnum( acmDriverEnumCallback, 0, 0 );
  if(g_mp3Drivers == 0){
    OutputDebugString( L"No MP3 decoders found!\n" );
    return E_FAIL;
  }

  // find the biggest format size
  DWORD maxFormatSize = 0;
  mmr = acmMetrics( NULL, ACM_METRIC_MAX_SIZE_FORMAT, &maxFormatSize );

  // define desired output format
  LPWAVEFORMATEX waveFormat = (LPWAVEFORMATEX) LocalAlloc( LPTR, maxFormatSize );
  waveFormat->wFormatTag = WAVE_FORMAT_PCM;
  waveFormat->nChannels = 2; // stereo
  waveFormat->nSamplesPerSec = 44100; // 44.1kHz
  waveFormat->wBitsPerSample = 16; // 16 bits
  waveFormat->nBlockAlign = 4; // 4 bytes of data at a time are useful (1 sample)
  waveFormat->nAvgBytesPerSec = 4 * 44100; // byte-rate
  waveFormat->cbSize = 0; // no more data to follow


  // define MP3 input format
  LPMPEGLAYER3WAVEFORMAT mp3format = (LPMPEGLAYER3WAVEFORMAT) LocalAlloc( LPTR, maxFormatSize );
  mp3format->wfx.cbSize = MPEGLAYER3_WFX_EXTRA_BYTES;
  mp3format->wfx.wFormatTag = WAVE_FORMAT_MPEGLAYER3;
  mp3format->wfx.nChannels = 2;
  mp3format->wfx.nAvgBytesPerSec = 128 * (1024 / 8);  // not really used but must be one of 64, 96, 112, 128, 160kbps
  mp3format->wfx.wBitsPerSample = 0;                  // MUST BE ZERO
  mp3format->wfx.nBlockAlign = 1;                     // MUST BE ONE
  mp3format->wfx.nSamplesPerSec = 44100;              // 44.1kHz
  mp3format->fdwFlags = MPEGLAYER3_FLAG_PADDING_OFF;
  mp3format->nBlockSize = MP3_BLOCK_SIZE;             // voodoo value #1
  mp3format->nFramesPerBlock = 1;                     // MUST BE ONE
  mp3format->nCodecDelay = 1393;                      // voodoo value #2
  mp3format->wID = MPEGLAYER3_ID_MPEG;

  g_mp3stream = NULL;
  mmr = acmStreamOpen( &g_mp3stream,               // open an ACM conversion stream
               NULL,                       // querying all ACM drivers
               (LPWAVEFORMATEX) mp3format, // converting from MP3
               waveFormat,                 // to WAV
               NULL,                       // with no filter
               0,                          // or async callbacks
               0,                          // (and no data for the callback)
               0                           // and no flags
               );

  LocalFree( mp3format );
  LocalFree( waveFormat );

  switch( mmr ) {
   case MMSYSERR_NOERROR:
     break; // success!
   case MMSYSERR_INVALPARAM:
     assert( !"Invalid parameters passed to acmStreamOpen" );
     return E_FAIL;
   case ACMERR_NOTPOSSIBLE:
     assert( !"No ACM filter found capable of decoding MP3" );
     return E_FAIL;
   default:
     assert( !"Some error opening ACM decoding stream!" );
     return E_FAIL;
  }

  // MP3 stream converter opened correctly
  // now, let's open a file, read in a bunch of MP3 data, and convert it!

  // open file
  FILE *fpIn = fopen( SOURCE_MP3, "rb" );
  if( fpIn == NULL ){
    assert( !"can't open MP3 file!" );
    return E_FAIL;
  }

  // find out how big the decompressed buffer will be
  unsigned long rawbufsize = 0;
  mmr = acmStreamSize( g_mp3stream, MP3_BLOCK_SIZE, &rawbufsize, ACM_STREAMSIZEF_SOURCE );
  assert( mmr == 0 );
  assert( rawbufsize > 0 );

  // allocate our I/O buffers
  LPBYTE mp3buf = (LPBYTE) LocalAlloc( LPTR, MP3_BLOCK_SIZE );
  LPBYTE rawbuf = (LPBYTE) LocalAlloc( LPTR, rawbufsize );

  // prepare the decoder
  ACMSTREAMHEADER mp3streamHead;
  ZeroMemory( &mp3streamHead, sizeof(ACMSTREAMHEADER ) );
  mp3streamHead.cbStruct = sizeof(ACMSTREAMHEADER );
  mp3streamHead.pbSrc = mp3buf;
  mp3streamHead.cbSrcLength = MP3_BLOCK_SIZE;
  mp3streamHead.pbDst = rawbuf;
  mp3streamHead.cbDstLength = rawbufsize;
  mmr = acmStreamPrepareHeader( g_mp3stream, &mp3streamHead, 0 );
  assert( mmr == 0 );

  // let's dump this data off to disk (for debug checking!)
  FILE *fpOut = fopen( OUTPUT_PCM_FILE, "wb" );
  if( fpOut == NULL ){
    assert( !"can't output output PCM!" );
    return E_FAIL;
  }

  while(1) {
    // suck in some MP3 data
    int count = fread( mp3buf, 1, MP3_BLOCK_SIZE, fpIn );
    if( count != MP3_BLOCK_SIZE )
      break;

    // convert the data
    mmr = acmStreamConvert( g_mp3stream, &mp3streamHead, ACM_STREAMCONVERTF_BLOCKALIGN );
    assert( mmr == 0 );

    // write the decoded PCM to disk
    count = fwrite( rawbuf, 1, mp3streamHead.cbDstLengthUsed, fpOut );
    assert( count == mp3streamHead.cbDstLengthUsed );
  };

  // clean up after yourself like a good little boy
  fclose( fpIn );
  fclose( fpOut );
  mmr = acmStreamUnprepareHeader( g_mp3stream, &mp3streamHead, 0 );
  assert( mmr == 0 );
  LocalFree(rawbuf);
  LocalFree(mp3buf);
  mmr = acmStreamClose( g_mp3stream, 0 );
  assert( mmr == 0 );

  return S_OK;
}

위의 예제를 변환 관련 정보가 고정된 값을 사용했다. 이 값들은 MP3 포멧에 따라서 변경해줘야한다. 이런 정보를 획득하기 위해서는 직접 MP3 파일을 파싱하거나, 혹은 IMediaType 인터페이스를 사용해서 획득할 수 있다. 이는 tag에 대한 정보를 가져오지 못하고, 오직 파일 포멧 정보만 얻을 수 있다.

포멧 정보 알아내기

고정된 포멧이 아닌 상황에 따라 해당 파일에 대한 포멧을 획득해서 좀더 유연한 코드가 된다. 다음 링크의 예제가 IMediaType를 이용한 포멧정보 획득을 보여주고 있으니 참고하길 바란다. MSDN에서 IMediaType의 멤버함수를 보면 어떤 정보를 가져올 수 있는지 대충 알 수 있다.

DirectShow MediaDet를이용해미디어정보읽기

위의 예제에 의해서 get_StreamMediaType 함수에 의해서 MediaType을 획득할 수 있으면 우리는 포멧 정보를 다음과 같이 획득 할 수 있다.

AM_MEDIA_TYPE   media_type;
//...
det->get_StreamMediaType(&media_type);
//...
WAVEFORMATEX *pAudioHeader = (WAVEFORMATEX *)media_type.pbFormat;

위의 WAVEFORMAT에서 채널 수, 샘플 레이트 등등을 알 수 있다. 해당 구조체를 MSDN에서 살펴보면 다음과 같다.

typedef struct { 
  WORD  wFormatTag; 
  WORD  nChannels; 
  DWORD nSamplesPerSec; 
  DWORD nAvgBytesPerSec; 
  WORD  nBlockAlign; 
} WAVEFORMAT; 

내부 변수 이름만 봐도 대충 어떤 것인지 쉽게 짐작할 수 있다.
이렇게 획득한 정보를 이용해서 ACM 변환 과정시 acmStreamOpen 함수에 들어갈 source와 destination 포멧 정보를 입력할 수 있다.

추가로 직접 MP3 포멧을 읽어서 정보를 획득하는 방법을 간략하게 보겠다. 아래 코드는 Alessandro Angeli의 포스팅에서 가져온 것이다. 아직 코드가 완성된 것이 아니기 때문에 그대로 사용해서는 안된다. 이외에 다른 곳에서도 다양한 코드들이 있으니 직접 찾아서 사용해보길 바란다.
단지 어떤 방법으로 처리되는지에 대한 예제만 보인 것이다.

#include <stdlib.h> 
#include <stdio.h> 
#define MPA_SYNC_CODE 0xFFE00000UL 
struct { 
int major; 
int minor; 
} versions[/* version */] = { {2,5}, {0,0}, {2,0}, {1,0}, }; 

int layers[/* layer */] = { 0, 3, 2, 1, }; 
char *booleans[/* boolean */] = { "no", "yes", }; 
int bitrates[4 /* version */][4 /* layer */][16 /* bitrate */] = { 
{ /* V2.5 */ 
  /* L0 */ { 0, }, 
  /* L3 */ { 0, }, 
  /* L2 */ { 0, }, 
  /* L1 */ { 0, }, 
}, 
{ /* V0.0 */ 
  /* L0 */ { 0, }, 
  /* L3 */ { 0, }, 
  /* L2 */ { 0, }, 
  /* L1 */ { 0, }, 
}, 
{ /* V2.0 */ 
  /* L0 */ { 0, }, 
  /* L3 */ { 0, }, 
  /* L2 */ { 0, }, 
  /* L1 */ { 0, }, 
}, 
{ /* V1.0 */ 
  /* L0 */ { 0, }, 
  /* L3 */ { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 
320, -1,  }, 
  /* L2 */ { 0, }, 
  /* L1 */ { 0, }, 
}, 
}; 

int samplerates[4 /* version */][4 /* samplerate */] = { 
/* V2.5 */ { 11025, 12000,  8000, -1, }, 
/* V0.0 */ {    -1,    -1,    -1, -1, }, 
/* V2.0 */ { 22050, 24000, 16000, -1, }, 
/* V1.0 */ { 44100, 48000, 32000, -1, }, 
}; 

char *chanmodes[] = { "stereo", "joint stereo", "dual channel", "mono", }; 
struct { 
int intensity; 
int ms; 
} extensions[] = { {0,0}, {1,0}, {0,1}, {1,1}, }; 

char *emphasises[] = { "none", "50/12 ms", "(reserved)", "CCITT J.17", }; 
int coeffs[4 /* version */][4 /* layer */] = { 
/* V2.5 */ { 0,      0,      0,      0, }, 
/* V0.0 */ { 0,      0,      0,      0, }, 
/* V2.0 */ { 0,  72000,  72000,  24000, }, 
/* V1.0 */ { 0, 144000, 144000,  48000, }, 
}; 

int main(int argc, char *argv[]) 
{ 
int rc = 0; 
FILE *hf = NULL; 
unsigned long hdr = 0x00000000UL; 
unsigned char b; 
long offset; 
int version; 
int layer; 
int protection; 
int bitrate; 
int samplerate; 
int padding; 
int private; 
int chanmode; 
int extension; 
int copyright; 
int original; 
int emphasis; 
int size; 
printf("*** fopen(\"%s\")...\n",argv[1]); 
if(NULL == (hf = fopen(argv[1],"rb"))) { rc = errno; goto exit; } 
while(1) { 
  if(fread(&b,1,1,hf) < 1) { rc = errno; goto exit; } 
  hdr = (hdr << 8) | b; 
  if((hdr & MPA_SYNC_CODE) == MPA_SYNC_CODE) { 
   if((offset = ftell(hf)) < 0) { rc = errno; goto exit; } 
   offset -= 4; 
   printf("*** frame header found @ 0x%08lX (%ld):\n",offset,offset); 
   /// hdr = hdr & ~MPA_SYNC_CODE; 
   version  = (hdr >> 19) & 0x03; 
   layer  = (hdr >> 17) & 0x03; 
   protection = (hdr >> 16) & 0x01; protection = 1 - protection; 
   bitrate  = (hdr >> 12) & 0x0F; 
   samplerate = (hdr >> 10) & 0x03; 
   padding  = (hdr >>  9) & 0x01; 
   private  = (hdr >>  8) & 0x01; 
   chanmode = (hdr >>  6) & 0x03; 
   extension = (hdr >>  4) & 0x03; 
   copyright = (hdr >>  3) & 0x01; 
   original = (hdr >>  2) & 0x01; 
   emphasis = (hdr >>  0) & 0x03; 
   printf(">>>\t version .. = (%d) MPEG 
%d.%d\n",version,versions[version].major,versions[version].minor); 
   printf(">>>\t layer .... = (%d) Layer %d\n",layer,layers[layer]); 
   printf(">>>\t protection = (%d) %s\n",protection,booleans[protection]); 
   printf(">>>\t bitrate .. = (%d) %d 
Kbps\n",bitrate,bitrates[version][layer][bitrate]); 
   printf(">>>\t samplerate = (%d) %d 
Hz\n",samplerate,samplerates[version][samplerate]); 
   printf(">>>\t padding .. = (%d) %s\n",padding,booleans[padding]); 
   printf(">>>\t private .. = (%d) %s\n",private,booleans[private]); 
   printf(">>>\t chanmode . = (%d) %s\n",chanmode,chanmodes[chanmode]); 
   printf(">>>\t extension  = (%d) Intensity stereo = %s, MS stereo = 
%s\n",extension,booleans[extensions[extension].intensity],booleans[extensio­n 
s[extension].ms]); 
   printf(">>>\t copyright  = (%d) %s\n",copyright,booleans[copyright]); 
   printf(">>>\t original . = (%d) %s\n",original,booleans[original]); 
   printf(">>>\t emphasis . = (%d) %s\n",emphasis,emphasises[emphasis]); 
   size = coeffs[version][layer] 
    * bitrates[version][layer][bitrate] 
    / samplerates[version][samplerate] 
    + padding - 4; 
   printf(">>>\t payload .. = %d bytes\n",size); 
   if(fseek(hf,size,SEEK_CUR)) { rc = errno; goto exit; } 
  } 
} 
exit: 
printf(">>> rc = %d\n",rc); 
if(hf) (void)fclose(hf); 
return rc; 
}

으로 Destionation 파일 포멧 얻기

ACM에서 source와 destionation 포멧을 설정하는 것은 매우 민감하다. 잘못된 값이 입력되면 변화과정시 에러가 발생하면서 중단이 된다. Source 정보를 원래 파일에서 추출해서 입력하면 되지만 destination이 문제이다. 다양한 설정 값이 존재하기 때문이다.

이를 위해서 ACM에서는 source 포멧에 따라 destination 포멧을 결정해주는 API가 있다. 바로 acmFormatSuggest 함수이다. 이 역시 MSDN를 살펴보면 되지만, 예제가 없기 때문에 실제 사용에 있어서 어려움이 있다.

아래 예제는 다른 사이트에서 참고해서 다시 간략하게 정리해서 코딩했다.
이 예제를 MP3포멧에서 PCM으로 변환하기 위해서 포멧을 결정하는 코드이다.

DWORD maxFormatSize = 0;
MMRESULT mmr = 0;

mmr = acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &maxFormatSize);


// 아래는 source인 mp3 포멧을 지정해준다.
LPMPEGLAYER3WAVEFORMAT mp3format = (LPMPEGLAYER3WAVEFORMAT) new BYTE[maxFormatSize]; 
memset(mp3format, 0, maxFormatSize);

mp3format->wfx.cbSize = MPEGLAYER3_WFX_EXTRA_BYTES; 
mp3format->wfx.wFormatTag = WAVE_FORMAT_MPEGLAYER3; 
mp3format->wID = MPEGLAYER3_ID_MPEG;
mp3format->wfx.nChannels = 2; // 스테레오
mp3format->wfx.wBitsPerSample = 0; // Must be 0?
mp3format->wfx.nBlockAlign = 1;    // Must be 1?
mp3format->wfx.nSamplesPerSec = 22050;
mp3format->wfx.nAvgBytesPerSec = mp3format->wfx.nSamplesPerSec * mp3format->wfx.nChannels*2; // 16bit 데이터로 간주
mp3format->fdwFlags = MPEGLAYER3_FLAG_PADDING_OFF;
mp3format->nBlockSize = 512; // 이값은 임의로 지정했다.(블럭크기인데, 정확한 용도는...)
mp3format->nFramesPerBlock = 1;
mp3format->nCodecDelay = 1393;

// 아래는 destination인 pcm 포멧을 지정한다. 포멧에 지정되는 값은 아주 간략하다.
WAVEFORMATEX *waveOut = (WAVEFORMATEX*)new Byte[maxFormatSize];
memset(waveOut,0, sizeof(WAVEFORMATEX));

waveOut->cbSize = static_cast<WORD>(maxFormatSize) - sizeof(WAVEFORMATEX); // 추가되는 정보 크기
waveOut->wFormatTag = WAVE_FORMAT_PCM; // 변환될 포멧을 지정한다.

mmr = acmFormatSuggest(NULL, (LPWAVEFORMATEX)mp3format, waveOut, maxFormatSize, ACM_FORMATSUGGESTF_WFORMATTAG);

결과로 waveOut에서 값들이 채워질 것이다. 그래로 사용해도 되고 때에 따라서 값을 변경도 가능하다.

다중 포멧 변환

앞에서 포멧 변화은 단순하다. 입력 포멧과 출력 포멧만 정해주면 된다. 그러나 실제로는 단순하지 않다. ACM에서는 때때로 여러번 변환해야 원하는 포멧을 얻을 수 있다. 즉 앞에서 변환과정을 한번이 아닌 두번 이상 거치는 것이다. 보통 source와 destination 포멧이 서로 다른 경우, 특히 둘다 PCM이 아니거나 어느 하나가 아닌 경우에 종종발생한다.

MSDN에 따르면 16-bit, 44-kHz, stereo PCM을 11-kHz mono ADPCM으로 변화를 하려면, 바로 변환은 힘들고 중간에 16-bit, 11-kHz, mono PCM으로 변환한 후에 ADPCM으로 변환해야한다.

16-bit 44-kHz stereo PCM --<변환>--> 16-bit 11-kHz mono PCM --<변환>--> 11-kHz mono ADPCM

만약 source가 PCM이 아니면 먼저 PCM으로 변환을 한후 작업을 해야한다. 일반적으로 가장 간단한 변환 단계는 source와 destination이 모두 PCM이면 된다. 혹은 변환하는 destination이 채널이나 비트레이스, 비트 수는 일치하고 단순히 PCM으로만 변환해도 간단하게 한번의 변환으로 종료된다.

다시 정리하면 다음과 같다.

  • Source가 PCM이 아니면 PCM으로 변환
  • Destination이 PCM이 아니면 source에서 PCM으로 변환하고 최종 포멧으로 변환
  • Source와 destination이 PCM이면 직접 변환
  • Source와 destination이 PCM이 아니면 더많은 단계를 거쳐서 변환

이외에 더많은 정보를 원하면 아래 "Audio Compression Manager"를 참고하길 바란다.

MSACM에서 MP3 지원

윈도우즈에서는 기본적으로 MP3 인코딩과 디코딩이 가능하다. 이중에서 인코딩을 할 수 있는 포멧에 제한이 있다. 즉 56kbps/24kHz 이상으로 인코딩은 불가능하다.

이를 확인할 수 있는 방법은 먼저 "C:\Windows\system32" 폴더로 이동한다. 만약 다른 드라이브나 폴더에 윈도우를 설치했다면 해당 윈도우 밑의 "system32"폴더로 이동한다. 그 안에서 "l3codeca.acm"파일을 찾는다. 해당 파일 속성에서 버전을 확인해보면, 1.9.0.305라고 되어 있을 것이다. 이 파일이 앞에서 말한 MP3를 지원해주는 ACM 드라이버이다.

만약 WMP10 이상으로 설치했다면 "system32"폴더안에 l3codecp.acm파일이 있을 것이다. 이 파일도 MP3를 지원하는 ACM 드라이버인데 이파일은 모든 비트레이트에 대해 인코딩과 디코딩을 할 수 있게 한다.

그래서 만약 프로그램을 개발하는 입장이라면 MP3에 대한 사용하기 위해서 사용자에게 WMP10버전 이상을 필수 설치 항목에 넣어둬야 한다.

  • l3codecx.acm - 디코딩 전용, 인코딩은 안됨
  • l3codeca.acm - advanced, 모든 비트레이트 디코딩 가능, 56kbps이하만 인코딩 가능
  • l3codecp.acm - professional, 모든 비트레이트를 디코딩과 인코딩 가능
  • l3codecx.ax - amc용이 아니다. WMP 6.4에 의해서 설치되는 파일(무시)

프로젝트

아래 프로젝트는 MP3파일을 PCM으로 변환하는 예제이다. 완벽한 소스가 아니며, 기능 구현이 완변하게 된 것이 아니기에 안에 소스만을 참고해서 살펴보기를 바란다.
주의! 바로 컴파일해서 실행이 안된다. 내부 변수 값을 수정해야한다.

Test10_acm.zip
51.4 kB

Troubleshooting

ACM에서 MP3 인코딩시 Halt

MP3는 다양한 버전과 포멧이 존재한다. 그리고 인코딩 품질에 따른 조합되는 경우의 수도 상당하다.
경험에 따르면 ACM에서

#define MP3\_BLOCK\_SIZE 522  

//... (생략)  

22-kHz stereo MP3 --<변환>--> 22-kHz stereo PCM (에러 발생)

ULONG rawbufsize = 0;

mmr = acmStreamSize(g_mp3stream, MP3_BLOCK_SIZE, &rawbufsize, ACM_STREAMSIZEF_SOURCE);
//...
LPBYTE mp3buf = new BYTE[MP3_BLOCK_SIZE];
LPBYTE rawbuf= new BYTE[rawbufsize];


ACMSTREAMHEADER mp3streamHead;
mp3streamHead.cbStruct = sizeof(ACMSTREAMHEADER);
mp3streamHead.cbSrc = mp3buf;
mp3streamHead.cbSrcLength = MP3_BLOCK_SIZE;
mp3streamHead.cbDst = rawbuf;
mp3streamHead.cbDstLength = rawbufsize;

mmr = acmStreamPrepareHeader(g_mp3stream, &mp3streamHead);

// ...

MMRESULT에서 512 에러가 발생한다. 위의 인터넷에 떠도는 예제를 그대로 사용했을 때 발생할 수 있는 문제이다. 512에러의 의미는 다음과 같다.

512 - ACM Operation Not Possible.

즉, ACM를 처리할 수 없다라는 의미이다. 원인이 뭘까? 한번 여러분이 곰곰히 생각해볼라.

정답

이는 실제 해결책인지는 확인하지 못했다. 경험에 의한 해결이라고 보면 된다. 아직 ACM에대해서 정확히 알지 못하므로 단언하지는 못하겠다.

위의 MP3_BLOCK_SIZE의 522 값을 512값으로 변경하면 된다. 그러면 이상없이 인코딩이 된다.
근데 이렇게 하면 소리가 이상하게 들린다. 문제가 해결되었다고 생각했는데 다신 산이 나타났다.

해결

정말 삽질 많이 했다. 짜증난다. 512값으로 변경된 것이 이상없다는 가정하에서 디버깅을 했지만 원인이 무었인지 몰랐다. 512값을 256값으로 변경해보았다. 앗!!! 소리가 조금 정상적으로 들렸다. 저품질 오디오에서도 다시 문제가 생겼다. 다시 128으로 변경했다. 이번에는 정상적으로 들렸다.
실제 이값이 인코딩시 어떤 영향을 미치는지는 알지못하겠다.
이 부분은 좀더 확인해보거나 자료가 필요하다. 혹시 이에 대해 알고 계신분은 저의 메일이나 댓글로 남겨주시길 바랍니다.

오디오 소리가 헬륨가스 먹은것 처럼 이상한 소리로 인코딩

간혹 오디오를 인코딩했는데 소리가 헬륨가스 먹으것 처럼 이상한 소리를 내면서 재생시간도 절반으로 줄어드는 경우가 있다.

해결
이는 22050Hz 오디오를 44100Hz 오디오로 바로 인코딩했을 경우 발생한다. 즉 22050Hz 오디오를 44100으로 변환하는게 아니라 원래는 22050Hz인데 44100Hz으로 그냥 인코딩하면서 발생한다.
다시 22050Hz으로 인코딩하거나 중간에 44100Hz으로 인코딩하는 과정을 추가해서 처리하면 된다.

참고

**참고사이트:

Converting Data from One Format to Another, Microsoft(R)
DirectShow MediaDet를이용해미디어정보읽기
Multistep Format Conversion, Microsoft(R)
Audio Compression Manager, Microsoft(R)
acm Decoders, mp3-tech.org
Alessandro Angeli, Reading the MP3 file/frame format

**MP3 참고사이트:

id3 (http://www.id3.org/)
mp3-tech(http://www.mp3-tech.org/)

기타 오디오 관련 자료:
Sampling Sound in Windows 32(http://www.relisoft.com/Freeware/recorder.html)
Simple DirectMedia Layer (http://www.libsdl.org/)
Steven De Toni, Simple Audio Out Oscilloscope and Spectrum Analyzer(http://www.codeproject.com/KB/audio-video/oscilloscope.aspx?df=100&forumid=324752&exp=0&select=1965375)

반응형