vlc播放流媒体时实现音视频同步,简单来说就是发送方发送的RTP包带有时间戳,接收方根据此时间戳不断校正本地时钟,播放音视频时根据本地时钟进行同步播放。首先了解两个概念:stream clock和system clock。stream clock是流时钟,可以理解为RTP包中的时间戳;system clock是本地时钟,可以理解为当前系统的Tick数。第一个RTP包到来时:

fSyncTimestamp = rtpTimestamp;// rtp时间戳赋值为本地记录的时间戳
fSyncTime = timeNow;// 本地同步时钟直接赋值为本地当前时钟,注意这样赋值是错误的,但随后就会被RTCP的SR包修正

之后有RTP包到来,则根据上一次RTP包的时间戳差值计算得到真实的时间差值:

// Divide this by the timestamp frequency to get real time:
double timeDiff = timestampDiff/(double)timestampFrequency;// 差值除以90KHz得到真实时间

当RTCP的Sender Report(SR)包到来时,会对fSyncTime进行重置,直接赋值为NTP时间戳

fSyncTime.tv_sec = ntpTimestampMSW - 0x83AA7E80; // 1/1/1900 -> 1/1/1970
double microseconds = (ntpTimestampLSW*15625.0)/0x04000000; // 10^6/2^32
fSyncTime.tv_usec = (unsigned)(microseconds+0.5);

然后以此差值更新fSyncTime,也就是说live555接收部分的时钟fSyncTime既由RTP包时间戳不断的校正,也由RTCP的SR包不断的赋值修改。

在RTSP的Session建立时会创建解码器的本地时钟,本地时钟是一对时钟,包括stream clock和system clock,初始值均为INVALID。

static inline clock_point_t clock_point_Create( mtime_t i_stream, mtime_t i_system )
{
clock_point_t p; p.i_stream = i_stream;// VLC_TS_INVALID
p.i_system = i_system;// VLC_TS_INVALID return p;
}

当RTP数据到来的时候,不仅会更新VLC接收部分的时钟,VLC解码部分的时钟也会通过input_clock_Update()函数更新。当解码部分根据判定stream clock出现较大延迟时,还会重置本地时钟对,重置时设置system clock为当前本机时钟Tick数。live555接收到RTP数据后,存入BufferedPacket中。由于RTP封装H264是按照RFC3984来封装的,所以解析的时候按照该协议解析H264数据,解析时发现NALU起始,就会放入一个block_t中,然后该block_t就被推入以block_t为单位的数据fifo(src\misc\block.c)中,等待解码线程解码。block_t带有pts和dts,均为RTPSource的pts。

解码时如果视频音频都有的话,会创建两个Decoder,每个Decoder包含一个fifo,同时会创建两个解码线程(视频和音频),分别从各自的fifo中取出数据解码。视频和音频解码入口都是DecoderThread,从fifo中取出数据数据进入视频或者音频的解码分支。视频解码线程在解码时会将block_t的pts和dts传递给AVPacket(modules/codec/avcodec/video.c):

pkt.pts = p_block->i_pts;
pkt.dts = p_block->i_dts;

FFmpeg解码视频后,AVFrame将带有时间戳,但是这个时间戳是stream clock,之后会把stream clock转换为system clock,转换函数如下:

static mtime_t ClockStreamToSystem( input_clock_t *cl, mtime_t i_stream )
{
if( !cl->b_has_reference )
return VLC_TS_INVALID; return ( i_stream - cl->ref.i_stream ) * cl->i_rate / INPUT_RATE_DEFAULT + cl->ref.i_system;
}

同理,音频解码完后,也会进行stream clock到system clock的转换。音频的解码后的数据会直接播放,视频解码完的图像帧会放入图像fifo(src\misc\picture_fifo.c)中,等待渲染线程渲染。渲染线程会根据解码后图像的显示时间,决定是否播放:

            ......
decoded = picture_fifo_Pop(vout->p->decoder_fifo);
if (is_late_dropped && decoded && !decoded->b_force) {
const mtime_t predicted = mdate() + ; /* TODO improve */
const mtime_t late = predicted - decoded->date;
if (late > VOUT_DISPLAY_LATE_THRESHOLD) {// 延迟大于20ms,则不予播放,直接释放该图像
msg_Warn(vout, "picture is too late to be displayed (missing %d ms)", (int)(late/));
picture_Release(decoded);
lost_count++;
continue;
} else if (late > ) {// 延迟大于0小于20ms,打印日志并播放
msg_Dbg(vout, "picture might be displayed late (missing %d ms)", (int)(late/));
}
}
......

整个接收流程的框图如下, 可以看出两个解码线程其实并没有直接联系,它们之间的联系是通过音视频数据包的的stream clock转换为system clock,然后渲染线程和声音播放线程根据本地时钟决定是否要播放当前音视频数据。

----------------------------------------------------------------------------------------

播放器内部的音视频同步

以上是流媒体的音视频同步,纯点播播放器同步还会略有不同。

一种同步思路是,视频向音频同步。

因为音频的渲染速率一定,视频只需同步至音频即可。

基于机器的本地时间,创建一个播放器基准时间类,由接收到的音频帧pts不断更新该类的时间。

接收到视频帧时,携带的pts为理论到达时间,根据该pts和此时的本机时间可以计算实际到达时间,根据两者的差值决定该视频帧的具体操作:

1. 实际到达过早:进行等待;

2. 实际到达适中:则计算diff,usleep diff;

3. 实际到达晚一点:则打印日志,计算diff,usleep diff;

4. 实际到达过晚:打印日志,返回结果为丢帧;

vlc源码分析(五) 流媒体的音视频同步的更多相关文章

  1. vlc源码分析(七) 调试学习HLS协议

    HTTP Live Streaming(HLS)是苹果公司提出来的流媒体传输协议.与RTP协议不同的是,HLS可以穿透某些允许HTTP协议通过的防火墙. 一.HLS播放模式 (1) 点播模式(Vide ...

  2. vlc源码分析(三) 调用live555接收RTSP数据

    首先了解RTSP/RTP/RTCP相关概念,尤其是了解RTP协议:RTP与RTCP协议介绍(转载). vlc使用模块加载机制调用live555,调用live555的文件是live555.cpp. 一. ...

  3. VLC源码分析知识总结

    1.  关于#和## 1.1).在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号. 比如在早 ...

  4. Vue系列---理解Vue.nextTick使用及源码分析(五)

    _ 阅读目录 一. 什么是Vue.nextTick()? 二. Vue.nextTick()方法的应用场景有哪些? 2.1 更改数据后,进行节点DOM操作. 2.2 在created生命周期中进行DO ...

  5. ABP源码分析五:ABP初始化全过程

    ABP在初始化阶段做了哪些操作,前面的四篇文章大致描述了一下. 为个更清楚的描述其脉络,做了张流程图以辅助说明.其中每一步都涉及很多细节,难以在一张图中全部表现出来.每一步的细节(会涉及到较多接口,类 ...

  6. MPTCP 源码分析(五) 接收端窗口值

    简述:      在TCP协议中影响数据发送的三个因素分别为:发送端窗口值.接收端窗口值和拥塞窗口值. 本文主要分析MPTCP中各个子路径对接收端窗口值rcv_wnd的处理.   接收端窗口值的初始化 ...

  7. vuex 源码分析(五) action 详解

    action类似于mutation,不同的是Action提交的是mutation,而不是直接变更状态,而且action里可以包含任意异步操作,每个mutation的参数1是一个对象,可以包含如下六个属 ...

  8. jQuery 源码分析(五) map函数 $.map和$.fn.map函数 详解

    $.map() 函数用于使用指定函数处理数组中的每个元素(或对象的每个属性),并将处理结果封装为新的数组返回,该函数有三个参数,如下: elems Array/Object类型 指定的需要处理的数组或 ...

  9. Vue.js 源码分析(五) 基础篇 方法 methods属性详解

    methods中定义了Vue实例的方法,官网是这样介绍的: 例如:: <!DOCTYPE html> <html lang="en"> <head&g ...

随机推荐

  1. java设计模式-观察者模式,装饰者模式

    1.1定义 慨念:定义了对象之间的一对多的依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新. 即:主题和观察者定义了一对多的关系,观察者依赖于主题,只要主题发生变化,观察者就 ...

  2. git 之奇技淫巧

    1,git remote prune origin  本地有很多其实早就被删除的远程分支,可以用 git remote prune origin 全部清除掉,这样再 checkout 别的分支时就清晰 ...

  3. Chrome控制台毫无反应,打印不出信息了?

    最近在使用console.log()方法的时候遇到一个奇怪的问题,打开chrome控制台想调试代码,结果控制台半天无反应,让我纳闷了半天.详情如图所示: 然后我又打开了新的标签页,不行!接着干脆关闭浏 ...

  4. 使用ArcGIS Chef Cookbook轻松搞掂WebGIS平台部署

    1.安装Chef Client v12版本. 2.复制arcgis cookbook资源到Chef安装目录. 3.考虑到一般部署的服务器环境无法连接互联网,所以需要事先部署ArcGIS Cookboo ...

  5. .NET开源工作流RoadFlow-流程设计-流程步骤设置-事件设置

    事件设置是设置当前步骤在提交前后或退回前后要执行的一些操作(该事件为服务器事件). 事件格式为:dll名称.命名空间名称.类名.方法名,这里不需要写括号和参数,处理时会自动带上当前流程实例的相关参数. ...

  6. Android SD卡上文件

    1. 得到存储设备的目录:/SDCARD(一般情况下) SDPATH=Environment.getExternalStorageDirectory()+"/"; 2. 判断SD卡 ...

  7. volley5--Request<T>类的介绍

    源码: /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, V ...

  8. mac下同时安装jdk1.7和jdk1.8

    1.安装jdk1.7时会弹出报错,说版本不兼容. 解决方案 双击安装包,使安装包挂在到机器上,即在Finder里可以看到一个名字为JDK 7 Update 60的Device. 在terminal下输 ...

  9. [翻译] GSProgressView

    GSProgressView 本人极不推荐使用drawRect的方式来绘制下载进度条,无论机器的性能怎么高,使用drawRect用于绘制图形都是低效的. A cute little circular ...

  10. Python学习---DjangoForm的总结大全

    DjangoForm基础知识总结 1.Form是什么东西? 用于验证用户请求数据合法性的一个组件 2. Django的Form的实现步骤: a. 创建一个验证用户请求的模板 from django i ...