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_INVALIDp.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() + 0; /* 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/1000));picture_Release(decoded);lost_count++;continue;} else if (late > 0) {// 延迟大于0小于20ms,打印日志并播放msg_Dbg(vout, "picture might be displayed late (missing %d ms)", (int)(late/1000));}}......

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

vlc源码分析(五) 流媒体的音视频同步相关推荐

  1. FFmpeg源码分析:av_read_frame()读取音视频帧

    FFmpeg使用av_read_frame()方法读取音频流.视频流.字幕流,得到AVPacket数据包.FFmpeg官方提供的samples有使用示例,或者在ffplay.c代码中:打开文件/网络流 ...

  2. 直播平台源码搭建教程之Android音视频开发

    直播平台源码搭建教程之Android音视频开发 音频 将声音保存成音频的过程,其实就是将模拟音频数字化的过程,为了实现这个过程,就需要对模拟音频进行采样.量化和编码.接下来我们详细讲解这一过程. 采样 ...

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

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

  4. 【转】ABP源码分析五:ABP初始化全过程

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

  5. Mybatis-Spring源码分析(五) MapperMethod和MappedStatement解析

    前言 基本上这就是Mybatis-Spring源码的最后一篇了,如果想起来什么再单开博客.比起来Spring源码,Mybatis的确实简单一些,本篇就说一下Mybatis中两个十分重要的类Mapper ...

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

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

  7. openstack-nova源码分析(五)flavor extra_specs 扩展属性

    Flavors extra-specs (flavors, os-flavor-extra-specs) Flavor 扩展属性设置, 扩展属性可以用来对虚拟机做一些额外的限制设置,具体的参数,将在后 ...

  8. tcp/ip 协议栈Linux源码分析五 IPv6分片报文重组分析一

    做防火墙模块的时候遇到过IPv6分片报文处理的问题是,当时的问题是netfilter无法基于传输层的端口拦截IPv6分片报文,但是IPv4的分片报文可以.分析了内核源码得知是因为netfilter的连 ...

  9. 谷歌chrome浏览器的源码分析(五)

    上一次说到类RenderThread和类RenderView把消息处理,那么这两个类是怎么样处理消息的呢?又是怎么样处理浏览的消息呢?现在就带着这两个问题去分析它的源码,理解它处理消息的方法.类Ren ...

最新文章

  1. 从源码分析DEARGUI之菜单
  2. 复制、移动和删除:cp, rm, mv
  3. 控制器及其中$scope
  4. how does UI Framework get url of detail page - _router
  5. 洛谷P5110:块速递推(特征根方程、光速幂)
  6. centos 静态拨号
  7. ftp与sftp及sftp和scp的区别
  8. iBatis in 语句参数传入方法
  9. Linux查看vga分辨率,调整ubuntu启动界面vga分辨率的方法
  10. 泰坦尼克号乘客存活预测详细笔记
  11. RS485串口Modbus设备联网解决方案
  12. 开源开放|数据地平线通过OpenKG开放全行业因果事理、大规模实时事理等7类常识知识库...
  13. java 爬取ajax,R语言爬虫系列4|AJAX与动态网页介绍
  14. 计算机网络-第1章-PPT
  15. 靶机Who Wants To Be King 1渗透记录
  16. 路易斯安那州立大学计算机科学,路易斯安那州立大学
  17. 表情符号(emoji)大全,只此一文便够了
  18. 鉴于B站的代码粘贴没有全选功能,up在这里放上软件小妹的脚本代码
  19. 聊一聊最近比较火的多云管理平台
  20. 联诚发召开宝安区卓越绩效管理标准实施项目启动大会

热门文章

  1. OAF_OAF增删改-新增的实现(案例)
  2. RMAN Restore, Recovery
  3. Zabbix 2.2(一):Web监控的监控项
  4. 使用Repeater实现类似GridView编辑功能
  5. POJ - 2516 Minimum Cost(最小费用最大流)
  6. 康托展开与逆展开(原理+模板)
  7. EfficientDet解读
  8. 数据分析与挖掘理论-概述
  9. HDU2888(二维RMQ)
  10. string基本字符系列容器