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. springcloud 实战 网关zuul使用中遇到的相关问题

    springcloud 实战  网关zuul使用中遇到的相关问题 1.网关zuul使用时,跨域问题在网关中配置pre过滤器: response.setHeader("Access-Contr ...

  2. Bootstrap网格

    首先了解一下,什么是网格? 在平面设计中,网格是一种由一系列用于组织内容的相交的直线(垂直的.水平的)组成的结构(通常是二维的).它广泛应用于打印设计中的设计布局和内容结构.在网页设计中,它是一种用于 ...

  3. 前端面试经典题目(HTML+CSS)二

    1.浏览器页面由哪三层构成,分别是什么,作用是什么? 构成:结构层.表示层.行为层 分别是:HTML.CSS.JavaScript 作用:HTML实现页面结构,CSS完成页面的表现与风格,JavaSc ...

  4. Vue组件通讯

    Vue最常用的组件通讯有三种:父->子组件通讯.子->父组件通讯,兄弟组件通讯.(template用的pug模板语法) 1.父->子组件通讯 父->子组件通讯,是通过props ...

  5. (PowerShell) 重命名文件

    Get-ChildItem -Path C:\temp\test -Filter *.txt | Rename-Item -NewName {$_.Basename.Replace("Old ...

  6. centors7 elasticsearch6.3安装以及问题记录

    1.安装elasticsearch . 安装系统:centors7 1.下载安装包 官网地址:https://www.elastic.co/downloads/past-releases 2.mac文 ...

  7. 启用代理导致 有道云笔记未知错误、网络错误和OneDrive断线

    最近有道云笔记和OneDrive时不时的连接不上服务器,不知道什么原因?后来到有道官网上看了下,才发现,和IE浏览器的设置有关系: 我本地因为之前设置了代理,然后自己忘记了,代理的软件没开,导致IE上 ...

  8. 沉淀再出发:java中的HashMap、ConcurrentHashMap和Hashtable的认识

    沉淀再出发:java中的HashMap.ConcurrentHashMap和Hashtable的认识 一.前言 很多知识在学习或者使用了之后总是会忘记的,但是如果把这些只是背后的原理理解了,并且记忆下 ...

  9. August 27th 2017 Week 35th Sunday

    You can't be brave if you've only had wonderful things happen to you. 人生若只是有美好的境遇,那你也没办法学会勇敢. Wherea ...

  10. December 24th 2016 Week 52nd Saturday

    The first step is as good as half over. 第一步是最关键的一步. If one goes wrong at the first steps, what shoul ...