ffmpeg转码MPEG2-TS的音视频同步机制分析(转)

一、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. 当视频时间戳连续,而音频时间戳不连续时
    不强行修改时间戳,
    用插入静音帧来实现重同步
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容