意外情况
你们将会注意到我们有一个全局变量quit,我们用它来保证还没有设置程序退出的信号(SDL会自动处理TERM类似的信号)。否则,这个线程将不停地运 行直到我们使用kill -9来结束程序。FFMPEG同样也提供了一个函数来进行回调并检查我们是否需要退出一些被阻塞的函数:这个函数就是 url_set_interrupt_cb。

  1. int decode_interrupt_cb(void) {
  2. return quit;
  3. }
  4. ...
  5. main() {
  6. ...
  7. url_set_interrupt_cb(decode_interrupt_cb);
  8. ...
  9. SDL_PollEvent(&event);
  10. switch(event.type) {
  11. case SDL_QUIT:
  12. quit = ;
  13. ...

当然,这仅仅是用来给ffmpeg中的阻塞情况使用的,而不是SDL中的。我们还必需要设置quit标志为1。
为队列提供包
剩下的我们唯一需要为队列所做的事就是提供包了:

  1. PacketQueue audioq;
  2. main() {
  3. ...
  4. avcodec_open(aCodecCtx, aCodec);
  5. packet_queue_init(&audioq);
  6. SDL_PauseAudio();

函数SDL_PauseAudio()让音频设备最终开始工作。如果没有立即供给足够的数据,它会播放静音。
我们已经建立好我们的队列,现在我们准备为它提供包。先看一下我们的读取包的循环:

  1. while(av_read_frame(pFormatCtx, &packet)>=) {
  2. // Is this a packet from the video stream?
  3. if(packet.stream_index==videoStream) {
  4. // Decode video frame
  5. ....
  6. }
  7. } else if(packet.stream_index==audioStream) {
  8. packet_queue_put(&audioq, &packet);
  9. } else {
  10. av_free_packet(&packet);
  11. }

注意:我们没有在把包放到队列里的时候释放它,我们将在解码后来释放它。
取出包
现在,让我们最后让声音回调函数audio_callback来从队列中取出包。回调函数的格式必需为void callback(void *userdata, Uint8 *stream, int len),这里的userdata就是我们给到SDL的指针,stream是我们要把声音数据写入的缓冲区指针,len是缓冲区的大小。下面就是代码:

  1. void audio_callback(void *userdata, Uint8 *stream, int len) {
  2. AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
  3. int len1, audio_size;
  4. static uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * ) / ];
  5. static unsigned int audio_buf_size = ;
  6. static unsigned int audio_buf_index = ;
  7. while(len > ) {
  8. if(audio_buf_index >= audio_buf_size) {
  9. audio_size = audio_decode_frame(aCodecCtx, audio_buf,
  10. sizeof(audio_buf));
  11. if(audio_size < ) {
  12. audio_buf_size = ;
  13. memset(audio_buf, , audio_buf_size);
  14. } else {
  15. audio_buf_size = audio_size;
  16. }
  17. audio_buf_index = ;
  18. }
  19. len1 = audio_buf_size - audio_buf_index;
  20. if(len1 > len)
  21. len1 = len;
  22. memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
  23. len -= len1;
  24. stream += len1;
  25. audio_buf_index += len1;
  26. }
  27. }

这基本上是一个简单的从另外一个我们将要写的audio_decode_frame()函数中获取数据的循环,这个循环把结果写入到中间缓冲区,尝试着向 流中写入len字节并且在我们没有足够的数据的时候会获取更多的数据或者当我们有多余数据的时候保存下来为后面使用。这个audio_buf的大小为 1.5倍的声音帧的大小以便于有一个比较好的缓冲,这个声音帧的大小是ffmpeg给出的。
最后解码音频
让我们看一下解码器的真正部分:audio_decode_frame

  1. int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf,
  2. int buf_size) {
  3. static AVPacket pkt;
  4. static uint8_t *audio_pkt_data = NULL;
  5. static int audio_pkt_size = ;
  6. int len1, data_size;
  7. for(;;) {
  8. while(audio_pkt_size > ) {
  9. data_size = buf_size;
  10. len1 = avcodec_decode_audio2(aCodecCtx, (int16_t *)audio_buf, &data_size,
  11. audio_pkt_data, audio_pkt_size);
  12. if(len1 < ) {
  13. audio_pkt_size = ;
  14. break;
  15. }
  16. audio_pkt_data += len1;
  17. audio_pkt_size -= len1;
  18. if(data_size <= ) {
  19. continue;
  20. }
  21. return data_size;
  22. }
  23. if(pkt.data)
  24. av_free_packet(&pkt);
  25. if(quit) {
  26. return -;
  27. }
  28. if(packet_queue_get(&audioq, &pkt, ) < ) {
  29. return -;
  30. }
  31. audio_pkt_data = pkt.data;
  32. audio_pkt_size = pkt.size;
  33. }
  34. }

整个过程实际上从函数的尾部开始,在这里我们调用了packet_queue_get()函数。我们从队列中取出包,并且保存它的信息。然后,一旦我们有 了可以使用的包,我们就调用函数avcodec_decode_audio2(),它的功能就像它的姐妹函数 avcodec_decode_video()一样,唯一的区别是它的一个包里可能有不止一个声音帧,所以你可能要调用很多次来解码出包中所有的数据。同 时也要记住进行指针audio_buf的强制转换,因为SDL给出的是8位整型缓冲指针而ffmpeg给出的数据是16位的整型指针。你应该也会注意到 len1和data_size的不同,len1表示解码使用的数据的在包中的大小,data_size表示实际返回的原始声音数据的大小。
当我们得到一些数据的时候,我们立刻返回来看一下是否仍然需要从队列中得到更加多的数据或者我们已经完成了。如果我们仍然有更加多的数据要处理,我们把它保存到下一次。如果我们完成了一个包的处理,我们最后要释放它。
就是这样。我们利用主的读取队列循环从文件得到音频并送到队列中,然后被audio_callback函数从队列中读取并处理,最后把数据送给SDL,于是SDL就相当于我们的声卡。让我们继续并且编译:

  1. gcc -o tutorial03 tutorial03.c -lavutil -lavformat -lavcodec -lz -lm \
  2. `sdl-config --cflags --libs`

啊哈!视频虽然还是像原来那样快,但是声音可以正常播放了。这是为什么呢?因为声音信息中的采样率--虽然我们把声音数据尽可能快的填充到声卡缓冲中,但是声音设备却会按照原来指定的采样率来进行播放。
我们几乎已经准备好来开始同步音频和视频了,但是首先我们需要的是一点程序的组织。用队列的方式来组织和播放音频在一个独立的线程中工作的很好:它使得程 序更加更加易于控制和模块化。在我们开始同步音视频之前,我们需要让我们的代码更加容易处理。所以下次要讲的是:创建一个线程。

FFPLAY的原理(四)的更多相关文章

  1. 支持向量机原理(四)SMO算法原理

    支持向量机原理(一) 线性支持向量机 支持向量机原理(二) 线性支持向量机的软间隔最大化模型 支持向量机原理(三)线性不可分支持向量机与核函数 支持向量机原理(四)SMO算法原理 支持向量机原理(五) ...

  2. juc线程池原理(四): 线程池状态介绍

    <Thread之一:线程生命周期及五种状态> <juc线程池原理(四): 线程池状态介绍> 线程有5种状态:新建状态,就绪状态,运行状态,阻塞状态,死亡状态.线程池也有5种状态 ...

  3. FFPLAY的原理

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

  4. Vue项目搭建及原理四

    四.Vue-cli工作原理及Vue实例创建,工作原理 (一)Vue-cli原理 1.webpack其实使用了node.js的express网页服务器来进行处理网页相关的数据,相当于使用一个类似apac ...

  5. How Javascript works (Javascript工作原理) (四) 事件循环及异步编程的出现和 5 种更好的 async/await 编程方式

    个人总结: 1.讲解了JS引擎,webAPI与event loop合作的机制. 2.setTimeout是把事件推送给Web API去处理,当时间到了之后才把setTimeout中的事件推入调用栈. ...

  6. Java多线程系列--“JUC线程池”05之 线程池原理(四)

    概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...

  7. FFPLAY的原理(六)

    显示视频 这就是我们的视频线程.现在我们看过了几乎所有的线程除了一个--记得我们调用schedule_refresh()函数吗?让我们看一下实际中是如何做的: static void schedule ...

  8. FFPLAY的原理(三)

    播放声音 现在我们要来播放声音.SDL也为我们准备了输出声音的方法.函数SDL_OpenAudio()本身就是用来打开声音设备的.它使用一个叫做SDL_AudioSpec结构体作为参数,这个结构体中包 ...

  9. FFPLAY的原理(七)

    同步音频 现在我们已经有了一个比较像样的播放器.所以让我们看一下还有哪些零碎的东西没处理.上次,我们掩饰了一点同步问题,也就是同步音频到视频而不是其它的同 步方式.我们将采用和视频一样的方式:做一个内 ...

随机推荐

  1. 【VSTS 日志】VSTS 所有功能,看这个页面就够了!

    随着Connect();//2015大会的结束,一大波的好消息随之而来.今天小编刚刚发现了Visual Studio Team Services / Team Foundation Server 的完 ...

  2. UNIX环境高级编程——计算机体系结构基础知识

    无论是在CPU外部接总线的设备还是在CPU内部接总线的设备都有各自的地址范围,都可以像访问内存一样访问,很多体系结构(比如ARM)采用这种方式操作设备,称为等都会产生异常. 通常操作系统把虚拟地址空间 ...

  3. 多重DES

    背景:单重DES在穷举攻击下相对比较脆弱 理论依据:以双重DES为例 加密:C = E(K2,E(K1,P))     解密:P = D(K1,D(K2,C)) 要证明多重加密有效,就要证明不存在K3 ...

  4. Google的两种广告推广方式

    1搜索关键字广告推送:AdWords: 覆盖广泛:在全球最大的搜索和网络平台上进行推广. 定位精准:锁定目标客户群体,让潜在客户轻松找上门. 成本可控:仅当用户点击广告时,您才支付费用. 2.网站内容 ...

  5. 在IFrame中查找IFRAME中的元素的方式

    下面是内部iframe找外部mainFrame的情况  var websiteSearchButton = window.parent.parent.document.getElementById(' ...

  6. 深入理解Android IPC机制之Binder机制

    Binder是Android系统进程间通信(IPC)方式之一.Linux已经拥有的进程间通信IPC手段包括(Internet Process Connection): 管道(Pipe).信号(Sign ...

  7. Java 反射之Class用法

    下面示范如果通过Class对象获取对应类的信息: package com.reflect; import java.lang.annotation.Annotation; import java.la ...

  8. Android NDK开发method GetStringUTFChars’could not be resolved

    Android NDK开发method GetStringUTFChars'could not be resolved 图1 最近用到android的ndk,但在eclipse中提示method Ge ...

  9. zTree的调用设使用(跨两个系统,两类技术实现的项目案例SpringMVC+Spring+MyBatis和Struts2+Spring+ibatis框架组合)

    1.从zTree官网上下载zTree的包,zTree的官方网址是:http://www.ztree.me/v3/main.php#_zTreeInfo 2.引入zTree所需的依赖,例如(jQuery ...

  10. 粒子滤波(PF:Particle Filter)

    先介绍概念:来自百科 粒子滤波指:通过寻找一组在状态空间中传播的随机样本来近似的表示概率密度函数,再用样本均值代替积分运算,进而获得系统状态的最小方差估计的过程,波动最小,这些样本被形象的称为&quo ...