FFPLAY的原理(四)
意外情况
你们将会注意到我们有一个全局变量quit,我们用它来保证还没有设置程序退出的信号(SDL会自动处理TERM类似的信号)。否则,这个线程将不停地运 行直到我们使用kill -9来结束程序。FFMPEG同样也提供了一个函数来进行回调并检查我们是否需要退出一些被阻塞的函数:这个函数就是 url_set_interrupt_cb。
int decode_interrupt_cb(void) {
return quit;
}
...
main() {
...
url_set_interrupt_cb(decode_interrupt_cb);
...
SDL_PollEvent(&event);
switch(event.type) {
case SDL_QUIT:
quit = ;
...
当然,这仅仅是用来给ffmpeg中的阻塞情况使用的,而不是SDL中的。我们还必需要设置quit标志为1。
为队列提供包
剩下的我们唯一需要为队列所做的事就是提供包了:
PacketQueue audioq;
main() {
...
avcodec_open(aCodecCtx, aCodec);
packet_queue_init(&audioq);
SDL_PauseAudio();
函数SDL_PauseAudio()让音频设备最终开始工作。如果没有立即供给足够的数据,它会播放静音。
我们已经建立好我们的队列,现在我们准备为它提供包。先看一下我们的读取包的循环:
while(av_read_frame(pFormatCtx, &packet)>=) {
// Is this a packet from the video stream?
if(packet.stream_index==videoStream) {
// Decode video frame
....
}
} else if(packet.stream_index==audioStream) {
packet_queue_put(&audioq, &packet);
} else {
av_free_packet(&packet);
}
注意:我们没有在把包放到队列里的时候释放它,我们将在解码后来释放它。
取出包
现在,让我们最后让声音回调函数audio_callback来从队列中取出包。回调函数的格式必需为void callback(void *userdata, Uint8 *stream, int len),这里的userdata就是我们给到SDL的指针,stream是我们要把声音数据写入的缓冲区指针,len是缓冲区的大小。下面就是代码:
void audio_callback(void *userdata, Uint8 *stream, int len) {
AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
int len1, audio_size;
static uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * ) / ];
static unsigned int audio_buf_size = ;
static unsigned int audio_buf_index = ;
while(len > ) {
if(audio_buf_index >= audio_buf_size) {
audio_size = audio_decode_frame(aCodecCtx, audio_buf,
sizeof(audio_buf));
if(audio_size < ) {
audio_buf_size = ;
memset(audio_buf, , audio_buf_size);
} else {
audio_buf_size = audio_size;
}
audio_buf_index = ;
}
len1 = audio_buf_size - audio_buf_index;
if(len1 > len)
len1 = len;
memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
len -= len1;
stream += len1;
audio_buf_index += len1;
}
}
这基本上是一个简单的从另外一个我们将要写的audio_decode_frame()函数中获取数据的循环,这个循环把结果写入到中间缓冲区,尝试着向 流中写入len字节并且在我们没有足够的数据的时候会获取更多的数据或者当我们有多余数据的时候保存下来为后面使用。这个audio_buf的大小为 1.5倍的声音帧的大小以便于有一个比较好的缓冲,这个声音帧的大小是ffmpeg给出的。
最后解码音频
让我们看一下解码器的真正部分:audio_decode_frame
int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf,
int buf_size) {
static AVPacket pkt;
static uint8_t *audio_pkt_data = NULL;
static int audio_pkt_size = ;
int len1, data_size;
for(;;) {
while(audio_pkt_size > ) {
data_size = buf_size;
len1 = avcodec_decode_audio2(aCodecCtx, (int16_t *)audio_buf, &data_size,
audio_pkt_data, audio_pkt_size);
if(len1 < ) {
audio_pkt_size = ;
break;
}
audio_pkt_data += len1;
audio_pkt_size -= len1;
if(data_size <= ) {
continue;
}
return data_size;
}
if(pkt.data)
av_free_packet(&pkt);
if(quit) {
return -;
}
if(packet_queue_get(&audioq, &pkt, ) < ) {
return -;
}
audio_pkt_data = pkt.data;
audio_pkt_size = pkt.size;
}
}
整个过程实际上从函数的尾部开始,在这里我们调用了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就相当于我们的声卡。让我们继续并且编译:
gcc -o tutorial03 tutorial03.c -lavutil -lavformat -lavcodec -lz -lm \
`sdl-config --cflags --libs`
啊哈!视频虽然还是像原来那样快,但是声音可以正常播放了。这是为什么呢?因为声音信息中的采样率--虽然我们把声音数据尽可能快的填充到声卡缓冲中,但是声音设备却会按照原来指定的采样率来进行播放。
我们几乎已经准备好来开始同步音频和视频了,但是首先我们需要的是一点程序的组织。用队列的方式来组织和播放音频在一个独立的线程中工作的很好:它使得程 序更加更加易于控制和模块化。在我们开始同步音视频之前,我们需要让我们的代码更加容易处理。所以下次要讲的是:创建一个线程。
FFPLAY的原理(四)的更多相关文章
- 支持向量机原理(四)SMO算法原理
支持向量机原理(一) 线性支持向量机 支持向量机原理(二) 线性支持向量机的软间隔最大化模型 支持向量机原理(三)线性不可分支持向量机与核函数 支持向量机原理(四)SMO算法原理 支持向量机原理(五) ...
- juc线程池原理(四): 线程池状态介绍
<Thread之一:线程生命周期及五种状态> <juc线程池原理(四): 线程池状态介绍> 线程有5种状态:新建状态,就绪状态,运行状态,阻塞状态,死亡状态.线程池也有5种状态 ...
- FFPLAY的原理
概要 电影文件有很多基本的组成部分.首先,文件本身被称为容器Container,容器的类型决定了信息被存放在文件中的位置.AVI和Quicktime就是容器的例子.接着,你有一组流,例如,你经常有的是 ...
- Vue项目搭建及原理四
四.Vue-cli工作原理及Vue实例创建,工作原理 (一)Vue-cli原理 1.webpack其实使用了node.js的express网页服务器来进行处理网页相关的数据,相当于使用一个类似apac ...
- How Javascript works (Javascript工作原理) (四) 事件循环及异步编程的出现和 5 种更好的 async/await 编程方式
个人总结: 1.讲解了JS引擎,webAPI与event loop合作的机制. 2.setTimeout是把事件推送给Web API去处理,当时间到了之后才把setTimeout中的事件推入调用栈. ...
- Java多线程系列--“JUC线程池”05之 线程池原理(四)
概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...
- FFPLAY的原理(六)
显示视频 这就是我们的视频线程.现在我们看过了几乎所有的线程除了一个--记得我们调用schedule_refresh()函数吗?让我们看一下实际中是如何做的: static void schedule ...
- FFPLAY的原理(三)
播放声音 现在我们要来播放声音.SDL也为我们准备了输出声音的方法.函数SDL_OpenAudio()本身就是用来打开声音设备的.它使用一个叫做SDL_AudioSpec结构体作为参数,这个结构体中包 ...
- FFPLAY的原理(七)
同步音频 现在我们已经有了一个比较像样的播放器.所以让我们看一下还有哪些零碎的东西没处理.上次,我们掩饰了一点同步问题,也就是同步音频到视频而不是其它的同 步方式.我们将采用和视频一样的方式:做一个内 ...
随机推荐
- 利用gradle添加构建版本号
在java的程序中,貌似都没有这个构建版本号的概念,用的诸如eclipse, idea和android studio的IDE也没有直接提供构建版本号的选项.不过我却想在android程序的版本号当中添 ...
- 存储那些事儿(三):OpenStack的块存储Cinder与商业存储的融合
OpenStack是一个美国国家航空航天局和Rackspace合作研发的云端运算软件,以Apache许可证授权,并且是一个自由软件和开放源代码项目.OpenStack是IaaS(基础设施即服务)软 ...
- 海量数据挖掘MMDS week5: 聚类clustering
http://blog.csdn.net/pipisorry/article/details/49427989 海量数据挖掘Mining Massive Datasets(MMDs) -Jure Le ...
- PHP解决中文乱码问题
初学PHP,在汉字页面间传输和转换的时候,遇到了中文乱码问题. 究其原因乱码无外乎以下几种情况: 1.html页本身的乱码问题, 解决方法:纯html页使用<meta http-equiv=&q ...
- Android开机键失灵启动手机的解决办法
问题描述 Android手机的关机键损坏,无法开机. 解决方法 将手机通过USB线链接电脑,进入命令行,找到adb命令所在目录,运行如下命令: adb reboot 注意:用这种方法的前提是,如果你当 ...
- pig读取部分列 (全部列中的少部分列)
pig流式数据,load数据时,不能读入任意列. 但是,可以从头读,只能连续几列.就是前几列.比如10列数据,可以只读前3列.但不能读第3列: 如:数据testdata [wizad@sr104 lm ...
- 扩展GDAL,支持CNSDTF格式(一)
扩展GDAL,支持CNSDTF格式(一) 一. 简介 本文主要根据<中华人民共和国国家标准GB/T17798-2007--地理空间数据交换格式(Geospatialdata tra ...
- React Native之hellWord
初始化项目工程 进入自己的工作空间然后shift+鼠标右键打开命令行窗口执行如下命令创建RN工程HelloWorld: 然后使用Android Studio打开AVD Manager创建模拟器,在打开 ...
- 安卓TV开发(十) 智能电视开发之在线视频直播
转载注明出处:http://blog.csdn.net/sk719887916/article/details/46582987 从<安卓TV开发(八) 移动智能终端多媒体之在线加载网页视频源& ...
- javascript语法之String对象
学习String类就是学习它的一些方法,主要用到方法全部罗列出来.如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transition ...