SDL音頻播放編程

        在使用SDL進行音頻解碼的時候涉及到一個回調函數,這裏有點複雜,初學不容易搞明白,作點記錄。 程序員

1 SDL_AudioSpec結構體與SDL_OpenAudio()函數

簡單地說,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
那麼其中的callback函數何時被誰調用呢?
#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

2 編程整體框架

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就是包數據了,可是,注意這個不是指針而是包自己。

顯然,定義了數據結構天然還要定義相關的操做才行。簡單地實現以下幾類操做: 線程

1) 隊列初始化


void packet_queue_init(PacketQueue *q) {
  memset(q, 0, sizeof(PacketQueue));
  q->mutex = SDL_CreateMutex();  
  q->cond = SDL_CreateCond(); 
}
用到了SDL中的互斥鎖和SDL的條件變量。

2) 音頻數據包放入隊列


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;
}

3) 從隊列中取出音頻數據包

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;
}
相關文章
相關標籤/搜索