ffmpeg轉碼MPEG2-TS的音視頻同步機制分析(轉)

一雾袱、FFmpeg忽略了adaptation_field()數(shù)據(jù)
FFmpeg忽略了包含PCR值的adaptation_filed數(shù)據(jù);
代碼(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函數(shù)
for(; received_sigterm == 0;){}
循環(huán)中):
step1. 解析PES包,得到時間戳望伦、流索引林说、PES包長度等數(shù)據(jù),并將這個PES包壓入到PES包隊列;
見libavformat/mpegts.c函數(shù)
int mpegts_push_data();

step2. 從PES包隊列中取出一個PES包;
見libavformat/utils.c函數(shù)
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);

      并根據(jù)音頻/視頻流的采樣率得到下一幀的PTS和/或DTS;
      見ffmpeg.c函數(shù)
      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);


   如果本幀解碼得到的時間戳和上一幀解碼得到的時間戳的差值超過了設定的閾值,
   為了使輸出的時間戳連續(xù)或同步,
   則需要調整, 如,
   視頻幀時間戳不連續(xù),則丟棄音頻幀以同步
   音頻幀時間戳不連續(xù),則插件靜音幀;
   或是其它的策略煎殷。

step4. 解碼這個PES包中的音/視頻幀, 并壓入到相應的已解碼音頻/視頻幀隊列;
見ffmpeg.c函數(shù)
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. 以已解碼音頻/視頻幀隊列做為輸入, 交錯編碼音頻/視頻幀,并將已編碼數(shù)據(jù)壓入到輸出隊列;
見ffmpeg.c函數(shù)
void do_video_out();
void do_audio_out();

step6. 根據(jù)要編碼輸出的音頻/視頻幀號及相應的采樣率/幀率計算輸出幀的時間戳;
見libavformat/utils.c函數(shù)
int compute_pkt_fields2();

step7. 將這個已編碼音頻/視頻幀的數(shù)據(jù)和時間戳信息一起輸出;
見libavformat/flvenc.c函數(shù)
int flv_write_packet()

step8. 沒有到結束時,跳回到step1.

轉碼中的時間戳流程:

  1. 解碼TS包,
    libavformat/mpegts.c的函數(shù)
    int mpegts_push_data(MpegTSFilter *filter,
    const uint8_t *buf, int buf_size, int is_start,
    int64_t pos);
    功能:
    解析PES包, 獲得時間戳等信息, 并取出負載數(shù)據(jù)組成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的負載數(shù)據(jù)組成TS流 */
  memcpy(pes->buffer+pes->data_index, p, buf_size);
}

}
}

五蝌数、輸入時間戳不邊續(xù)時的處理機制
目的: 輸入時間戳不連續(xù),必須保證輸出時間戳的連續(xù)度秘。

  1. 當視頻時間戳連續(xù),而音頻時間戳不連續(xù)時
    不強行修改時間戳饵撑,
    用插入靜音幀來實現(xiàn)重同步
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末剑梳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子滑潘,更是在濱河造成了極大的恐慌垢乙,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件语卤,死亡現(xiàn)場離奇詭異追逮,居然都是意外死亡,警方通過查閱死者的電腦和手機粹舵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門钮孵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人眼滤,你說我怎么就攤上這事巴席。” “怎么了诅需?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵漾唉,是天一觀的道長。 經常有香客問我堰塌,道長赵刑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任场刑,我火速辦了婚禮般此,結果婚禮上,老公的妹妹穿的比我還像新娘牵现。我一直安慰自己恤煞,他們只是感情好,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布施籍。 她就那樣靜靜地躺著居扒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丑慎。 梳的紋絲不亂的頭發(fā)上喜喂,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天瓤摧,我揣著相機與錄音,去河邊找鬼玉吁。 笑死照弥,一個胖子當著我的面吹牛,可吹牛的內容都是我干的进副。 我是一名探鬼主播这揣,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼影斑!你這毒婦竟也來了给赞?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤矫户,失蹤者是張志新(化名)和其女友劉穎片迅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體皆辽,經...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡柑蛇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了驱闷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耻台。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖空另,靈堂內的尸體忽然破棺而出粘我,到底是詐尸還是另有隱情,我是刑警寧澤痹换,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布征字,位于F島的核電站,受9級特大地震影響娇豫,放射性物質發(fā)生泄漏匙姜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一冯痢、第九天 我趴在偏房一處隱蔽的房頂上張望氮昧。 院中可真熱鬧,春花似錦浦楣、人聲如沸袖肥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽椎组。三九已至,卻和暖如春历恐,著一層夾襖步出監(jiān)牢的瞬間寸癌,已是汗流浹背专筷。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蒸苇,地道東北人磷蛹。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像溪烤,于是被迫代替她去往敵國和親味咳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內容