SDL音频播放编程

Wesley13
• 阅读 718

        在使用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。

2 编程总体框架

SDL的音频编程需要进行回调播放,那么回调函数中的数据包从哪里来呢?当然是ffmpeg从文件里读入来的,但是,因为这里是回调函数,回调函数是运行在单独的线程中的,常用的做法是将ffmpeg从文件中取出来的数据AVPacket存在一个自定义的支持互斥访问的全局队列结构体中,这样两边的线程可以正常的互斥访问这个队列。在ffmpeg官方的tutorials中是定义了一个如下的队列结构体:

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;
}
点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
SDL中的Anti
默认情况下,SDL2中默认是不启用MSAA.为了能启用,必须做些设置.如果渲染器工作在OpenGL/ES模式下,那么直接使用:SDL\_GL\_SetAttribute(SDL\_GL\_MULTISAMPLEBUFFERS,1);SDL\_GL\_SetAttribute(SDL\_GL\_MULTISAMPLESAMPLES,n);即可
Wesley13 Wesley13
2年前
FFMPEG学习
参考雷神的代码:/最简单的SDL2播放音频的例子(SDL2播放PCM)SimplestAudioPlaySDL2(SDL2playPCM)本程序使用SDL2播放PCM音频采样数据。SDL实际上是对底层绘图API(Direct3D,OpenGL)的封装,使用起来明显
Stella981 Stella981
2年前
DevSecOps在百度的实践
本文将从传统 SDL 开始,介绍百度从 SDL 到DevSecOps的演进历程。全文涉及 SDL 的痛点、DevSecOps 建设初衷、实践形式、落地思路,以及落地后的效果与收益,也会介绍DevSecOps在云原生时代的探索思路与落地场景。如果你正准备或者已经参与到企业DevSecOps建设的相关工作中,这篇文章或许能够给你的工作带来一些启发。一、轻量级
Stella981 Stella981
2年前
AVIOInterruptCB结构体分析
1AVIOInterruptCB结构体定义在/usr/include/libavformat/avio.h中有如下的结构体定义,根据头文件中的注释:这是一个回调函数和参数的结构体。有些函数是在阻塞的,用这个回调函数来检查是否中断这个阻塞函数,如果回调函数返回1,那么这个正在阻塞的操作将被中止。那么就用这个结构体里的参数opaque来回调其中的callb
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这