基於bsd socket傳輸libmad解碼,alsa驅動的mp3流媒體播放器
1. 前言
折騰了也有差不多一個來月,之前沒怎麼上手過linux下的C,很多東西都是想當然的狀態就開始東看看西敲敲了。這篇本章意在讓一個小白學會做一個流媒體播放器。我最開始就是個小白。
2. 準備工作
2.1 系統環境
系統環境: ubuntu 12.04
編輯器: code::blocks
2.2 相關庫
alsa-lib: 使用者空間的音效卡介面(不知道這麼描述對不對,其實ubuntu裡alsa音效卡驅動已經有了,這個庫則是提供一個可使用的API)。下載地址: alsa-lib-1.0.25.tar.bz2
關於libmad和alsa的詳細介紹建議搜搜看下。
2.2 庫的安裝
一般步驟
解壓 - 進入解壓目錄,然後
./configure
make
sudo make instal
在make libmad的時候,由於gcc版本問題,可能編譯不過,開啟跟目錄下的Makefile去掉裡面出現的"-fforce-mem"即可解決。
3. 總體設計
3.1 概述
大致思想有接觸過socket程式設計應該很明瞭,問題在於如何邊接收mp3資料邊播放。
有興趣可以看一下下面幾篇文章,我也是看了這幾篇文章才知道怎麼做的。
關於文中提到的minimad.c檔案在編譯完的libmad庫裡面就有,看懂這個程式對之後工作的開展有很大益處。
3.2 需求分析
這個程式是別人的課程設計,我拿來練練手。
主要需求有以下幾個點:
1. Mp3檔案以幀為單位進行傳送,資料包格式可參照RTP。
2. 可手動建立多條socket進行傳輸,socket的傳輸率從10%~100%可自動以。eg. 傳輸率為80%的socket,每10個包中傳送8個包,另2個包丟棄。
3. 接收端有一個適當大小的快取,講包按順序排滿後即可播放,缺失的包需要打印出來。eg. 快取[1,2,3,5,6]已排滿,進行播放,缺少序號為4的包,列印提示。
4. 可雙向播放。
3.3 核心問題
1. Mp3檔案的分割,打包以及傳輸。
2. BSD socket的建立和傳輸。
3. 資料包的重組及播放。
4. 合理的建立、刪除執行緒。
5. 執行緒同步
我這篇文章想寫的是1和3,其他的打算另外寫文章來自我總結。
3.3 問題分析
3.3.1 MP3的檔案格式
想要分割和重組MP3自然需要了解其檔案格式
然後再畫個圖吧。
關於ID3V2,虛線之間的部分為一個標籤幀,一個IDV3V2可以有多個標籤幀。標籤幀記錄著專輯名稱,專輯封面,歌手資訊等等。
3.3.2 ID3V2頭的解析。
實際上我並不需要解析出所有資料,我只想將ID3V2的資訊跳過,知道哪裡是MP3幀資料(也就是聲音資料主體內容)的開始,然後分隔出每個幀進行打包就好了。
當然以學習的心態還是寫了簡單的解析程式碼,標籤幀就沒有解析了,利用Size[4]記錄的標籤大小直接跳過。
關於標籤大小的計算,如果看了3.3.1連結裡的那篇文章應該就會明白了。這裡將公式再寫一下。
total_size = (Size[0]&0x7F)*0x200000 + (Size[1]&0x7F)*0x400 + (Size[2]&0x7F)*0x80 + (Size[3]&0x7F)
將每一位的最高位捨棄,只取低七位,然後帶入公式進行計算。
3.3.2 Frame Header的解析
這個部分就按照結構體頭部的格式細心的進行位操作即可。剛開始寫的時候自己給自己開了不少玩笑,一個位元組的高位低位愣是搞了半天才搞清楚。
解析完頭部,一幀的大小就可以比對著下表進行計算。
表1 MP3 幀頭位元組說明表
名稱 | 位長 | 說明 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
同步資訊 | 11 | 第1、2位元組 | 所有位均為1,第1位元組恆為FF。 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
版本 | 2 | 00-MPEG 2.5 01-未定義 10-MPEG 2 11-MPEG 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
層 | 2 | 00-未定義 01-Layer 3 10-Layer 2 11-Layer 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
CRC校驗 | 1 | 0-校驗 1-不校驗 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
位率 | 4 | 第3位元組 |
取樣率,單位是kbps,例如採用MPEG-1 Layer 3,64kbps是,值為0101。
L1 - Layer 1 L2 - Layer 2 L3 - Layer 3 "free" 表示位率可變 "bad" 表示不允許值 |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
取樣頻率 | 2 | 取樣頻率,對於MPEG-1: 00-44.1kHz 01-48kHz 10-32kHz 11-未定義 對於MPEG-2: 00-22.05kHz 01-24kHz 10-16kHz 11-未定義 對於MPEG-2.5: 00-11.025kHz 01-12kHz 10-8kHz 11-未定義 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
幀長調節 | 1 | 用來調整檔案頭長度,0-無需調整,1-調整,具體調整計算方法見下文。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
保留字 | 1 | 沒有使用。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
聲道模式 | 2 | 第4位元組 | 表示聲道, 00-立體聲Stereo 01-Joint Stereo 10-雙聲道 11-單聲道 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
擴充模式 | 2 |
當聲道模式為01是才使用。
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
版權 | 1 | 檔案是否合法,0-不合法 1-合法 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
原版標誌 | 1 | 是否原版, 0-非原版 1-原版 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
強調方式 | 2 | 用於聲音經降噪壓縮後再補償的分類,很少用到,今後也可能不會用。 00-未定義 01-50/15ms 10-保留 11-CCITT J.17 |
MP3幀長取決於位率和頻率,計算公式為:.
mpeg1.0
layer1:幀長= (48000*bitrate)/sampling_freq + padding
layer2&3: 幀長= (144000*bitrate)/sampling_freq + padding
mpeg2.0
layer1 : 幀長= (24000*bitrate)/sampling_freq + padding
layer2&3 : 幀長= (72000*bitrate)/sampling_freq + padding
3.3.4 資料幀的打包,傳輸以及重組
這裡的工作其實想法很簡單。就是將每一幀的資料截取出來後,在頭部加上MP3的編碼(在程式的初始化時我會預先載入Mp3檔案然後賦予一個唯一的編碼),序號,時間戳和幀的長度作為一個包。接收方讀取完一個包之後就根據序號對包進行排序。當快取滿了之後,將幀資料從每一個包裡提取出來拼接成一個位元組流,然後進行解碼,播放。
4 關鍵程式碼說明
其實如果是我看別人寫的文章的話,前面可能就晃一眼,直接看這裡了。前面的部分其實都能想到。恩,廢話說太多了。直接程式碼吧。
4.1 libmad 如何解碼
首先來看官方的例子: minimad.c
# include <stdio.h>
# include <unistd.h>
# include <sys/stat.h>
# include <sys/mman.h>
# include "mad.h"
/*
* 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(unsigned char const *, unsigned long);
int main(int argc, char *argv[])
{
struct stat stat;
void *fdm;
if (argc != 1)
return 1;
if (fstat(STDIN_FILENO, &stat) == -1 ||
stat.st_size == 0)
return 2;
fdm = mmap(0, stat.st_size, PROT_READ, MAP_SHARED, STDIN_FILENO, 0);
if (fdm == MAP_FAILED)
return 3;
decode(fdm, stat.st_size);
if (munmap(fdm, stat.st_size) == -1)
return 4;
return 0;
}
首先看main方法裡面,傳入的第一個引數無疑是檔案的路徑。fstat和mmap兩個函式在一起做的事情簡單來說就是獲取檔案資訊,然後將檔案資料對映到記憶體中。這和用open代開一個檔案,然後read檔案中的資料到一個數組中是一樣的效果,decode後的兩個引數也可以寫成陣列的首地址和長度。
接下來再看decode函式做了什麼。
/*
* 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 {
unsigned char const *start;
unsigned long length;
};
/*
* 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).
*/<pre name="code" class="cpp">static
int decode(unsigned char const *start, unsigned long length)
{
struct buffer buffer;
struct mad_decoder decoder;
int result;
/* initialize our private message structure */
buffer.start = start;
buffer.length = length;
/* configure input, output, and error functions */
mad_decoder_init(&decoder, &buffer,
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;
}
首先,buffer是一個自定義的結構體,記錄需要解碼的陣列的起始位置和長度。可以看到buffer物件是引用傳遞的方式傳入到mad_decoder_init方法中的,為什麼特地說下這個?因為當我們進行流媒體播放時,每次載入的都是一部分資料,所以需要不停的解碼同一個快取裡存放的資料。當快取裡的資料解碼完畢,新的資料寫進來時,我們無須再次呼叫mad_docoder_init的方法進行初始化,而是更改buffer的length的值,(在後面會講到,buffer.length的值將被置為0來表示資料已解析完畢)再呼叫mad_decoder_run方法來繼續進行解碼。mad_decoder_init 同時還為decorder綁定了input,output,和error函式。
/*
* This is the input callback. The purpose of this callback is to (re)fill
* the stream buffer which is to be decoded. In this example, an entire file
* has been mapped into memory, so we just call mad_stream_buffer() with the
* address and length of the mapping. When this callback is called a second
* time, we are finished decoding.
*/
static
enum mad_flow input(void *data,
struct mad_stream *stream)
{
struct buffer *buffer = data;
if (!buffer->length)
return MAD_FLOW_STOP;
mad_stream_buffer(stream, buffer->start, buffer->length);
buffer->length = 0;
return MAD_FLOW_CONTINUE;
}
/*
* 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];
while (nsamples--) {
signed int sample;
/* output sample(s) in 16-bit signed little-endian PCM */
sample = scale(*left_ch++);
putchar((sample >> 0) & 0xff);
putchar((sample >> 8) & 0xff);
if (nchannels == 2) {
sample = scale(*right_ch++);
putchar((sample >> 0) & 0xff);
putchar((sample >> 8) & 0xff);
}
}
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)
{
struct buffer *buffer = data;
fprintf(stderr, "decoding error 0x%04x (%s) at byte offset %u\n",
stream->error, mad_stream_errorstr(stream),
stream->this_frame - buffer->start);
/* return MAD_FLOW_BREAK here to stop decoding (and propagate an error) */
return MAD_FLOW_CONTINUE;
}
scale方法我不懂,事實上後來再自己寫程式的時候也把這個函式刪除掉了。
看到input函式,很直觀的,mad_stream_buffer將資料寫入解碼工作流水線,然後將buffer.length置為0(去掉這句在流處理時會出錯,我猜測在其內部實現時應該還會用到這個結構體)。然後就是output函式。我們知道,一般的音訊資料可分為兩個左右聲道,所以會有sample[2]兩組資料。最後解碼出的資料叫做PCM,pcm是音效卡可以直接識別並播放的資料。
4.2 PCM資料如何播放
minimad中的方法僅僅只是將pcm資料列印在了控制檯。如果你在網上也搜尋過類似的文章,也許會看到有人在這裡將pcm資料寫到了/dev/dsp裝置中,我的UBUNTU沒有這個裝置,所以用到的詩alsa,我的output方法詩這麼寫的
enum mad_flow Mp3Player::output(void *data, struct mad_header const *header, struct mad_pcm *pcm)
{
unsigned int nchannels, nsamples;
if(!playback_handle) //alsa裝置未開啟
{
if(setupSndMixer() < 0)
{
fprintf(stdout, "setup Snd Mixer error");
return MAD_FLOW_STOP;
}
if(open_device(header) < 0)
{
fprintf(stdout, "open device error");
return MAD_FLOW_STOP;
}
}
/* pcm->samplerate contains the sampling frequency */
nchannels = pcm->channels;
nsamples = pcm->length;
int j=0;
unsigned char* buf = new unsigned char[nsamples * 4 * nchannels];
unsigned char* OutputPtr = buf;
unsigned int samples = nsamples;
while (nsamples--) {
signed int sample;
/* output sample(s) in 32-bit signed little-endian PCM */
sample = pcm->samples[0][j];
*(OutputPtr++) = sample & 0xff;
*(OutputPtr++) = (sample >> 8) & 0xff;
*(OutputPtr++) = (sample >> 16) & 0xff;
*(OutputPtr++) = (sample >> 24) & 0xff;
if (nchannels == 2) {
sample = pcm->samples[1][j];
*(OutputPtr++) = sample & 0xff;
*(OutputPtr++) = (sample >> 8) & 0xff;
*(OutputPtr++) = (sample >> 16) & 0xff;
*(OutputPtr++) = (sample >> 24) & 0xff;
}
j++;
}
//printf("writing data from data to dev\r\n");
int err;
if ((err = snd_pcm_writei (playback_handle, buf, samples)) < 0)
{
err = xrun_recovery(playback_handle, err);
if (err < 0) {
printf("Write error: %s\n", snd_strerror(err));
return MAD_FLOW_CONTINUE;
}
}
//printf("finished! release resource\r\n");
delete[] buf;
return MAD_FLOW_CONTINUE;
}
playback_handle是音效卡的控制控制代碼(個人理解),open_device做的工作一些播放參數進行初始化設定,傳入的header是libmad的mad_header物件,包含了幀的取樣率等資訊,這些在初始化alsa驅動的時候是必不可少的。之後我們定義一個臨時快取,將兩個通道的資料寫入同一個變數中,通過snd_pcm_writei方法寫入裝置進行播放。
對於open_device, 具體是這樣實現的。
int Mp3Player::open_device(struct mad_header const *header)
{
int err;
snd_pcm_hw_params_t *hw_params;
char *pcm_name = "default";
unsigned int rate = header->samplerate;
int channels = 2;
if (header->mode == 0) {
channels = 1;
} else {
channels = 2;
}
if ((err = snd_pcm_open (&playback_handle,
pcm_name, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
printf("cannot open audio device %s (%s)\n",
pcm_name,
snd_strerror (err));
return -1;
}
if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
printf("cannot allocate hardware parameter structure (%s)\n",
snd_strerror (err));
return -1;
}
if ((err = snd_pcm_hw_params_any (playback_handle, hw_params)) < 0) {
printf("cannot initialize hardware parameter structure (%s)\n",
snd_strerror (err));
return -1;
}
if ((err = snd_pcm_hw_params_set_access (playback_handle, hw_params,
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
printf("cannot set access type (%s)\n",
snd_strerror (err));
return -1;
}
if ((err = snd_pcm_hw_params_set_format (playback_handle,
hw_params, SND_PCM_FORMAT_S32_LE)) < 0) {
printf("cannot set sample format (%s)\n",
snd_strerror (err));
return -1;
}
if ((err = snd_pcm_hw_params_set_rate_near (playback_handle,
hw_params, &rate, 0)) < 0) {
printf("cannot set sample rate (%s)\n",
snd_strerror (err));
return -1;
}
if ((err = snd_pcm_hw_params_set_channels (playback_handle,
hw_params, channels)) < 0) {
printf("cannot set channel count (%s)\n",
snd_strerror (err));
return -1;
}
if ((err = snd_pcm_hw_params (playback_handle,
hw_params)) < 0) {
printf("cannot set parameters (%s)\n",
snd_strerror (err));
return -1;
}
snd_pcm_hw_params_free (hw_params);
if ((err = snd_pcm_prepare (playback_handle)) < 0) {
printf("cannot prepare audio interface for use (%s)\n",
snd_strerror (err));
return -1;
}
return 0;
}
pcm_name設定為“default”是下放控制權的做法,也是最傻瓜式的做法,另外還有三個可選引數。(我寫程式碼的時候除了default都是找不到裝置)
大多數引數都是MP3資料幀的直接對映,SND_PCM_FORMAT_S32_LE說明採用的詩32位的取樣資訊,對應的output函式中就需要寫入32位的PCM資料。
這裡寫到的都過於簡單了,有興趣還可以多找找關於alsa的資料,不過中文資料很有限就是了。
4.3 流播放的大致工作流程
我並不確定這是不是最合理的方法,我的想法是,一個執行緒(如果是多個socket那就是多執行緒)負責往快取佇列裡寫資料,寫完之後將佇列裡的資料複製到一個數組快取中,寫完之後通知播放執行緒開始對陣列快取進行解碼播放,而寫入執行緒可以繼續往快取佇列中寫資料。
請看程式碼:
int Mp3Player::start_Player()
{
pthread_t tid;
int err;
if((err = pthread_create(&tid, NULL, frame_play, NULL)) != 0 )
{
fprintf(stderr,"error - start_Player:pthread_create FAILED \r\n");
return -1;
}
return 0;
}
void* Mp3Player::frame_play(void* arg)
{
while(1)
{
sem_wait(&_ms_full);
pthread_mutex_lock(&playBufMutex);
_m_buffer.start = _ms_byteBuffer;
_m_buffer.length = _ms_offset;
mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
pthread_mutex_unlock(&playBufMutex);
sem_post(&_ms_empty);
}
return ((void*)arg);
}
這部分是播放執行緒,採用了執行緒同步中的訊號量和鎖機制,一共兩個訊號量,並進行如下初始化sem_init(&_ms_empty, 0, 1);
sem_init(&_ms_full, 0, 0);
當陣列快取寫滿之後,會sem_post(_ms_full),訊號量_ms_full值加1,則播放執行緒會進一步獲取快取鎖,然後進行解碼。每次解碼前都要定義好start和length,雖然start不會變,但是每次快取的長度還是會略有差別。mad_decoder_run方法之前已經提到過,它是解碼的起點。
播放器的資料寫入入口是addFrame函式,首先我自定義個一個幀的結構體mp3_frame
struct mp3_frame
{
unsigned int _order;
unsigned int _length;
unsigned char* _data;
~mp3_frame()
{
delete[] _data;
}
};
序號,幀長度和資料。
int Mp3Player::addFrame(mp3_frame* f)
{
//插入排序放出快取中
int pos = 0;
for(int i=0; i<=_ms_size; i++)
{
if(_ms_frameBuffer[i] == 0)// 空位,直接填補
{
_ms_frameBuffer[i] = f;
pos = i;
}
else if(f->_order == _ms_frameBuffer[i]->_order) // 出現重複幀
{
delete f;
f=0;
return -1;
}
else if(f->_order < _ms_frameBuffer[i]->_order) // 幀優先,i後幀全部後移
{
for(int j=_ms_size; j > i; j--)
_ms_frameBuffer[j] = _ms_frameBuffer[j-1];
_ms_frameBuffer[i] = f;
pos = i;
break;
}
}
_ms_size++;
_ms_totalBufLength += f->_length;
if(_ms_size == BUFSIZE)//快取已滿
{
//組裝
if(_ms_totalBufLength > BYTEBUFSIZE)
{
printf("error - byteBuffer is too small.\r\n");
return -1;
}
sem_wait(&_ms_empty);
pthread_mutex_lock(&playBufMutex);
_ms_offset = 0;
for(int i=0; i<BUFSIZE; i++)
{
if(i>0)//列印缺失的幀
{
int lostFrames = _ms_frameBuffer[i]->_order - _ms_frameBuffer[i-1]->_order;
if(lostFrames == 2)
{
printf("\033[%dC\033[%dAlost frame %d. \r\n\033[%dB",70,1,_ms_frameBuffer[i-1]->_order+1,1);
}
else if(lostFrames > 2)
{
printf("\033[%dC\033[%dAlost frame %d~%d.\r\n\033[%dB",70,1,_ms_frameBuffer[i-1]->_order+1,_ms_frameBuffer[i]->_order-1,1);
}
}
memcpy(&_ms_byteBuffer[_ms_offset], _ms_frameBuffer[i]->_data, _ms_frameBuffer[i]->_length * sizeof(unsigned char));
_ms_offset += _ms_frameBuffer[i]->_length;
}
pthread_mutex_unlock(&playBufMutex);
sem_post(&_ms_full);
for(int i=0; i < BUFSIZE; i++)//清空佇列快取
{
delete _ms_frameBuffer[i];
_ms_frameBuffer[i] = 0;
}
//初始化相關變數
_ms_size = 0;
_ms_totalBufLength = 0;
}
return pos;
}
第一個迴圈裡的方法就是將幀寫入快取佇列_ms_frameBuffer,每次寫入都是安順序插入,保證每次寫入後佇列裡都是按照序號從小到大排好的順序。當快取寫滿後,將_ms_frameBuffer裡的資料寫入_ms_byteBuffer中,每次寫入前都要確保之前_ms_byteBuffer裡的資料已經被解碼了,故要等待_ms_empty訊號,寫完之後就發出_ms_full訊號讓播放執行緒結束等待開始解碼。5.寫在後面
本來雄心滿滿的想寫的豐滿一點,寫到這裡還是覺得骨感。很多東西之前想寫,卻發現純屬個人設計範疇,沒必要寫出來阻礙了各位程式設計師的想象力。
有些東西想繼續寫卻發現瞭解不深寫不出來。
最後遺留的問題是播放雖然沒有問題了,但播放時會出現明顯的頓卡感覺,懷疑不應該多次呼叫mad_decoder_run方法來進行解碼,而是應該修改input的函式,來達到迴圈解碼的目的。不過大致功能已經完成了就不想再多深究了。程式碼寫累了哎。