MAD (libmad)是一個開源的高精度 MPEG 音頻解碼庫,支持 MPEG-1(Layer I, Layer II 和 LayerIII(也就是 MP3)。LIBMAD 提供 24-bit 的 PCM 輸出,完全是定點計算,非常適合沒有浮點支持的平台上使用。使用 libmad 提供的一系列 API,就可以非常簡單地實現 MP3 數據解碼工作。在 libmad 的源代碼文件目錄下的 mad.h 文件中,可以看到絕大部分該庫的數據結構和 API 等。
網上有很多關於libmad的使用實例,在他們的基礎上,我稍加總結、整理和衍生,文末給出相關參考鏈接,表示感謝!
一、libmad庫源碼
可以去相關網站下載,給出鏈接:
可以根據不同的平台自行編譯或者移植,略述。
二、相關數據結構及函數接口簡介
1、struct mad_decode
- struct mad_decoder {
- enum mad_decoder_mode mode;
-
- int options;
-
- struct {
- long pid;
- int in;
- int out;
- } async;
-
- struct {
- struct mad_stream stream;
- struct mad_frame frame;
- struct mad_synth synth;
- } *sync;
-
- void *cb_data;
-
- enum mad_flow (*input_func)(void *, struct mad_stream *);
- enum mad_flow (*header_func)(void *, struct mad_header const *);
- enum mad_flow (*filter_func)(void *,
- struct mad_stream const *, struct mad_frame *);
- enum mad_flow (*output_func)(void *,
- struct mad_header const *, struct mad_pcm *);
- enum mad_flow (*error_func)(void *, struct mad_stream *, struct mad_frame *);
- enum mad_flow (*message_func)(void *, void *, unsigned int *);
- };
2、struct mad_stream
- struct mad_stream {
- unsigned char const *buffer; /* input bitstream buffer */
- unsigned char const *bufend; /* end of buffer */
- unsigned long skiplen; /* bytes to skip before next frame */
-
- int sync; /* stream sync found */
- unsigned long freerate; /* free bitrate (fixed) */
-
- unsigned char const *this_frame; /* start of current frame */
- unsigned char const *next_frame; /* start of next frame */
- struct mad_bitptr ptr; /* current processing bit pointer */
-
- struct mad_bitptr anc_ptr; /* ancillary bits pointer */
- unsigned int anc_bitlen; /* number of ancillary bits */
-
- unsigned char (*main_data)[MAD_BUFFER_MDLEN];
- /* Layer III main_data() */
- unsigned int md_len; /* bytes in main_data */
-
- int options; /* decoding options (see below) */
- enum mad_error error; /* error code (see above) */
- };
三、MP3解碼流程簡介
MP3解碼有同步方式和異步方式兩種,libmad是以楨為單位對MP3進行解碼的,所謂同步方式是指解碼函數在解碼完一幀后才返回並帶回出錯信息,異步方式是指解碼函數在調用后立即返回,通過消息傳遞解碼狀態信息。
1、首先創建一個解碼器 struct mad_decoder decoder,緊接着調用函數 mad_decoder_init(...)函數,給出這個函數的原型及定義
- /*
- * NAME: decoder->init()
- * DESCRIPTION: initialize a decoder object with callback routines
- */
- void mad_decoder_init(struct mad_decoder *decoder, void *data,
- enum mad_flow (*input_func)(void *,
- struct mad_stream *),
- enum mad_flow (*header_func)(void *,
- struct mad_header const *),
- enum mad_flow (*filter_func)(void *,
- struct mad_stream const *,
- struct mad_frame *),
- enum mad_flow (*output_func)(void *,
- struct mad_header const *,
- struct mad_pcm *),
- enum mad_flow (*error_func)(void *,
- struct mad_stream *,
- struct mad_frame *),
- enum mad_flow (*message_func)(void *,
- void *, unsigned int *))
- {
- decoder->mode = -1;
-
- decoder->options = 0;
-
- decoder->async.pid = 0;
- decoder->async.in = -1;
- decoder->async.out = -1;
-
- decoder->sync = 0;
-
- decoder->cb_data = data;
-
- decoder->input_func = input_func;
- decoder->header_func = header_func;
- decoder->filter_func = filter_func;
- decoder->output_func = output_func;
- decoder->error_func = error_func;
- decoder->message_func = message_func;
- }
用戶編程可以用如下方式調用,可以看到從第三個參數開始,其實都是一些列的函數指針,這里初始化的目的其實是給創建的decoder注冊下面即將要自己實現的這些函數。Libmad庫會在解碼過程中回調這些函數:
- mad_decoder_init(&decoder, &buffer,
- input, 0 /* header */, 0 /* filter */, output,
- error, 0 /* message */);
第一個參數,就是定義的解碼器decoder;
第二個參數,是一個void型的函數指針,這里也就是給你的用戶空間定義私有的數據結構體用的,下面會給出具體的例子來說明其用法;
第三個參數,input_func函數,這個是用來讀取你的mp3資源的函數;
第四個參數,header_func函數,這個顧名思義是處理mp3頭部信息的函數,可以根據需要取舍;
第五個參數,filter_func函數,也沒有深入理解過,可以不必實現;
第六個參數,output_func函數,這個是用來將解碼之后的數據寫入輸出緩沖區或者音頻設備節點的;
第六個參數,output_func函數,這個是用來將解碼之后的數據寫入輸出緩沖區或者音頻設備節點的;
第七個參數,error_func函數,是用來打印返回的解碼出錯信息的;
第八個參數,message_func可以不必實現。
2、調用mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC)函數啟動解碼,查看Libmad庫源碼可知,這個函數里面會注冊一個函數指針
- /*
- * NAME: decoder->run()
- * DESCRIPTION: run the decoder thread either synchronously or asynchronously
- */
- int mad_decoder_run(struct mad_decoder *decoder, enum mad_decoder_mode mode)
- {
- int result;
- int (*run)(struct mad_decoder *) = 0;
-
- switch (decoder->mode = mode) {
- case MAD_DECODER_MODE_SYNC:
- run = run_sync;
- break;
-
- case MAD_DECODER_MODE_ASYNC:
- # if defined(USE_ASYNC)
- run = run_async;
- # endif
- break;
- }
-
- if (run == 0)
- return -1;
-
- decoder->sync = malloc(sizeof(*decoder->sync));
- if (decoder->sync == 0)
- return -1;
-
- result = run(decoder);
-
- free(decoder->sync);
- decoder->sync = 0;
-
- return result;
- }
而在這個run_sync(struct mad_decoder *decoder)函數中則有一個大的while循環來依次調用
decoder->input_func(decoder->cb_data, stream)獲取mp3源文件,然后交由相關庫函數解碼。
而后會有decoder->output_func(decoder->cb_data, &frame->header, &synth->pcm)函數來輸出解碼后的數據。
3、最后調用mad_decoder_finish(&decoder)結束解碼,釋放decoder資源。
4、在input_func函數中,會調用一個很重要的函數
mad_stream_buffer(stream, buffer->start, buffer->length) ,第一個參數指向一個mad_stream變量,mad_stream結構定義在stream.h頭文件里,用於記錄文件的地址和當前處理的位置。第二、三個參數分別是mp3文件在內存中映像的起始地址和文件長度。mad_stream_buffer()函數將mp3文件與mad_stream結構進行關聯。
四、MP3解碼編程實例
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/stat.h>
- #include <sys/mman.h>
- #include <fcntl.h>
- #include <sys/types.h>
- #include <sys/ioctl.h>
- #include <sys/soundcard.h>
- #include "mad.h"
-
- #define BUFSIZE 8192
-
- /*
- * This is a private message structure. A generic pointer to this structure
- * is passed to each of the callback functions. Put here any data you need
- * to access from within the callbacks.
- */
- struct buffer {
- FILE *fp; /*file pointer*/
- unsigned int flen; /*file length*/
- unsigned int fpos; /*current position*/
- unsigned char fbuf[BUFSIZE]; /*buffer*/
- unsigned int fbsize; /*indeed size of buffer*/
- };
- typedef struct buffer mp3_file;
-
- int soundfd; /*soundcard file*/
- unsigned int prerate = 0; /*the pre simple rate*/
-
- int writedsp(int c)
- {
- return write(soundfd, (char *)&c, 1);
- }
-
- void set_dsp()
- {
- #if 0
- int format = AFMT_S16_LE;
- int channels = 2;
- int rate = 44100;
-
- soundfd = open("/dev/dsp", O_WRONLY);
- ioctl(soundfd, SNDCTL_DSP_SPEED,&rate);
- ioctl(soundfd, SNDCTL_DSP_SETFMT, &format);
- ioctl(soundfd, SNDCTL_DSP_CHANNELS, &channels);
- #else
- if((soundfd = open("test.bin" , O_WRONLY | O_CREAT)) < 0)
- {
- fprintf(stderr , "can't open sound device!\n");
- exit(-1);
- }
- #endif
- }
-
- /*
- * This is perhaps the simplest example use of the MAD high-level API.
- * Standard input is mapped into memory via mmap(), then the high-level API
- * is invoked with three callbacks: input, output, and error. The output
- * callback converts MAD's high-resolution PCM samples to 16 bits, then
- * writes them to standard output in little-endian, stereo-interleaved
- * format.
- */
-
- static int decode(mp3_file *mp3fp);
-
- int main(int argc, char *argv[])
- {
- long flen, fsta, fend;
- int dlen;
- mp3_file *mp3fp;
-
- if (argc != 2)
- return 1;
-
- mp3fp = (mp3_file *)malloc(sizeof(mp3_file));
- if((mp3fp->fp = fopen(argv[1], "r")) == NULL)
- {
- printf("can't open source file.\n");
- return 2;
- }
- fsta = ftell(mp3fp->fp);
- fseek(mp3fp->fp, 0, SEEK_END);
- fend = ftell(mp3fp->fp);
- flen = fend - fsta;
- if(flen > 0)
- fseek(mp3fp->fp, 0, SEEK_SET);
- fread(mp3fp->fbuf, 1, BUFSIZE, mp3fp->fp);
- mp3fp->fbsize = BUFSIZE;
- mp3fp->fpos = BUFSIZE;
- mp3fp->flen = flen;
-
- set_dsp();
-
- decode(mp3fp);
-
- close(soundfd);
- fclose(mp3fp->fp);
-
- return 0;
- }
-
- static enum mad_flow input(void *data, struct mad_stream *stream)
- {
- mp3_file *mp3fp;
- int ret_code;
- int unproc_data_size; /*the unprocessed data's size*/
- int copy_size;
-
- mp3fp = (mp3_file *)data;
- if(mp3fp->fpos < mp3fp->flen) {
- unproc_data_size = stream->bufend - stream->next_frame;
- //printf("%d, %d, %d\n", unproc_data_size, mp3fp->fpos, mp3fp->fbsize);
- memcpy(mp3fp->fbuf, mp3fp->fbuf + mp3fp->fbsize - unproc_data_size, unproc_data_size);
- copy_size = BUFSIZE - unproc_data_size;
- if(mp3fp->fpos + copy_size > mp3fp->flen) {
- copy_size = mp3fp->flen - mp3fp->fpos;
- }
- fread(mp3fp->fbuf+unproc_data_size, 1, copy_size, mp3fp->fp);
- mp3fp->fbsize = unproc_data_size + copy_size;
- mp3fp->fpos += copy_size;
-
- /*Hand off the buffer to the mp3 input stream*/
- mad_stream_buffer(stream, mp3fp->fbuf, mp3fp->fbsize);
- ret_code = MAD_FLOW_CONTINUE;
- } else {
- ret_code = MAD_FLOW_STOP;
- }
-
- return ret_code;
-
- }
-
- /*
- * The following utility routine performs simple rounding, clipping, and
- * scaling of MAD's high-resolution samples down to 16 bits. It does not
- * perform any dithering or noise shaping, which would be recommended to
- * obtain any exceptional audio quality. It is therefore not recommended to
- * use this routine if high-quality output is desired.
- */
-
- static inline signed int scale(mad_fixed_t sample)
- {
- /* round */
- sample += (1L << (MAD_F_FRACBITS - 16));
-
- /* clip */
- if (sample >= MAD_F_ONE)
- sample = MAD_F_ONE - 1;
- else if (sample < -MAD_F_ONE)
- sample = -MAD_F_ONE;
-
- /* quantize */
- return sample >> (MAD_F_FRACBITS + 1 - 16);
- }
-
- /*
- * This is the output callback function. It is called after each frame of
- * MPEG audio data has been completely decoded. The purpose of this callback
- * is to output (or play) the decoded PCM audio.
- */
-
- //輸出函數做相應的修改,目的是解決播放音樂時聲音卡的問題。
- static enum mad_flow output(void *data, struct mad_header const *header,
- struct mad_pcm *pcm)
- {
- unsigned int nchannels, nsamples;
- mad_fixed_t const *left_ch, *right_ch;
- // pcm->samplerate contains the sampling frequency
- nchannels = pcm->channels;
- nsamples = pcm->length;
- left_ch = pcm->samples[0];
- right_ch = pcm->samples[1];
- short buf[nsamples *2];
- int i = 0;
- //printf(">>%d\n", nsamples);
- while (nsamples--) {
- signed int sample;
- // output sample(s) in 16-bit signed little-endian PCM
- sample = scale(*left_ch++);
- buf[i++] = sample & 0xFFFF;
- if (nchannels == 2) {
- sample = scale(*right_ch++);
- buf[i++] = sample & 0xFFFF;
- }
- }
- //fprintf(stderr, ".");
- write(soundfd, &buf[0], i * 2);
- return MAD_FLOW_CONTINUE;
- }
-
- /*
- * This is the error callback function. It is called whenever a decoding
- * error occurs. The error is indicated by stream->error; the list of
- * possible MAD_ERROR_* errors can be found in the mad.h (or stream.h)
- * header file.
- */
-
- static enum mad_flow error(void *data,
- struct mad_stream *stream,
- struct mad_frame *frame)
- {
- mp3_file *mp3fp = data;
-
- fprintf(stderr, "decoding error 0x%04x (%s) at byte offset %u\n",
- stream->error, mad_stream_errorstr(stream),
- stream->this_frame - mp3fp->fbuf);
-
- /* return MAD_FLOW_BREAK here to stop decoding (and propagate an error) */
-
- return MAD_FLOW_CONTINUE;
- }
-
- /*
- * This is the function called by main() above to perform all the decoding.
- * It instantiates a decoder object and configures it with the input,
- * output, and error callback functions above. A single call to
- * mad_decoder_run() continues until a callback function returns
- * MAD_FLOW_STOP (to stop decoding) or MAD_FLOW_BREAK (to stop decoding and
- * signal an error).
- */
-
- static int decode(mp3_file *mp3fp)
- {
- struct mad_decoder decoder;
- int result;
-
- /* configure input, output, and error functions */
- mad_decoder_init(&decoder, mp3fp,
- input, 0 /* header */, 0 /* filter */, output,
- error, 0 /* message */);
-
- /* start decoding */
- result = mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
-
- /* release the decoder */
- mad_decoder_finish(&decoder);
-
- return result;
- }
說明:1、實例原本是基於音頻OSS框架的,當然,在嵌入式領域,ALSA也是兼容OSS接口的;
2、為了在ubuntu上調試方便,並沒有直接往音頻接口,而是創建了一個文件,直接往文件里面寫;
3、上述代碼中的紅色區域重點講解一下
- static enum mad_flow input(void *data, struct mad_stream *stream)
- {
- mp3_file *mp3fp;
- int ret_code;
- int unproc_data_size; /*the unprocessed data's size*/
- int copy_size;
-
- mp3fp = (mp3_file *)data;
- if(mp3fp->fpos < mp3fp->flen) {
- unproc_data_size = stream->bufend - stream->next_frame;
- //printf("%d, %d, %d\n", unproc_data_size, mp3fp->fpos, mp3fp->fbsize);
- memcpy(mp3fp->fbuf, mp3fp->fbuf + mp3fp->fbsize - unproc_data_size, unproc_data_size);
- copy_size = BUFSIZE - unproc_data_size;
- if(mp3fp->fpos + copy_size > mp3fp->flen) {
- copy_size = mp3fp->flen - mp3fp->fpos;
- }
- fread(mp3fp->fbuf+unproc_data_size, 1, copy_size, mp3fp->fp);
- mp3fp->fbsize = unproc_data_size + copy_size;
- mp3fp->fpos += copy_size;
-
- /*Hand off the buffer to the mp3 input stream*/
- mad_stream_buffer(stream, mp3fp->fbuf, mp3fp->fbsize);
- ret_code = MAD_FLOW_CONTINUE;
- } else {
- ret_code = MAD_FLOW_STOP;
- }
-
- return ret_code;
-
- }
我們設置的輸入buff緩沖區的大小是8192字節,但是對於mp3文件來講,不一定這8192個字節就剛好是若干個完整的幀,有可能會有若干字節是輸入下一個幀的,所有要根據struct mad_stream中的兩個指針,標示了緩沖區中的完整幀的起始地址:
- unsigned char const *this_frame; /* start of current frame */
- unsigned char const *next_frame; /* start of next frame */
- unproc_data_size = stream->bufend - stream->next_frame;
得到剩余的下一個幀的數據,並且需要將其從buff數組的尾部拷貝到頭部,再從mp3文件中讀取一部分字節拼湊成下一個8192字節,提交給庫去解碼,如此周而復始。
4、此代碼解碼出來的pcm可以加上44字節的wav頭文件,則可以用播放器正常播放。
五、如何從網絡socket獲取相應數據,邊解碼邊播放
由於我的項目是要實現一個遠程播放器的功能,即手機端的mp3源文件通過wifi傳輸到開發板上解碼播放,所以,對於輸入緩沖區的控制就不像操作文件那個,可以通過file結構體精確控制好讀取的數據位置了,為此,做了些許修改。
可以開兩個線程,一個線程用於接收socket數據,一個用於解碼播放。主要是緩沖區的控制,可以如此實現:將接收buff[]大小設置為8192*10字節,然后,解碼input函數里面的buff[]的大小設置為8192*11字節,也就是說,多余了8192用來緩沖多余的下一幀字節的數據(因為mp3文件的幀不會超過8192字節),那么,區別於上面的思路,我們可以固定的讓socket的buff[]接收8192*10字節的數據,如果解碼的buff[]里面初次解碼后有剩余的數據,仍然將其復制到解碼buff[]的頭部,只是這時候還是將socket的buff[]的8192*10字節的數據加到解碼buff[]的剛剛拷貝的數據后面,所以,這里調用mad_stream_buffer(stream, buf, bsize)中的bsize就是8192*10+剩余的幀數據大小了。
相關參考:
1、作者:cqulpj 網址: http://cqulpj.blogbus.com/logs/68406670.html