在使用SDL進行音頻解碼的時候涉及到一個回調函數,這裏有點複雜,初學不容易搞明白,作點記錄。 程序員
簡單地說,SDL_AudioSpec結構體中是與SDL進行音頻解碼相關的參數的一個結構體。文檔中內容以下; 編程
SDL_AudioSpec Name SDL_AudioSpec -- Audio Specification Structure Structure Definition typedef struct{ int freq; Uint16 format; Uint8 channels; Uint8 silence; Uint16 samples; Uint32 size; void (*callback)(void *userdata, Uint8 *stream, int len); void *userdata; } SDL_AudioSpec;
其中各成員的意義以下: 數據結構
freq | Audio frequency in samples per second |
format | Audio data format |
channels | Number of channels: 1 mono, 2 stereo |
silence | Audio buffer silence value (calculated) |
samples | Audio buffer size in samples |
size | Audio buffer size in bytes (calculated) |
callback(..) | Callback function for filling the audio buffer |
userdata | Pointer the user data which is passed to the callback function |
#include "SDL.h"int SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained); 框架
在SDL_OpenAudio()的文檔中,此函數用desired指定的參數來打開音頻設備併成功時返回0,並將真正的硬件參數防盜obtained參數指定的結構體中。 函數
其中的desired->callback,這個函數指針是SDL內部在當音頻設備已經準備好處理接下來的數據的時候SDL進行回調的。其中傳入的stream參數是指向SDL內部的音頻緩衝區的指針,len參數指向的是音頻緩衝區的長度(字節爲單位)。其中的userdata參數是咱們在定義SDL_AudioSpec結構體的時候指定的通常跟ffmpeg一塊兒用的話,會實現爲一個AVCodecContext結構體指針用於在回調函數的定義體中實現對音頻數據包的解碼。而後編程的時候咱們來實現這個回調函數。具體的流程是:A 解碼音頻數據包 B 將解碼後的音頻數據向stream參數指向的音頻緩衝區裏面拷貝memcpy(),這樣SDL內部自動會去實現音頻的播放,這個應用程序員就無論了,程序員只管送進去就ok。 ui
SDL的音頻編程須要進行回調播放,那麼回調函數中的數據包從哪裏來呢?固然是ffmpeg從文件裏讀入來的,可是,由於這裏是回調函數,回調函數是運行在單獨的線程中的,經常使用的作法是將ffmpeg從文件中取出來的數據AVPacket存在一個自定義的支持互斥訪問的全局隊列結構體中,這樣兩邊的線程能夠正常的互斥訪問這個隊列。在ffmpeg官方的tutorials中是定義了一個以下的隊列結構體: spa
typedef struct PacketQueue { AVPacketList *first_pkt, *last_pkt; int nb_packets; int size; SDL_mutex *mutex; SDL_cond *cond; } PacketQueue;其中的成員的AVPacketList類型是:
typedef struct AVPacketList { AVPacket pkt; struct AVPacketList *next; } AVPacketList;顯然這個就是一個鏈表的節點而已,其中的AVPacket就是包數據了,可是,注意這個不是指針而是包自己。
顯然,定義了數據結構天然還要定義相關的操做才行。簡單地實現以下幾類操做: 線程
void packet_queue_init(PacketQueue *q) { memset(q, 0, sizeof(PacketQueue)); q->mutex = SDL_CreateMutex(); q->cond = SDL_CreateCond(); }用到了SDL中的互斥鎖和SDL的條件變量。
int packet_queue_put(PacketQueue *q, AVPacket *pkt) { AVPacketList *pkt1; if(av_dup_packet(pkt) < 0) { return -1; } pkt1 = av_malloc(sizeof(AVPacketList)); if (!pkt1) return -1; pkt1->pkt = *pkt; pkt1->next = NULL; SDL_LockMutex(q->mutex); //互斥訪問 //這裏插入的位置是在last_pkt以後,可是先要判斷last_pkt是否爲NULL if (!q->last_pkt) q->first_pkt = pkt1; else q->last_pkt->next = pkt1; q->last_pkt = pkt1; // last_pkt下移 q->nb_packets++; q->size += pkt1->pkt.size; SDL_CondSignal(q->cond); //restart那個由於wait這個條件變量的進程 SDL_UnlockMutex(q->mutex); return 0; }
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) { AVPacketList *pkt1; int ret; SDL_LockMutex(q->mutex); //互斥訪問隊列 for(;;) { if(quit) { // quit是全局變量,用於退出 ret = -1; break; } pkt1 = q->first_pkt; // 顯然每次取包都是取的隊頭的包 if (pkt1) { //若是隊列中對頭不爲空的話 q->first_pkt = pkt1->next; //隊頭指針下移 if (!q->first_pkt) //若是取得的是最後一個包的話,那麼記得設置下last_pkt,不然它會錯指向剛纔已經取走的包 q->last_pkt = NULL; q->nb_packets--; //隊列中的包數減小 q->size -= pkt1->pkt.size; *pkt = pkt1->pkt; //由此能夠看書,這個函數的pkt參數必須是有內存的,不能爲一個指針。 av_free(pkt1); //取出的這個隊列節點已經不用了,必須釋放掉,不然就內存泄漏了,由於他是在前面的put裏av_malloc來的 ret = 1; break; //取完包,且成功了,返回1 } else if (!block) { //這裏表示,隊列爲空,block是阻塞標誌,爲0表示不阻,當即返回0 ret = 0; break; } else { SDL_CondWait(q->cond, q->mutex); // 在這裏阻塞 } } SDL_UnlockMutex(q->mutex); return ret; }