http://blog.chinaunix.net/uid-26000296-id-3483782.html

一、FFmpeg忽略了adaptation_field()数据
FFmpeg忽略了包含PCR值的adaptation_filed数据;
代码(libavformat/mpegts.c)分析如下:

/* 解析TS包 */
int handle_packet(MpegTSContext *ts, const uint8_t *packet)
{
  ...

pid = AV_RB16(packet + 1) & 0x1fff;            //SYNTAX: PID
  is_start = packet[1] & 0x40;                        //SYNTAX: payload_unit_start_indicator
  ...

/* continuity check (currently not used) */
  cc = (packet[3] & 0xf);                              //SYNTAX: continuity_counter   
  expected_cc = (packet[3] & 0x10) ? (tss->last_cc + 1) & 0x0f : tss->last_cc;
  cc_ok = (tss->last_cc < 0) || (expected_cc == cc);
  tss->last_cc = cc;

/* skip adaptation field */ 
  afc = (packet[3] >> 4) & 3;                      //SYNTAX: adaptation_field_control
  p = packet + 4;
  if (afc == 0) /* reserved value */
    return 0;
  if (afc == 2) /* adaptation field only */ 
    return 0;
  if (afc == 3) 
  {    
    /* skip adapation field */
    p += p[0] + 1;            
  }    
  ...
}

二、解码初始时间戳的计算
原理如下:
a. 分析阶段: 分析多个TS包,并找到第一个PES包的PTS,做为初始偏移量;
b. PTS置零:  分析与初始化阶段完成后,
             解码TS的第一个PES包,得到其PTS值,
             减去初始偏移量,使得第一个编码后帧的PTS为零;
c. DTS/PTS增量累加;

1. PTS置零代码分析
main(){
  |-- ...
  |-- parse_options(){
         |-- …
         |-- opt_input_file(){
                |-- … 
                   av_find_stream_info(ic);
                   timestamp = start_time;
                   timestamp += ic->start_time;
                   … 
                   input_files_ts_offset[nb_input_files] = 
                     input_ts_offset - (copy_ts ? 0 : timestamp);
                   …
             }
             …
      }
  |-- transcode(){
        |-- …
            for( ; received_sigterm == 0; ) {
              AVPacket pkt;
              …
              ret = av_read_frame(is, &pkt);
              …
              pkt.dts += av_rescale_q(input_files_ts_offset[nb_input_files],
                                     AV_TIME_BASE_Q, ist->st->time_base);
      }
}

三、编码音视频帧的DTS/PTS计算
音频帧的DTS/PTS计算:
一个音频帧(对于AAC来说, 是1024个采样点),
相对于音频采样率(如 44100个采样点/second = 44.1KHz)来说,
累加上每帧的增量(1024*1000/44100 = 23ms/frame)

st->time_base.den = 1000         //时钟基, 1 second = 1000 ms
frame_size        = 1024              //一帧 = 1024个采样点
st->pts           = {val=0, 
                          num=22050,

den=44100}; // 音频采样率

av_frac_add(&st->pts, (int64_t)st->time_base.den * frame_size);

/* f.val = f.val + ((f.num + incr) / f->den) */
static void av_frac_add(AVFrac *f, int64_t incr)
{       
  int64_t num, den;
  
  num = f->num + incr;
  den = f->den;
  
  if (num < 0) 
  {
    f->val += num / den;
    num     = num % den;

if (num < 0) 
    { 
      num += den;
      f->val--;
    }
  } 
  else if (num >= den) 
  {
    f->val += num / den;
    num = num % den;
  } 
  
  f->num = num;
}
st->pts           = {val=23,        // 计算后的时间戳
                         num=31750, // 上一帧未播放完的余值
                         den=44100}

视频帧的DTS/PTS计算:
一个视频帧,
相对于视频帧率来说(如 25 frames/second),
累加上每帧的增量(1000ms/25frames = 40ms/frame)

time_base.den     = 1000
time_base.num     = 1
st->pts           = {val=0, num=12, den=25}, 
av_frac_add(&st->pts, (int64_t)st->time_base.den * st->codec->time_base.num);

st->pts           = {val=40, num=12, den=25}

四、解码时间戳与编码时间戳的同步机制
正常的转码流程
(ffmpeg version 0.8.10 在ffmpeg.c的transcode函数
for(; received_sigterm == 0;){}
循环中):
step1. 解析PES包,得到时间戳、流索引、PES包长度等数据,并将这个PES包压入到PES包队列;
         见libavformat/mpegts.c函数

int mpegts_push_data();

step2. 从PES包队列中取出一个PES包;
          见libavformat/utils.c函数
          int av_read_frame();
step3. 将这个PES包的PTS和/或DTS减去初始时间戳,
          见ffmpeg.c
          pkt.dts += av_rescale_q(input_files_ts_offset[ist->file_index], AV_TIME_BASE_Q, ist->st->time_base);

pkt.pts += av_rescale_q(input_files_ts_offset[ist->file_index], AV_TIME_BASE_Q, ist->st->time_base);

并根据音频/视频流的采样率得到下一帧的PTS和/或DTS;
          见ffmpeg.c函数
          int output_packet();
          ist->next_pts = ist->pts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q);
          pkt_pts = av_rescale_q(pkt->pts, ist->st->time_base, AV_TIME_BASE_Q);

如果本帧解码得到的时间戳和上一帧解码得到的时间戳的差值超过了设定的阈值,
       为了使输出的时间戳连续或同步,
       则需要调整, 如,
       视频帧时间戳不连续,则丢弃音频帧以同步
       音频帧时间戳不连续,则插件静音帧;
       或是其它的策略。
       
step4. 解码这个PES包中的音/视频帧, 并压入到相应的已解码音频/视频帧队列;
       见ffmpeg.c函数
       int output_packet();
       ret = avcodec_decode_audio3(ist->st->codec, samples, &decoded_data_size,&avpkt);
       ret = avcodec_decode_video2(ist->st->codec,&picture, &got_output, &avpkt);
                     
step5. 以已解码音频/视频帧队列做为输入, 交错编码音频/视频帧,并将已编码数据压入到输出队列;
       见ffmpeg.c函数
       void do_video_out();
       void do_audio_out();

step6. 根据要编码输出的音频/视频帧号及相应的采样率/帧率计算输出帧的时间戳;
       见libavformat/utils.c函数
       int compute_pkt_fields2();
       
step7. 将这个已编码音频/视频帧的数据和时间戳信息一起输出;
       见libavformat/flvenc.c函数
       int flv_write_packet()

step8. 没有到结束时,跳回到step1.

转码中的时间戳流程:
1. 解码TS包,
libavformat/mpegts.c的函数
int mpegts_push_data(MpegTSFilter *filter,
                     const uint8_t *buf, int buf_size, int is_start,
                     int64_t pos);
功能:
解析PES包, 获得时间戳等信息, 并取出负载数据组成ES流。

分析:
int mpegts_push_data(MpegTSFilter *filter,
                     const uint8_t *buf, int buf_size, int is_start,
                     int64_t pos)
{
      
  if (pes->header[0] == 0x00 &&                        //SYNTAX: packet_start_code_prefix 
      pes->header[1] == 0x00 && 
      pes->header[2] == 0x01)
  {
    code = pes->header[3] | 0x100;                    //SYNTAX: stream_id
    pes->total_size = AV_RB16(pes->header + 4); //SYNTAX: PES_packet_length
 
    /* 分配ES的空间 */
    pes->buffer = av_malloc(pes->total_size+FF_INPUT_BUFFER_PADDING_SIZE);

if (code != 0x1bc && code != 0x1bf && /* program_stream_map, private_stream_2 */
        code != 0x1f0 && code != 0x1f1 && /* ECM, EMM */
        code != 0x1ff && code != 0x1f2 && /* program_stream_directory, DSMCC_stream */
        code != 0x1f8)                    /* ITU-T Rec.H.222.1 type E stream
    {
      flags = pes->header[7];                      //SYNTAX: PTS_DTS_flags
      if((flags & 0xc0) == ...)
      {
        pes->pts = ff_parse_pes_pts(r);        //SYNTAX: PTS[32...0]
        r += 5;
        pes->dts = ff_parse_pes_pts(r);        //SYNTAX: DTS[32...0]
        r += 5;
      }
      /* 取出PES的负载数据组成TS流 */
      memcpy(pes->buffer+pes->data_index, p, buf_size);
    }
  }
}

五、输入时间戳不边续时的处理机制
目的: 输入时间戳不连续,必须保证输出时间戳的连续。

1. 当视频时间戳连续,而音频时间戳不连续时
不强行修改时间戳,
用插入静音帧来实现重同步

ffmpeg转码MPEG2-TS的音视频同步机制分析的更多相关文章

  1. ffmpeg 2.3版本号, 关于ffplay音视频同步的分析

    近期学习播放器的一些东西.所以接触了ffmpeg,看源代码的过程中.就想了解一下ffplay是怎么处理音视频同步的,之前仅仅大概知道通过pts来进行同步,但对于怎样实现却不甚了解,所以想借助这个机会, ...

  2. Android 音视频同步机制

    一.概述 音视频同步(avsync),是影响多媒体应用体验质量的一个重要因素.而我们在看到音视频同步的时候,最先想到的就是对齐两者的pts,但是实际使用中的各类播放器,其音视频同步机制都比这些复杂的多 ...

  3. FFmpeg简易播放器的实现-音视频同步

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10284653.html 基于FFmpeg和SDL实现的简易视频播放器,主要分为读取视频文 ...

  4. vlc源码分析(五) 流媒体的音视频同步

    vlc播放流媒体时实现音视频同步,简单来说就是发送方发送的RTP包带有时间戳,接收方根据此时间戳不断校正本地时钟,播放音视频时根据本地时钟进行同步播放.首先了解两个概念:stream clock和sy ...

  5. ffplay的音视频同步分析

    以前工作中参与了一些音视频程序的开发,不过使用的都是芯片公司的SDK,没有研究到更深入一层,比如说音视频同步是怎么回事.只好自己抽点时间出来分析开源代码了,做音视频编解码的人都知道ffmpeg,他在各 ...

  6. ffplay(2.0.1)中的音视频同步

    最近在看ffmpeg相关的一些东西,以及一些播放器相关资料和代码. 然后对于ffmpeg-2.0.1版本下的ffplay进行了大概的代码阅读,其中这里把里面的音视频同步,按个人的理解,暂时在这里作个笔 ...

  7. (转)ffplay的音视频同步分析之视频同步到音频

          以前工作中参与了一些音视频程序的开发,不过使用的都是芯片公司的SDK,没有研究到更深入一层,比如说音视频同步是怎么回事.只好自己抽点时间出来分析开源代码了,做音视频编解码的人都知道ffmp ...

  8. 通俗的解释下音视频同步里pcr作用

    PCR同步在非硬件精确时钟源的情况还是谨慎使用,gstreamer里面采用PCR同步,但是发现好多ffmpeg转的片儿,或者是CP方的片源,pcr打得很粗糙的,老是有跳帧等现象.音视频同步,有三种方法 ...

  9. [SimplePlayer] 8. 音视频同步

    音频与视频在播放当中可能会由于种种原因(如:音视频并非在同一时间开始播放,或视频由于解码任务繁重导致输出图像延迟等)导致音频与视频的播放时间出现偏差,这种就是音视频的同步问题,本文会对音视频同步进行讨 ...

随机推荐

  1. js学习篇--数组按升序降序排列

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. 堆(heap)和栈(stack)的区别

    转: 一.预备知识―程序的内存分配 一个由c/C++编译的程序占用的内存分为以下几个部分 1.栈区(stack)― 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构中 ...

  3. 分布式配置管理平台 - Disconf介绍

    原博客地址:http://blog.csdn.net/zhu_tianwei/article/details/47984545 Disconf专注于各种分布式系统配置管理的通用组件/通用平台,提供统一 ...

  4. hdu1114(完全背包)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1114 分析:很裸的一道完全背包题,只是这里求装满背包后使得价值最少,只需初始化数组dp为inf:dp[ ...

  5. apache代理转发

    打开apache安装目录的conf文件夹下的httpd.conf1.将以下两行前的注释字符 # 去掉:#LoadModule proxy_module modules/mod_proxy.so#Loa ...

  6. 演讲小技巧iPhone+Keynote

    原文发布在简书上:http://www.jianshu.com/p/a45538ca611f 今天在公司里分享了一个技术雷达里关于 ECMAScript 2017 的小 Session,分享加问答总共 ...

  7. Java 逆变与协变的名词说明

    最近在研究Thinking in Java的时候,感觉逆变与协变有点绕,特意整理一下,方便后人.我参考于Java中的逆变与协变,但是该作者整理的稍微有点过于概念化,我在这里简单的说一下 我对于协变于逆 ...

  8. Spring - Spring容器概念及其初始化过程

    引言 工作4年多,做了3年的java,每个项目都用Spring,但对Spring一直都是知其然而不知其所以然.鄙人深知Spring是一个高深的框架,正好近期脱离加班的苦逼状态,遂决定从Spring的官 ...

  9. 一个App与另一个App之间的交互,添加了自己的一些理解

    URL Scheme 是什么? iOS有个特性就是应用将其自身"绑定"到一个自定义 URL scheme 上,该 scheme用于从浏览器或其他应用中启动本应用.常见的分享到第三方 ...

  10. oracle,mysql,sql server三大数据库的事务隔离级别查看方法

    1:mysql的事务隔离级别查看方法 mysql 最简单,执行这条语句就行:select @@tx_isolation  详情: 1.查看当前会话隔离级别 select @@tx_isolation; ...