Click here to Skip to main content
15,884,388 members
Articles / Desktop Programming / MFC
Article

DLL Source For Buffer-based MP3 Decoding

Rate me:
Please Sign up or sign in to vote.
4.38/5 (7 votes)
11 Mar 20055 min read 138.8K   2K   59   37
Source code for creating a DLL that decodes MP3 data buffer-at-a-time to raw audio (PCM) based on the open source library libmad.

Sample Image - madxlib-screenshot.gif

Introduction

'madxlib' is a source code for creating a Windows DLL, that performs buffer-based MP3 decoding. It allows you to build interfaces that can process a given MP3 file in chunks at the intervals you specify, and returns the decoded 16-bit PCM samples in a buffer.

Background

madxlib effectively supersedes madlldlib, another MP3 decoding source I have released via The Code Project. The two sources differ in that, madlldlib took as parameters the location of the input MP3 and output WAV/PCM files and performed the conversion while passing status back to the calling code. This gave little control to the calling code to pause, restart, queue the samples, etc. madxlib rectifies this by relying on the calling code to fill the input buffer (which can come from a local file, the Internet, network share, etc). It then returns an output buffer of the converted PCM samples and waits for the next input.

This source, like madlldlib, was based on the madlld source, which was designed as a tutorial of the low-level functions of the libmad library.

Using the Code

Included in the source is the file test.cpp. This is a simple example demonstrating how to program the madxlib API. It consists of a single main() function.

Within main(), the variable declarations are first made:

// Output file and output buffer
unsigned char     in_buffer[MADX_INPUT_BUFFER_SIZE + MAD_BUFFER_GUARD];
unsigned char     out_buffer[MADX_OUTPUT_BUFFER_SIZE];
    
size_t             a;
size_t            in_size = MADX_INPUT_BUFFER_SIZE + MAD_BUFFER_GUARD;
    
// Structure necessary to use madxlib
madx_house        mxhouse;
// Status and control
madx_stat        mxstat;
// Catch signals from madx_read()
madx_sig        mxsig;    

in_buffer and out_buffer will be used to send the MP3 data to madxlib and retrieve the PCM samples once they've been decoded, respectively. There are three datatypes specific to madxlib. madx_house "houses" the necessary data types that make madxlib (and the underlying library libmad) work. madx_stat is used to send status and information, such as how many bytes to read from the input, between the calling code and madxlib. madx_sig is an enum type that is returned by the madx_read() function (described below) to signal what action your code should take.

Next, madx_init() is executed.

// Initialize madxlib
    
madx_init(out_buffer, &mxhouse);

madx_init() takes as parameters a pointer to the output buffer, and the address of the madx_house structure, mxhouse. It then initializes the necessary libmad structures and assigns the out_buffer pointer to an internal variable.

Next test.cpp opens its input and output files with fopen(), erroring if there is a problem. Now we are ready to start the main loop (and most important logic) of the program.

// This loop reads input until EOF, fills the
// output buffer, writes the output file, and 
// checks for errors. The return values from 
// madx_read() must be inspected after each 
// call to determine the appropriate action.

do
{
    // Read input, pass processed data to out_buffer
    mxsig = madx_read( in_buffer, out_buffer, &mxhouse, &mxstat );

    if (strcmp(mxstat.msg, "")) printf("%s\n", mxstat.msg); 
 
    if (mxsig == ERROR_OCCURED)            // Error
    {

        printf("Unrecoverable error %s\n", mxstat.msg);
        break;

    }
    else if (mxsig == MORE_INPUT)     // Fill buffer
    {
        if (mxstat.buffstart)         // Fill partial buffer
        {
            if ( (a = fread(mxstat.buffstart, 1, 
              mxstat.readsize, in_file))== mxstat.readsize)
            {
                printf("Filling buffer.\n");
            }
            else if (feof(in_file))        // EOF must be flagged
            {
                mxstat.is_eof = 1;
                mxstat.readsize = a;
            }
            else
                printf("Error! %d\n", ferror(in_file));
        
        }
        else                         // Read full buffer
        {

            if ( fread(in_buffer, 1, mxstat.readsize, in_file) == 
                    mxstat.readsize)
            {
                printf("Fill buffer full.\n");
            }
            else if (feof(in_file))        // EOF must be flagged
            {
                mxstat.is_eof = 1;
                mxstat.readsize = a;
            }
            else
                printf("Error! %d\n", ferror(in_file));
            
        }

        
    }
    else if (mxsig == FLUSH_BUFFER)     // Output to file
    {

        
        if (fwrite(out_buffer, 1, mxstat.write_size, out_file)
                   == mxstat.write_size)
            printf("Writing buffer.\n");
        else        
            printf("fwrite() error\n");            

        
    }
    else if (mxsig == EOF_REACHED)         // Input file EOF
    {

        if ( (a = fwrite(out_buffer,1,mxstat.write_size,out_file)) 
                       != mxstat.write_size)
        {
            printf("Error with final write! out_size:%d, fwrite: %d\n", 
                mxstat.write_size, a);
        }
        else
            printf("Finished. out_size:%d, fwrite: %d\n", 
                mxstat.write_size, a);            

        break;    
        
    }



}    
while(1);

The above do...while loop is the bulk of test.cpp. For every iteration, it makes a call to madx_read(). madx_read() takes as parameters the input buffer (initially empty), the output buffer, the address of mxhouse (the madx_house structure), and the address of mxstat (the madx_stat structure). It returns a madx_sig value, which is stored in the variable mxsig.

madx_read() essentially does the following. It decodes the MP3 samples in the input buffer to raw audio (PCM) and synthesizes them, then scales the samples down to 16-bit. Depending on what is encountered during the execution of the function (error, more input is needed, output buffer is full), the appropriate madx_sig signal is returned, and the calling code (i.e. this loop) must handle the signal. The trickiest part of this is handling the partial reads when the buffer fills up. This is where madx_stat comes in.

After madx_read() returns, mxsig is inspected to determine what action should be taken. If an ERROR_OCCURED, it prints an appropriate message and breaks out of the loop. If the instruction is FLUSH_BUFFER, then this means that the output buffer (out_buffer) is full and ready to be written, in this case, to disk. Note the use of mxstat.write_size in the fwrite() statement. mxstat.write_size will always contain the correct number of units to write. When a FLUSH_BUFFER has been returned, we do not exit the loop. We only exit if ERROR_OCCURED or EOF_REACHED. If the latter occured, then we must write the remaining bytes in out_buffer to the file and break the loop.

If MORE_INPUT is signaled, then the calling code must refill the input buffer. First, this code segment (else if mxsig == MORE_INPUT above) checks the mxstat.buffstart variable. If it is 0, then a full buffer read is done, which fills the buffer from the first position to mxstat.readsize. mxstat.readsize is always set by madx_read() before returning MORE_INPUT. If mxstat.buffstart is not 0, then it means you must fill the input buffer, but not from the first position. In this fread() statement, the input buffer in_buffer is substituted for mxstat.buffstart, which is set by madx_read() to some point between the starting position of in_buffer and the end.

Note that after both reads EOF is checked, and if reached the variable mxstat.is_eof is set to 1 (or "true"). This must be done so that madx_read() can pad the final bytes before processing them. This is a specific requirement of the underlying libmad library.

All that remains is to pass the &mxhouse to madx_deinit(), which takes care of freeing memory used by the libmad structures. We then close out_file and our task has been completed.

// Clean-up
madx_deinit(&mxhouse);
fclose(out_file);

return;

Notes

  • The source provided above depends on the libmad source. It must be downloaded and compiled first. See the notes in the file "Makefile" in madxlib for further instructions on compiling.
  • This is a Beta release of madxlib. You should always check my website for the latest updates. They will be there first. In time I should have a forum dedicated to madxlib for answering questions.
  • Also included in the demo is a C# sample that uses P/Invoke to access madxlib.dll.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionHow can I get mp3 header information? Pin
fwl200025-Nov-13 3:27
fwl200025-Nov-13 3:27 
GeneralThanks for the great article Pin
Option Greek19-Aug-10 21:09
Option Greek19-Aug-10 21:09 
GeneralCompact Framework/Windows Mobile Pin
GutterBoy25-Aug-09 9:31
GutterBoy25-Aug-09 9:31 
Generalsend mp3 byte and get pcm byte Pin
javad_20051-Aug-09 23:02
javad_20051-Aug-09 23:02 
GeneralRe: send mp3 byte and get pcm byte Pin
GutterBoy25-Aug-09 9:14
GutterBoy25-Aug-09 9:14 
QuestionIs it possible to specify the bit depth? Pin
haden.pereira28-Apr-09 4:46
haden.pereira28-Apr-09 4:46 
AnswerRe: Is it possible to specify the bit depth? Pin
gilad-ap21-Jul-09 6:39
gilad-ap21-Jul-09 6:39 
QuestionUsage of madxlib under what licence? Pin
MKahlow5-Apr-08 21:21
MKahlow5-Apr-08 21:21 
GeneralmxHouse2 has no data Pin
Member 342066110-Jan-08 20:44
Member 342066110-Jan-08 20:44 
GeneralHaving trouble following the logic of the API Pin
Photon8-Dec-07 8:31
Photon8-Dec-07 8:31 
QuestionOpenAL scratchy sound [modified] Pin
sagetarian31-Jul-07 8:37
sagetarian31-Jul-07 8:37 
AnswerRe: OpenAL scratchy sound Pin
Paul Sanders (the other one)26-Oct-07 11:58
Paul Sanders (the other one)26-Oct-07 11:58 
QuestionHow Can I call from VB? Pin
darkschine3-Feb-07 19:10
darkschine3-Feb-07 19:10 
AnswerRe: How Can I call from VB? Pin
gilad-ap12-Oct-07 7:10
gilad-ap12-Oct-07 7:10 
QuestionIs there a function(get total size of mp3 file, get WAVEFORMATEX info) in madxlib SDK? Pin
blue sky77712-Dec-06 17:23
blue sky77712-Dec-06 17:23 
QuestionHi, How to handle CALL_AGAIN? Pin
Amanda Davis12-Jun-06 21:04
Amanda Davis12-Jun-06 21:04 
AnswerRe: Hi, How to handle CALL_AGAIN? Pin
gilad-ap11-Jul-06 6:25
gilad-ap11-Jul-06 6:25 
QuestionHow can i get the total size of decoded mp3 file? Pin
sashilover23-May-06 0:02
sashilover23-May-06 0:02 
AnswerRe: How can i get the total size of decoded mp3 file? Pin
gilad-ap25-May-06 6:11
gilad-ap25-May-06 6:11 
QuestionBut how can I fill the WAVEFORMATEX struct? Pin
Cherish_He22-Mar-06 2:40
Cherish_He22-Mar-06 2:40 
AnswerRe: But how can I fill the WAVEFORMATEX struct? Pin
gilad-ap22-Mar-06 10:27
gilad-ap22-Mar-06 10:27 
GeneralRe: But how can I fill the WAVEFORMATEX struct? Pin
Cherish_He22-Mar-06 21:51
Cherish_He22-Mar-06 21:51 
GeneralRe: But how can I fill the WAVEFORMATEX struct? Pin
gilad-ap23-Mar-06 3:38
gilad-ap23-Mar-06 3:38 
Generalcode crashes in debug mode in madx_read function line: 338 in madxlib.cpp Pin
Hannan Azam8-Jan-06 4:46
Hannan Azam8-Jan-06 4:46 
GeneralRe: code crashes in debug mode in madx_read function line: 338 in madxlib.cpp Pin
gilad-ap12-Jan-06 10:13
gilad-ap12-Jan-06 10:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.