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. C#学习笔记-中英文切换(XML)

    这几天因为软件需要加英文版本,所以查了好久的资料找到了相关的信息,原资料参考:http://blog.csdn.net/softimite_zifeng 上网查的中英文切换大约有两种方式:1.动态加载 ...

  2. 关于VUE中 import 、 export 和 export default 的注意问题

    1.import引入一个依赖包,不需要相对路径.import 引入一个自己写的js文件,是需要相对路径的. 示例:import axios from ‘axios’; import AppServic ...

  3. sql:PostgreSQL

    PostgreSQL sql script: -- Database: geovindu -- DROP DATABASE geovindu; CREATE DATABASE geovindu WIT ...

  4. 安装和使用phpstorm

    下载网址:https://www.jetbrains.com/phpstorm/ 第一步:解压在官网上所下的文件,双击 Phpstorm10.0.3.exe 第二步:安装目录选择,自定义选择安装目录, ...

  5. Anaconda管理多版本的python环境

    通过Conda的环境管理功能,我们能同时安装多个不同版本的Python,并能根据需要自由切换.下面我将给大家分享一下,新增Python版本,切换,再切回主版本的详细过程. 方法/步骤   1 首先确保 ...

  6. 剑指offer相关问题

    1. 变态跳台阶 Fib(n) = Fib(n-1)+Fib(n-2)+Fib(n-3)+..........+Fib(n-n)         =Fib(0)+Fib(1)+Fib(2)+..... ...

  7. python----------闭包 、装饰器

    闭包: 就是内层函数对外层函数(非全局变量的)非全局变量的引用 def func(): name = '老人家' def func1(): print(name)#局部变量对全局变量的引用 因为并没有 ...

  8. C++程序暂停

    //这里的getchar();用来暂停程序,以便查看程序输出的内容 //也可以用system("pause");等来代替

  9. Nginx学习---Nginx的详解_【all】

    1.1. Nginx简介 1.什么是nginx nginx:静态的,开源的www软件,可以解析静态的小文件(低于1M ),支持高并发占用较发少的资源(3W并发,10个进程,内存150M),跨平台 te ...

  10. docker容器修改hosts文件,重启失效问题解决

    docker容器修改hosts文件 搜了一大批资料,有说需要在docker run --hosts...改:dockerfile改:有点麻烦,下面方案比较好: 参照docker吧(https://ti ...