播放声音
现在我们要来播放声音。SDL也为我们准备了输出声音的方法。函数SDL_OpenAudio()本身就是用来打开声音设备的。它使用一个叫做SDL_AudioSpec结构体作为参数,这个结构体中包含了我们将要输出的音频的所有信息。
在我们展示如何建立之前,让我们先解释一下电脑是如何处理音频的。数字音频是由一长串的样本流组成的。每个样本表示声音波形中的一个值。声音按照一个特定 的采样率来进行录制,采样率表示以多快的速度来播放这段样本流,它的表示方式为每秒多少次采样。例如22050和44100的采样率就是电台和CD常用的 采样率。此外,大多音频有不只一个通道来表示立体声或者环绕。例如,如果采样是立体声,那么每次的采样数就为2个。当我们从一个电影文件中等到数据的时 候,我们不知道我们将得到多少个样本,但是ffmpeg将不会给我们部分的样本――这意味着它将不会把立体声分割开来。
SDL播放声音的方式是这样的:你先设置声音的选项:采样率(在SDL的结构体中被叫做freq的表示频率frequency),声音通道数和其它的参 数,然后我们设置一个回调函数和一些用户数据userdata。当开始播放音频的时候,SDL将不断地调用这个回调函数并且要求它来向声音缓冲填入一个特 定的数量的字节。当我们把这些信息放到SDL_AudioSpec结构体中后,我们调用函数SDL_OpenAudio()就会打开声音设备并且给我们送 回另外一个AudioSpec结构体。这个结构体是我们实际上用到的--因为我们不能保证得到我们所要求的。
设置音频
目前先把讲的记住,因为我们实际上还没有任何关于声音流的信息。让我们回过头来看一下我们的代码,看我们是如何找到视频流的,同样我们也可以找到声音流。

 1 // Find the first video stream
 2 videoStream=-1;
 3 audioStream=-1;
 4 for(i=0; i < pFormatCtx->nb_streams; i++) {
 5 if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO
 6 &&
 7 videoStream < 0) {
 8 videoStream=i;
 9 }
10 if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO &&
11 audioStream < 0) {
12 audioStream=i;
13 }
14 }
15 if(videoStream==-1)
16 return -1; // Didn't find a video stream
17 if(audioStream==-1)
18 return -1; 

从这里我们可以从描述流的AVCodecContext中得到我们想要的信息,就像我们得到视频流的信息一样。

1 AVCodecContext *aCodecCtx;
2 aCodecCtx=pFormatCtx->streams[audioStream]->codec; 

包含在编解码上下文中的所有信息正是我们所需要的用来建立音频的信息:

 1 wanted_spec.freq = aCodecCtx->sample_rate;
 2 wanted_spec.format = AUDIO_S16SYS;
 3 wanted_spec.channels = aCodecCtx->channels;
 4 wanted_spec.silence = 0;
 5 wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
 6 wanted_spec.callback = audio_callback;
 7 wanted_spec.userdata = aCodecCtx;
 8 if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
 9 fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
10 return -1;
11 } 

让我们浏览一下这些:
·freq 前面所讲的采样率
·format 告诉SDL我们将要给的格式。在“S16SYS”中的S表示有符号的signed,16表示每个样本是16位长的,SYS表示大小头的顺序是与使用的系统相同的。这些格式是由avcodec_decode_audio2为我们给出来的输入音频的格式。
·channels 声音的通道数
·silence 这是用来表示静音的值。因为声音采样是有符号的,所以0当然就是这个值。
·samples 这是当我们想要更多声音的时候,我们想让SDL给出来的声音缓冲区的尺寸。一个比较合适的值在512到8192之间;ffplay使用1024。
·callback 这个是我们的回调函数。我们后面将会详细讨论。
·userdata 这个是SDL供给回调函数运行的参数。我们将让回调函数得到整个编解码的上下文;你将在后面知道原因。
最后,我们使用SDL_OpenAudio函数来打开声音。
如果你还记得前面的指导,我们仍然需要打开声音编解码器本身。这是很显然的。

1 AVCodec *aCodec;
2 aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
3 if(!aCodec) {
4 fprintf(stderr, "Unsupported codec!\n");
5 return -1;
6 }
7 avcodec_open(aCodecCtx, aCodec);

队列
嗯!现在我们已经准备好从流中取出声音信息。但是我们如何来处理这些信息呢?我们将会不断地从文件中得到这些包,但同时SDL也将调用回调函数。解决方法 为创建一个全局的结构体变量以便于我们从文件中得到的声音包有地方存放同时也保证SDL中的声音回调函数audio_callback能从这个地方得到声 音数据。所以我们要做的是创建一个包的队列queue。在ffmpeg中有一个叫AVPacketList的结构体可以帮助我们,这个结构体实际是一串包 的链表。下面就是我们的队列结构体:

1 typedef struct PacketQueue {
2 AVPacketList *first_pkt, *last_pkt;
3 int nb_packets;
4 int size;
5 SDL_mutex *mutex;
6 SDL_cond *cond;
7 } PacketQueue; 

首先,我们应当指出nb_packets是与size不一样的--size表示我们从packet->size中得到的字节数。你会注意到我们有一 个互斥量mutex和一个条件变量cond在结构体里面。这是因为SDL是在一个独立的线程中来进行音频处理的。如果我们没有正确的锁定这个队列,我们有 可能把数据搞乱。我们将来看一个这个队列是如何来运行的。每一个程序员应当知道如何来生成的一个队列,但是我们将把这部分也来讨论从而可以学习到SDL的 函数。
一开始我们先创建一个函数来初始化队列:

1 void packet_queue_init(PacketQueue *q) {
2 memset(q, 0, sizeof(PacketQueue));
3 q->mutex = SDL_CreateMutex();
4 q->cond = SDL_CreateCond();
5 } 

接着我们再做一个函数来给队列中填入东西:

 1 int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
 2 AVPacketList *pkt1;
 3 if(av_dup_packet(pkt) < 0) {
 4 return -1;
 5 }
 6 pkt1 = av_malloc(sizeof(AVPacketList));
 7 if (!pkt1)
 8 return -1;
 9 pkt1->pkt = *pkt;
10 pkt1->next = NULL;
11 SDL_LockMutex(q->mutex);
12 if (!q->last_pkt)
13 q->first_pkt = pkt1;
14 else
15 q->last_pkt->next = pkt1;
16 q->last_pkt = pkt1;
17 q->nb_packets++;
18 q->size += pkt1->pkt.size;
19 SDL_CondSignal(q->cond);
20 SDL_UnlockMutex(q->mutex);
21 return 0;
22 } 

函数SDL_LockMutex()锁定队列的互斥量以便于我们向队列中添加东西,然后函数SDL_CondSignal()通过我们的条件变量为一个接收函数(如果它在等待)发出一个信号来告诉它现在已经有数据了,接着就会解锁互斥量并让队列可以自由访问。
下面是相应的接收函数。注意函数SDL_CondWait()是如何按照我们的要求让函数阻塞block的(例如一直等到队列中有数据)。

 1 int quit = 0;
 2 static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) {
 3 AVPacketList *pkt1;
 4 int ret;
 5 SDL_LockMutex(q->mutex);
 6 for(;;) {
 7 if(quit) {
 8 ret = -1;
 9 break;
10 }
11 pkt1 = q->first_pkt;
12 if (pkt1) {
13 q->first_pkt = pkt1->next;
14 if (!q->first_pkt)
15 q->last_pkt = NULL;
16 q->nb_packets--;
17 q->size -= pkt1->pkt.size;
18 *pkt = pkt1->pkt;
19 av_free(pkt1);
20 ret = 1;
21 break;
22 } else if (!block) {
23 ret = 0;
24 break;
25 } else {
26 SDL_CondWait(q->cond, q->mutex);
27 }
28 }
29 SDL_UnlockMutex(q->mutex);
30 return ret;
31 } 

正如你所看到的,我们已经用一个无限循环包装了这个函数以便于我们想用阻塞的方式来得到数据。我们通过使用SDL中的函数 SDL_CondWait()来避免无限循环。基本上,所有的CondWait只等待从SDL_CondSignal()函数(或者 SDL_CondBroadcast()函数)中发出的信号,然后再继续执行。然而,虽然看起来我们陷入了我们的互斥体中--如果我们一直保持着这个锁, 我们的函数将永远无法把数据放入到队列中去!但是,SDL_CondWait()函数也为我们做了解锁互斥量的动作然后才尝试着在得到信号后去重新锁定 它。

转载于:https://www.cnblogs.com/djzny/p/3399520.html

FFPLAY的原理(三)相关推荐

  1. HIDL示例-C++服务创建Client验证-Android10.0 HwBinder通信原理(三)

    摘要:本节主要来讲解Android10.0 Native层的HIDL服务创建和Native层的Client验证 阅读本文大约需要花费18分钟. 文章首发微信公众号:IngresGe 专注于Androi ...

  2. Android10.0 Binder通信原理(三)-ServiceManager篇

    摘要:本节主要来讲解Android10.0 Binder中守护进程ServiceManager是如何启动.注册.获取服务 阅读本文大约需要花费35分钟. 文章首发微信公众号:IngresGe 专注于A ...

  3. 跟vczh看实例学编译原理——三:Tinymoe与无歧义语法分析

    文章中引用的代码均来自https://github.com/vczh/tinymoe. 看了前面的三篇文章,大家应该基本对Tinymoe的代码有一个初步的感觉了.在正确分析"print su ...

  4. 双向数据绑定原理(三种实现方式)

    <!DOCTYPE html> <html><head><meta charset="UTF-8"><title>双向数 ...

  5. 电容屏和电磁屏 一:电阻式触摸屏 二:电容式触摸屏以及原理 三:电磁感应触摸屏 PDF电磁屏签名: 签名完成后,点坐转化文件: android 开发 实现网页跳转

    目录 电容屏和电磁屏 一:电阻式触摸屏 二:电容式触摸屏以及原理 三:电磁感应触摸屏

  6. ffplay实现原理

    1. ffmpeg是音视频界的瑞士军刀: 它提供了从录制.编码.封装.推流到拉流.解封装.解码.滤镜.播放的完整解决方案. 它是跨平台的解决方案,一套代码适配Windows.Linux.Mac OS. ...

  7. 翻译: 漫画HTTPS原理三 浏览器和互联网之间的秘密握手

    漫画HTTPS原理五部曲 翻译: 漫画HTTPS原理一 为什么我们需要HTTPS 翻译: 漫画HTTPS原理二 了解对称和非对称加密 翻译: 漫画HTTPS原理三 浏览器和互联网之间的秘密握手 翻译: ...

  8. MyBatis运行原理(三)接口式编程及创建代理对象原理分析

    一.面向接口开发步骤 定义代理接口,将操作数据库的方法定义在代理接口中. 在SQL 映射文件中编写SQL 语句. 将SQL 映射文件注册在MyBatis 的全局配置文件中. 编写测试代码. 二.环境准 ...

  9. FFPLAY的原理(一)

    概要 电影文件有很多基本的组成部分.首先,文件本身被称为容器Container,容器的类型决定了信息被存放在文件中的位置.AVI和Quicktime就是容器的例子.接着,你有一组流,例如,你经常有的是 ...

  10. 编译器之后端原理(三十六)

    一.编译器的后端技术 1. 编译器的前端技术,重点是让编译器能够读懂程序,无结构的代码文本经过前端的处理以后,就变成了Token.AST和语义属性.符号表等结构化的信息,基于这些信息,可以实现简单的脚 ...

最新文章

  1. Spring Framework 5.1.6、5.0.13 与 4.3.23 发布
  2. Python案例:输出指定要求的回文日期
  3. mysql镜像备份和同步备份_mysql 主从同步及备份
  4. 深入解读Docker底层技术cgroup系列(1)——cgroup介绍
  5. ubuntu安装nvidia显卡驱动+cuda+cudnn
  6. python截图搜题_用python的OCR实现自动截图搜题
  7. EPL许可证人话翻译
  8. UE4简单水体使用记录
  9. 树莓派 USB摄像头
  10. h5中的结构元素header、nav、article、aside、section、footer介绍
  11. Notepad++ 替代品开源了!
  12. 实现memcmp函数
  13. 作为技术人员,写博客对我们到底有什么好处?为什么要写博客?
  14. 【ROOT from CERN】——TSpectrum2类与二维寻峰
  15. 本地安装青龙面板教程【详细版】2022-5.5
  16. 弘辽科技:2022年拼多多活动时间表是什么?促销方式有哪些?
  17. Python实现画图软件功能
  18. Novavax和武田宣布在日本合作Novavax的COVID-19候选疫苗
  19. SQL截取字符串(substring与patindex的使用)
  20. 从浏览器地址栏输入url到请求返回发生了什么?

热门文章

  1. clickhouse条件函数
  2. 用户奖励体系有哪些反作弊的机制?
  3. 什么是去中心化?交易所为什么要去中心化?
  4. 企业应用大数据探索发展新路径
  5. UILongPressGestureRecognizer
  6. 今天辞去了联盟的版主职务
  7. 9;XHTML 多媒体
  8. 【eclipse】Multiple annotations found at this line:——解决方法
  9. 配置多台计算机之间ssh无密码登录的一种简便方法
  10. 洛谷P2939 [USACO09FEB]改造路Revamping Trails(最短路)