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;
}
相关文章
相关标签/搜索