← Back to code | Home

MP3 Decoding Using ACM

Historical Archive: This code snippet is from the early 2000s and demonstrates MP3 decoding using Microsoft's Audio Compression Manager APIs.
I have no idea if this is useful to folks, but since I had to beat my head against the silly Microsoft APIs for quite some time to get a useable result, I thought it might be helpful to post this little snippet showing you how to find an ACM decoder for MP3s, initialize it, and use it to decode streaming MP3 buffers.

#include 
#include 
#include 
#include 
#include 

#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 PCM
                       NULL,                       // no filter
                       0,                          // no callback
                       0,                          // no instance data
                       ACM_STREAMOPENF_NONREALTIME ); // not realtime - query all ACM drivers
  
  if( mmr ){
    assert( !"failed to open ACM stream" );
    return E_FAIL;
  }
  
  // allocate our I/O buffers
  LPBYTE mp3buf, rawbuf;
  mp3buf = (LPBYTE) LocalAlloc( LPTR, MP3_BLOCK_SIZE );
  
  DWORD rawbufsize = 0;
  mmr = acmStreamSize( g_mp3stream, MP3_BLOCK_SIZE, &rawbufsize, ACM_STREAMSIZEF_SOURCE );
  assert( mmr == 0 );
  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 );
  
  // open the files for conversion
  FILE *fpIn, *fpOut;
  fpIn = fopen( SOURCE_MP3, "rb" );
  if( !fpIn ){
    assert( !"can't open input MP3!" );
    return E_FAIL;
  }
  
  fpOut = fopen( OUTPUT_PCM_FILE, "wb" );
  if( !fpOut ){
    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;
}

Historical Context

This code demonstrates MP3 decoding using Microsoft's Audio Compression Manager (ACM) APIs, which were commonly used in Windows applications during the early 2000s for audio format conversion. The code shows the complex process of finding MP3 codecs, setting up the conversion stream, and performing block-by-block decoding from MP3 to PCM format.

Note the "voodoo values" mentioned in the comments - these were empirically determined constants needed to make the Microsoft APIs work correctly for MP3 decoding, highlighting the challenges developers faced when working with these proprietary audio APIs.

Technical Note: This code is preserved for historical and educational purposes. Modern applications would typically use more current audio libraries like DirectSound, Core Audio, or cross-platform solutions like FMOD or OpenAL.