一雾袱、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增量累加;
- 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.
轉碼中的時間戳流程:
- 解碼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ù)度秘。
- 當視頻時間戳連續(xù),而音頻時間戳不連續(xù)時
不強行修改時間戳饵撑,
用插入靜音幀來實現(xiàn)重同步