ong>

同步音頻

现在我们已经有了一个比较像样的播放器。所以让我们看一下还有哪些零碎的东西没处理。上次,我们掩饰了一点同步问题,也就是同步音频到视频而不是其它的同步方式。我们将采用和视频一样的方式:做一个内部视频时钟来记录视频线程播放了多久,然后同步音频到上面去。后面我们也来看一下如何推而广之把音频和视频都同步到外部时钟。

 

生成一个视频时钟

现在我们要生成一个类似于上次我们的声音时钟的视频时钟:一个给出当前视频播放时间的内部值。开始,你可能会想这和使用上一帧的时间戳来更新定时器一样简单。但是,不要忘了视频帧之间的时间间隔是很长的,以毫秒为计量的。解决办法是跟踪另外一个值:我们在设置上一帧时间戳的时候的时间值。于是当前视频时间值就是PTS_of_last_frame + (current_time - time_elapsed_since_PTS_value_was_set)。这种解决方式与我们在函数get_audio_clock中的方式很类似。

所以在我们的大结构体中,我们将放上一个双精度浮点变量video_current_pts和一个64位宽整型变量video_current_pts_time。时钟更新将被放在video_refresh_timer函数中。

void video_refresh_timer(void *userdata) {

if(is->video_st) {

if(is->pictq_size == 0) {

schedule_refresh(is, 1);

} else {

vp = &is->pictq[is->pictq_rindex];

is->video_current_pts = vp->pts;

is->video_current_pts_time = av_gettime();

不要忘记在stream_component_open函数中初始化它:

is->video_current_pts_time = av_gettime();

现在我们需要一种得到信息的方式:

double get_video_clock(VideoState *is) {

double delta;

delta = (av_gettime() - is->video_current_pts_time) / 1000000.0;

return is->video_current_pts + delta;

}

 

提取时钟

但是为什么要强制使用视频时钟呢?我们更改视频同步代码以致于音频和视频不会试着去相互同步。想像一下我们让它像ffplay一样有一个命令行参数。所以让我们抽象一样这件事情:我们将做一个新的封装函数get_master_clock,用来检测av_sync_type变量然后决定调用 get_audio_clock还是get_video_clock或者其它的想使用的获得时钟的函数。我们甚至可以使用电脑时钟,这个函数我们叫做 get_external_clock:

enum {

AV_SYNC_AUDIO_MASTER,

AV_SYNC_VIDEO_MASTER,

AV_SYNC_EXTERNAL_MASTER,

};

#define DEFAULT_AV_SYNC_TYPE AV_SYNC_VIDEO_MASTER

double get_master_clock(VideoState *is) {

if(is->av_sync_type == AV_SYNC_VIDEO_MASTER) {

return get_video_clock(is);

} else if(is->av_sync_type == AV_SYNC_AUDIO_MASTER) {

return get_audio_clock(is);

} else {

return get_external_clock(is);

}

}

main() {

...

is->av_sync_type = DEFAULT_AV_SYNC_TYPE;

...

}

 

同步音频

现在是最难的部分:同步音频到视频时钟。我们的策略是测量声音的位置,把它与视频时间比较然后算出我们需要修正多少的样本数,也就是说:我们是否需要通过丢弃样本的方式来加速播放还是需要通过插值样本的方式来放慢播放?

我们将在每次处理声音样本的时候运行一个synchronize_audio的函数来正确的收缩或者扩展声音样本。然而,我们不想在每次发现有偏差的时候都进行同步,因为这样会使同步音频多于视频包。所以我们为函数synchronize_audio设置一个最小连续值来限定需要同步的时刻,这样我们就不会总是在调整了。当然,就像上次那样,“失去同步”意味着声音时钟和视频时钟的差异大于我们的阈值。

所以我们将使用一个分数系数,叫c,所以现在可以说我们得到了N个失去同步的声音样本。失去同步的数量可能会有很多变化,所以我们要计算一下失去同步的长度的均值。例如,第一次调用的时候,显示出来我们失去同步的长度为40ms,下次变为50ms等等。但是我们不会使用一个简单的均值,因为距离现在最近的值比靠前的值要重要的多。所以我们将使用一个分数系统,叫c,然后用这样的公式来计算差异:diff_sum = new_diff + diff_sum*c。当我们准备好去找平均差异的时候,我们用简单的计算方式:avg_diff = diff_sum * (1-c)。

注意:为什么会在这里?这个公式看来很神奇!嗯,它基本上是一个使用等比级数的加权平均值。我不知道这是否有名字(我甚至查过维基百科!),但是如果想要更多的信息,这里是一个解释http://www.dranger.com/ffmpeg/weightedmean.html 或者在http://www.dranger.com/ffmpeg/weightedmean.txt 里。

下面是我们的函数:

int synchronize_audio(VideoState *is, short *samples,

int samples_size, double pts) {

int n;

double ref_clock;

n = 2 * is->audio_st->codec->channels;

if(is->av_sync_type != AV_SYNC_AUDIO_MASTER) {

double diff, avg_diff;

int wanted_size, min_size, max_size, nb_samples;

ref_clock = get_master_clock(is);

diff = get_audio_clock(is) - ref_clock;

if(diff < AV_NOSYNC_THRESHOLD) {

// accumulate the diffs

is->audio_diff_cum = diff + is->audio_diff_avg_coef

* is->audio_diff_cum;

if(is->audio_diff_avg_count < AUDIO_DIFF_AVG_NB) {

is->audio_diff_avg_count++;

} else {

avg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef);

}

} else {

is->audio_diff_avg_count = 0;

is->audio_diff_cum = 0;

}

}

return samples_size;

}

现在我们已经做得很好;我们已经近似的知道如何用视频或者其它的时钟来调整音频了。所以让我们来计算一下要在添加和砍掉多少样本,并且如何在“Shrinking/expanding buffer code”部分来写上代码:

if(fabs(avg_diff) >= is->audio_diff_threshold) {

wanted_size = samples_size +

((int)(diff * is->audio_st->codec->sample_rate) * n);

min_size = samples_size * ((100 - SAMPLE_CORRECTION_PERCENT_MAX)

/ 100);

max_size = samples_size * ((100 + SAMPLE_CORRECTION_PERCENT_MAX)

/ 100);

if(wanted_size < min_size) {

wanted_size = min_size;

} else if (wanted_size > max_size) {

wanted_size = max_size;

}

记住audio_length * (sample_rate * # of channels * 2)就是audio_length秒时间的声音的样本数。所以,我们想要的样本数就是我们根据声音偏移添加或者减少后的声音样本数。我们也可以设置一个范围来限定我们一次进行修正的长度,因为如果我们改变的太多,用户会听到刺耳的声音。

 

修正样本数

现在我们要真正的修正一下声音。你可能会注意到我们的同步函数synchronize_audio返回了一个样本数,这可以告诉我们有多少个字节被送到流中。所以我们只要调整样本数为wanted_size就可以了。这会让样本更小一些。但是如果我们想让它变大,我们不能只是让样本大小变大,因为在缓冲区中没有多余的数据!所以我们必需添加上去。但是我们怎样来添加呢?最笨的办法就是试着来推算声音,所以让我们用已有的数据在缓冲的末尾添加上最后的样本。

if(wanted_size < samples_size) {

samples_size = wanted_size;

} else if(wanted_size > samples_size) {

uint8_t *samples_end, *q;

int nb;

nb = (samples_size - wanted_size);

samples_end = (uint8_t *)samples + samples_size - n;

q = samples_end + n;

while(nb > 0) {

memcpy(q, samples_end, n);

q += n;

nb -= n;

}

samples_size = wanted_size;

}

现在我们通过这个函数返回的是样本数。我们现在要做的是使用它:

void audio_callback(void *userdata, Uint8 *stream, int len) {

VideoState *is = (VideoState *)userdata;

int len1, audio_size;

double pts;

while(len > 0) {

if(is->audio_buf_index >= is->audio_buf_size) {

audio_size = audio_decode_frame(is, is->audio_buf, sizeof(is->audio_buf), &pts);

if(audio_size < 0) {

is->audio_buf_size = 1024;

memset(is->audio_buf, 0, is->audio_buf_size);

} else {

audio_size = synchronize_audio(is, (int16_t *)is->audio_buf,

audio_size, pts);

is->audio_buf_size = audio_size;

我们要做的是把函数synchronize_audio插入进去。(同时,保证在初始化上面变量的时候检查一下代码,这些我没有赘述)。

结束之前的最后一件事情:我们需要添加一个if语句来保证我们不会在视频为主时钟的时候也来同步视频。

if(is->av_sync_type != AV_SYNC_VIDEO_MASTER) {

ref_clock = get_master_clock(is);

diff = vp->pts - ref_clock;

sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay :

AV_SYNC_THRESHOLD;

if(fabs(diff) < AV_NOSYNC_THRESHOLD) {

if(diff <= -sync_threshold) {

delay = 0;

} else if(diff >= sync_threshold) {

delay = 2 * delay;

}

}

}

添加后就可以了。要保证整个程序中我没有赘述的变量都被初始化过了。然后编译它:

gcc -o tutorial06 tutorial06.c -lavutil -lavformat -lavcodec -lz -lm`sdl-config --cflags --libs`

然后你就可以运行它了。

Ffmpeg和SDL如何同步音频的更多相关文章

  1. [原]如何在Android用FFmpeg+SDL2.0之同步音频

    同步音频的原理可以参考:http://dranger.com/ffmpeg/tutorial05.html  本文是在 [原]如何在Android用FFmpeg+SDL2.0之同步视频 的基础上面继续 ...

  2. Ffmpeg和SDL如何同步视频(转)

    ong> PTS和DTS 幸运的是,音频和视频流都有一些关于以多快速度和什么时间来播放它们的信息在里面.音频流有采样,视频流有每秒的帧率.然而,如果我们只是简单的通过数帧和乘以帧率的方式来同步视 ...

  3. ffmpeg 和 SDL 的结合使用

    FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.采用LGPL或GPL许可证.它提供了录制.转换以及流化音视 频的完整解决方案.它包含了非常先进的音频/视频编解码库 ...

  4. FFmpeg学习6:视音频同步

    在上一篇文章中,视频和音频是各自独立播放的,并不同步.本文主要描述了如何以音频的播放时长为基准,将视频同步到音频上以实现视音频的同步播放的.主要有以下几个方面的内容 视音频同步的简单介绍 DTS 和 ...

  5. Ffmpeg和SDL创建线程(转)

    Spawning Threads Overview Last time we added audio support by taking advantage of SDL's audio functi ...

  6. (转)如何基于FFMPEG和SDL写一个少于1000行代码的视频播放器

    原文地址:http://www.dranger.com/ffmpeg/ FFMPEG是一个很好的库,可以用来创建视频应用或者生成特定的工具.FFMPEG几乎为你把所有的繁重工作都做了,比如解码.编码. ...

  7. [原]如何在Android用FFmpeg+SDL2.0之同步视频

    关于视频同步的原理可以参考http://dranger.com/ffmpeg/tutorial05.html 和 [原]基础学习视频解码之同步视频 这两篇文章,本文是在这两篇的基础上移植到了Andro ...

  8. FFmpeg--如何同步音视频的解决方案

    如何同步视频 PTS和DTS 幸运的是,音频和视频流都有一些关于以多快速度和什么时间来播放它们的信息在里面.音频流有采样,视频流有每秒的帧率.然而,如果我们只是简单的通过数帧和乘以帧率的方式来同步视频 ...

  9. FFmpeg 入门(3):播放音频

    本文转自:FFmpeg 入门(3):播放音频 | www.samirchen.com 音频 SDL 提供了播放音频的方法.SDL_OpenAudio 函数用来让设备播放音频,它需要我们传入一个包含了所 ...

随机推荐

  1. 虚拟机IOS开发环境搭建教程

    来源:http://www.cnblogs.com/xiaoyaoju/archive/2013/05/21/3091171.html 安装条件: 硬件:一台拥有支持虚拟技术的64位双核处理器和2GB ...

  2. Codeforces 474C Captain Marmot 给定4个点和各自旋转中心 问旋转成正方形的次数

    题目链接:点击打开链接 题意: 给定T表示case数 以下4行是一个case 每行2个点,u v 每次u能够绕着v逆时针转90° 问最少操作多少次使得4个u构成一个正方形. 思路: 枚举判可行 #in ...

  3. linux服务器时间同步

    date命令: date :查看当前时间,结果如下:Tue Mar 4 01:36:45 CST 2014 date -s 09:38:40 :设置当前时间,结果如下:Tue Mar 4 09:38: ...

  4. SQL:define和verify命令及替换变量&

    =================替换变量&===============使用一个&符号来指定一个变量值,执行SQL语句时,会提示用户输入一个数值. SQL> select sa ...

  5. .Net Errors

    1.Unknown column 'Extent1.Discriminator' in 'field list' Resole:http://blog.csdn.net/philip502/artic ...

  6. nodejs报错 events.js:72 throw er; // Unhandled 'error' event

    var http = require('http'); var handlerRequest = function(req,res){ res.end('hello');}; var webServe ...

  7. long类型在C#和C++中的异同

    C++中long是32位的整数类型.   而在C#中long是64位的,对应包装类型是Int64,int对应Int32.   显然C++中的long类型,而应该对应C#中的int,   C#调用C++ ...

  8. UIDeviceOrientation UIInterfaceOrientation 区别

    UIDeviceOrientation      是机器硬件的当前旋转方向   这个你只能取值 不能设置 UIInterfaceOrientation   是你程序界面的当前旋转方向   这个可以设置 ...

  9. JavaScript获取css 行间样式,内连样式和外链样式的方式

    [行间样式获取] <div id='div1' style="backgroud:red">测试</div> <script> var odiv ...

  10. jira汉化

    https://translations.atlassian.com/dashboard/dashboard 下载汉化jar文件,在jira中上传插件,系统设置中文即可 LOFTER:我们的故事    ...