FFmpeg時間戳詳解。 解碼編碼

對於時間戳一直不懂。 偶然看到這篇博客,寫得很好。 

轉自: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. 當視頻時間戳連續,而音頻時間戳不連續時
不強行修改時間戳,
用插入靜音幀來實現重同步
發佈了136 篇原創文章 · 獲贊 5 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章