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. CentOS-Linux系统下安装MySQL

    一.mysql的安装 Yum(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及CentOS中的Shell前端软件包管理器.基于RPM包管理,能够 ...

  2. IEEE VIS 2018专题

    PoPo数据可视化 聚焦于Web数据可视化与可视化交互领域,发现可视化领域有意思的内容.不想错过可视化领域的精彩内容, 就快快关注我们吧 :) 本文带有视频,浏览视频请关注公众号浏览. IEEE VI ...

  3. javascript实现深克隆的几种方法

    1)普通函数实现 function cloneObject(obj) { if (obj === null || typeof obj !== 'object') { return obj; } va ...

  4. 实验吧Crypto题目Writeup

    这大概是一篇不怎么更新的没什么用的网上已经有了很多差不多的东西的博客. 变异凯撒 忘记了2333 传统知识+古典密码 先查百度百科,把年份变成数字,然后猜测+甲子的意思,一开始以为是加1,后来意识到是 ...

  5. VS2010 创建 windows service 程序

    参考网上保护眼睛程序,自写程序如下. 1.创建一个名词为“CareEyeService”,类型为“WindowsService”的应用程序. 自动生成代码如下图: 2.修改ServiceCareEye ...

  6. mvn install 时候报GBK编码错误解决办法

    在pom.xml里面 <properties> <!-- 文件拷贝时的编码 --> <project.build.sourceEncoding>UTF-</p ...

  7. Google Developers 中国网站正式发布

    Google Developers 中国网站 (developers.google.cn) 正式发布!Google Developers 中国网站是特别为中国开发者而建立的,它汇集了 Google 为 ...

  8. hbase shell基础和常用命令详解

    HBase是Google Bigtable的开源实现,它利用Hadoop HDFS作为其文件存储系统,利用Hadoop MapReduce来处理HBase中的海量数据,利用Zookeeper作为协同服 ...

  9. Java实例---简单的数据库操作

    源码分析 DAOFactory.java package cn.ftl.mysql ; public class DAOFactory { public static IEmpDAO getIEmpD ...

  10. Java实例---简单的个人管理系统

    代码分析 FileOperate.java package com.ftl.testperson; import java.io.File ; import java.io.FileInputStre ...