ffmpeg文档6:同步音频
指导6:同步音频
同步音频
现在我们已经有了一个比较像样的播放器。所以让我们看一下还有哪些零碎的东西没处理。上次,我们掩饰了一点同步问题,也就是同步音频到视频而不是其它的同步方式。我们将采用和视频一样的方式:做一个内部视频时钟来记录视频线程播放了多久,然后同步音频到上面去。后面我们也来看一下如何推而广之把音频和视频都同步到外部时钟。
生成一个视频时钟
现在我们要生成一个类似于上次我们的声音时钟的视频时钟:一个给出当前视频播放时间的内部值。开始,你可能会想这和使用上一帧的时间戳来更新定时器一样简单。但是,不要忘了视频帧之间的时间间隔是很长的,以毫秒为计量的。解决办法是跟踪另外一个值:我们在设置上一帧时间戳的时候的时间值。于是当前视频时间值就是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文档6:同步音频相关推荐
- 文章标题ffmpeg文档37-视频滤镜
ffmpeg文档37-视频滤镜 37 视频滤镜 在配置编译FFmpeg时可以通过--disable-filters来禁止所有滤镜的编译.也可以配置编译脚本来输出所有包含进编译的滤镜信息. 下面是当前可 ...
- Skydrive与Office的完美结合!多方处理文档不同步问题
Skydrive不是网盘么?和Office结合难道不是在线的Office 365么?你想的没错,这些功能都可以通过浏览器来实现,但是在目前的网络环境下,浏览器实现必定对于网络带宽还有延时要求较高,所以 ...
- 使用 goodsync 软件将指定目录的文档单向同步到 hexo 博客
使用 goodsync 软件将指定目录的文档单向同步到 hexo 博客 我的所有笔记保存在 "我的文档" 目录, 我的笔记类型有 .md, .pdf, .docx 等等, 包括私人 ...
- 免费无限文档云同步方案浅析[2021最新方案][粉丝专属]
需求分析: 格式优美,免费,自动同步,傻瓜式操作,真正的无限存储,本地存储占用少 不安装臃肿窃取隐私的软件 [因此最好是开源的] 方案1:onenote 笔记 + onedirve 5G 空间:[可用 ...
- ArcGIS arcpy代码工具——数据驱动工具批量导出MXD文档并同步导出图片
ArcGIS arcpy代码工具--数据驱动工具批量导出MXD文档并同步导出图片 功能说明: 1 准备工作 设置数据驱动页面 页面范围设置 保存MXD文档 2 代码分段 (1)设定基础数据 (2)调用 ...
- ffmpeg文档2:输出到屏幕
SDL和视频 为了在屏幕上显示,我们将使用SDL.SDL是Simple Direct Layer的缩写.它是一个出色的多媒体库,适用于多平台,并且被用在许多工程中.你可以从它的官方网站的网址 http ...
- ffmpeg文档中文翻译
https://www.longqi.cf/tools/2015/02/13/ffmpegcn/ 1. 概要 ffmpeg [global_options] {[input_file_options] ...
- ffmpeg文档16-音频编码器
16 音频编码器 介绍当前可用的音频编码器 aac AAC(Advanced Audio Coding )编码器 当前原生(内置)编码器还处于实验阶段,而且只能支持AAC-LC(低复杂度AAC).要使 ...
- ffmpeg文档3:播放声音
指导3:播放声音 现在我们要来播放声音.SDL也为我们准备了输出声音的方法.函数SDL_OpenAudio()本身就是用来打开声音设备的.它使用一个叫做SDL_AudioSpec结构体作为参数,这个结 ...
最新文章
- org.apache.hadoop.fs-ChecksumException
- 西数硬盘固件刷新工具_鲁大师Q2季度硬盘排行:三星、西数上榜产品最多
- R语言删除包含缺失值的行并将字符数据列(character)转化为因子列(factor)实战
- flutter环境搭建-完整版
- OpenGL编译错误的解决
- Linux 下 Jni 实现
- yum mysql 启动失败_Linux下MySQL数据库yum升级后无法启动解决办法 | 系统运维
- mysql 链接慢_mysql连接非常慢的觖决办法及其它常见问题解决办法
- Android之面试题精选,自己收藏下
- linux c 封装redis,封装hiredis——C++与redis对接(一)(string的SET与GET操作)
- Luogu 3625 [APIO2009]采油区域
- android备忘录_苹果备忘录怎样把内容置顶?有置顶功能的备忘录便签
- makefile how to
- 在spring boot中3分钟上手RPC框架Dubbo
- iOS 蓝牙开发中数据收发的坑
- 怎么提高c语言编程能力,如何才能提高用C语言编程的能力
- 中级计算机软件师考试试题,计算机水平考试-(a)中级软件设计师下午试题模拟64.doc...
- ORB_VI思想框架
- 工作进度跟踪表excel_在Excel中跟踪时间
- stringsAsFactors=FALSE是什么意思