在使用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; }