FFmpeg 時間戳回繞處理:保障流媒體時間連續性的核心機制
一、回繞處理函數
/** * Wrap a given time stamp, if there is an indication for an overflow * * @param st stream // 傳入一個指向AVStream結構體的指針,代表流信息 * @param timestamp the time stamp to wrap // 傳入需要處理的時間戳 * @return resulting time stamp // 返回處理后的時間戳 */
static int64_t wrap_timestamp(const AVStream *st, int64_t timestamp)
{ // 檢查pts_wrap_behavior是否設置為不忽略,且pts_wrap_reference有效,并且傳入的時間戳有效 if (st->pts_wrap_behavior != AV_PTS_WRAP_IGNORE && st->pts_wrap_reference != AV_NOPTS_VALUE && timestamp != AV_NOPTS_VALUE) { // 如果pts_wrap_behavior設置為添加偏移量,并且時間戳小于參考時間戳 if (st->pts_wrap_behavior == AV_PTS_WRAP_ADD_OFFSET && timestamp < st->pts_wrap_reference) // 返回時間戳加上一個由pts_wrap_bits定義的偏移量(通常是2的pts_wrap_bits次方) return timestamp + (1ULL << st->pts_wrap_bits); // 如果pts_wrap_behavior設置為減去偏移量,并且時間戳大于或等于參考時間戳 else if (st->pts_wrap_behavior == AV_PTS_WRAP_SUB_OFFSET && timestamp >= st->pts_wrap_reference) // 返回時間戳減去一個由pts_wrap_bits定義的偏移量 return timestamp - (1ULL << st->pts_wrap_bits); } // 如果不滿足上述條件,則直接返回原始時間戳,不進行任何處理 return timestamp;
}
該函數wrap_timestamp用于處理可能發生溢出的時間戳。在流式媒體處理中,由于時間戳通常是使用固定位數的整數來表示的,當時間戳超過該整數類型能夠表示的最大值時,它可能會回繞(wrap around)到0或者負數,造成處理上的混亂。為了處理這種回繞情況,FFmpeg庫提供了這樣的機制。
-
函數首先檢查是否啟用了時間戳回繞處理(pts_wrap_behavior不為AV_PTS_WRAP_IGNORE),并且有一個有效的參考時間戳(pts_wrap_reference)以及傳入的時間戳是有效的。
-
如果滿足條件,則根據pts_wrap_behavior的值來決定是添加還是減去一個偏移量。偏移量的大小由pts_wrap_bits決定,通常是2的pts_wrap_bits次方。
-
如果pts_wrap_behavior設置為AV_PTS_WRAP_ADD_OFFSET,并且傳入的時間戳小于參考時間戳,說明時間戳即將回繞到0,此時需要加上一個偏移量來避免回繞。
如果pts_wrap_behavior設置為AV_PTS_WRAP_SUB_OFFSET,并且傳入的時間戳大于或等于參考時間戳,說明時間戳還未回繞,此時需要減去一個偏移量來確保時間戳的連續性。
如果上述條件都不滿足,則函數直接返回原始的時間戳,不進行任何處理。
通過這個函數,可以確保在處理流式媒體時,即使時間戳發生回繞,也能得到正確且連續的時間戳值。
二、讀取輸入流時的回繞處理
讀取輸入流時的回繞處理在函數 int ff_read_packet(AVFormatContext *s, AVPacket *pkt);中,代碼如下:
// 獲取指定流的信息,stream_index 是數據包(pkt)所在的流的索引
st = s->streams[pkt->stream_index]; // 調用 update_wrap_reference 函數,根據返回結果和流的 pts_wrap_behavior 設置來決定是否需要對時間戳進行調整
if (update_wrap_reference(s, st, pkt->stream_index, pkt) && st->pts_wrap_behavior == AV_PTS_WRAP_SUB_OFFSET) { // 如果流的 pts_wrap_behavior 是 AV_PTS_WRAP_SUB_OFFSET(即減去偏移量),并且 update_wrap_reference 返回 true // 那么需要修正首次出現的時間戳為負值 // 如果 first_dts 不是相對值,則調用 wrap_timestamp 函數修正它 // 這里的目的是確保時間戳不會因為回繞而導致錯誤的排序 if (!is_relative(st->first_dts)) st->first_dts = wrap_timestamp(st, st->first_dts); // 如果 start_time 不是相對值,同樣調用 wrap_timestamp 函數修正它 // start_time 通常表示流的開始時間 if (!is_relative(st->start_time)) st->start_time = wrap_timestamp(st, st->start_time); // 如果 cur_dts 不是相對值,也調用 wrap_timestamp 函數修正它 // cur_dts 表示當前解碼時間戳 if (!is_relative(st->cur_dts)) st->cur_dts = wrap_timestamp(st, st->cur_dts);
} // 對數據包(pkt)的 dts(解碼時間戳)調用 wrap_timestamp 函數進行修正
pkt->dts = wrap_timestamp(st, pkt->dts); // 對數據包(pkt)的 pts(顯示時間戳)調用 wrap_timestamp 函數進行修正
pkt->pts = wrap_timestamp(st, pkt->pts);