概念
PTS(Presentation Time Stamp)顯示時間戳
- 表示:該幀應該在什么時間被顯示/播放。
- 主要用于:同步音頻與視頻,控制播放節奏。
- 舉例:視頻幀 A 的 PTS 是 300ms,表示應在視頻播放第 300 毫秒時顯示。
DTS(Decoding Time Stamp)解碼時間戳
- 表示:該幀應該在什么時間被解碼。
- 主要用于:解碼器按正確順序解碼幀(尤其對于有 B 幀的視頻)。
作用
因為視頻編碼中存在幀的重排序:
編碼時為了壓縮率更高,視頻通常采用如下三種幀類型:
- I幀(關鍵幀):可以獨立解碼。
- P幀(預測幀):依賴前面的幀。
- B幀(雙向預測幀):依賴前后幀。
這就導致:
顯示順序 ≠ 解碼順序
因此,必須使用 PTS 控制顯示順序,使用 DTS 控制解碼順序。
示例
假設視頻幀順序如下(按顯示順序):
幀類型 | 顯示順序 (PTS) | 解碼順序 (DTS) |
---|---|---|
I | 0 | 0 |
B | 1 | 2 |
B | 2 | 3 |
P | 3 | 1 |
解碼器必須這樣處理:
- 先解碼 I(DTS=0)
- 再解碼 P(DTS=1)
- 然后可以解碼 B(DTS=2,依賴 I 和 P)
- 然后解碼下一幀 B(DTS=3)
但播放時按照 PTS 排序:I → B → B → P
賦值
1. 沒有B幀的情況(PTS = DTS)
例如只使用 I 幀 + P 幀:
int64_t pts = frame_index * frame_interval; // 單位:時間基(如1/90000)
int64_t dts = pts;
frame_interval = 時間基 / 幀率
,例如:1/25fps => 每幀間隔 3600(如果時間基是 90000)
2. 有B幀的情況(PTS ≠ DTS)
例如 IBBP 結構(假設 GOP = IPBBP):
- 編碼順序:I P B B P
- 顯示順序:I B B P P
幀類型 | 編碼順序 | DTS | PTS |
---|---|---|---|
I | 0 | 0 | 0 |
P | 1 | 1 | 3 |
B | 2 | 2 | 1 |
B | 3 | 3 | 2 |
P | 4 | 4 | 4 |
通常:PTS = 顯示順序 * frame_interval,DTS = 編碼順序 * frame_interval。
這種關系在 H.264/H.265 中通過 AVC reorder buffer 或 Decoding Order Number (DON) 推導。
示例
- 幀率:25fps → 每幀間隔 3600(以時間基
1/90000
為例) - GOP結構:
I B B P
循環 - 顯示順序:
I B B P I B B P ...
- 編碼順序:
I P B B I P B B ...
代碼(c++):
#include <iostream>
#include <vector>
#include <string>
#include <map>enum FrameType {I_FRAME,P_FRAME,B_FRAME
};struct Frame {int display_index; // 顯示順序編號int encode_index; // 編碼順序編號int64_t pts; // 顯示時間戳int64_t dts; // 解碼時間戳FrameType type;
};// 模擬一個 GOP(IBBP)
std::vector<Frame> generate_gop_with_bframes(int gop_size, int frame_interval) {std::vector<Frame> display_order; // 顯示順序std::vector<Frame> encode_order; // 編碼順序// 構造 GOP:顯示順序 I B B P(假設gop_size是4或其倍數)for (int gop_index = 0; gop_index < gop_size / 4; ++gop_index) {int base = gop_index * 4;display_order.push_back({base + 0, -1, 0, 0, I_FRAME});display_order.push_back({base + 1, -1, 0, 0, B_FRAME});display_order.push_back({base + 2, -1, 0, 0, B_FRAME});display_order.push_back({base + 3, -1, 0, 0, P_FRAME});}// 編碼順序為 I P B Bfor (int gop_index = 0; gop_index < gop_size / 4; ++gop_index) {int base = gop_index * 4;encode_order.push_back({base + 0, -1, 0, 0, I_FRAME});encode_order.push_back({base + 3, -1, 0, 0, P_FRAME});encode_order.push_back({base + 1, -1, 0, 0, B_FRAME});encode_order.push_back({base + 2, -1, 0, 0, B_FRAME});}// 構建最終幀列表,賦值PTS(按顯示順序)和 DTS(按編碼順序)std::map<int, Frame> frame_map;for (size_t i = 0; i < display_order.size(); ++i) {int display_index = display_order[i].display_index;frame_map[display_index].display_index = display_index;frame_map[display_index].pts = i * frame_interval;frame_map[display_index].type = display_order[i].type;}for (size_t i = 0; i < encode_order.size(); ++i) {int display_index = encode_order[i].display_index;frame_map[display_index].encode_index = i;frame_map[display_index].dts = i * frame_interval;}// 按編碼順序輸出結果std::vector<Frame> result;for (const auto& [_, frame] : frame_map) {result.push_back(frame);}// 根據 DTS 排序std::sort(result.begin(), result.end(), [](const Frame& a, const Frame& b) {return a.dts < b.dts;});return result;
}std::string frame_type_str(FrameType type) {switch (type) {case I_FRAME: return "I";case P_FRAME: return "P";case B_FRAME: return "B";default: return "?";}
}int main() {int gop_size = 8; // 2組 IBBPint fps = 25;int time_base = 90000;int frame_interval = time_base / fps;std::vector<Frame> frames = generate_gop_with_bframes(gop_size, frame_interval);std::cout << "Idx\tType\tPTS\t\tDTS\n";for (const auto& f : frames) {std::cout << f.display_index << "\t" << frame_type_str(f.type)<< "\t" << f.pts << "\t" << f.dts << "\n";}return 0;
}
結果輸出:
Idx Type PTS DTS
0 I 0 0
3 P 10800 3600
1 B 3600 7200
2 B 7200 10800
4 I 14400 14400
7 P 25200 18000
5 B 18000 21600
6 B 21600 25200
時間基
概念
時間基是一個分數,表示時間戳的單位,即:
1 time_base = 1 / 秒數
通俗講:
如果時間基是
{1, 25}
,則時間戳單位為 1/25 秒,每遞增 1,表示時間過去了 1/25 秒(即一幀)。
使用場景
場景 | 用途說明 |
---|---|
編碼器 time_base | 控制 frame->pts 的單位 |
AVStream time_base | 控制 packet->pts/dts 在封裝文件中的時間戳單位 |
音視頻同步 | 不同 stream 間通過統一時間基換算進行對齊 |
時間戳轉換 | 不同模塊交互時需要 av_rescale_q() 進行換算 |
常見時間基含義
時間基 {num, den} | 表示含義 | 常見用途 |
---|---|---|
{1, 25} | 每單位 = 1/25 秒 | 25fps 視頻 |
{1, 1000} | 每單位 = 1ms | MP4/FLV 封裝層常用 |
{1, 90000} | 每單位 = 1/90000 秒 | MPEG-TS、RTSP 常用 |
{1, 48000} | 每單位 = 一個采樣周期 | 48kHz 音頻 |
{1, AV_TIME_BASE} | 每單位 = 微秒(1/1000000s) | FFmpeg 通用時間單位 |
設置建議
視頻編碼器(AVCodecContext)
codec_ctx->time_base = (AVRational){1, fps};
場景 | 示例 |
---|---|
25fps 視頻 | {1, 25} |
30fps 視頻 | {1, 30} |
60fps 視頻 | {1, 60} |
表示每幀間隔 1/25、1/30 秒,
AVFrame->pts
從 0 開始每幀加 1。
音頻編碼器
codec_ctx->time_base = (AVRational){1, sample_rate};
場景 | 示例 |
---|---|
48kHz 音頻 | {1, 48000} |
44.1kHz 音頻 | {1, 44100} |
表示每個采樣點對應 1/48000 秒,
AVFrame->pts
表示采樣點偏移量。
封裝層 AVStream(非常重要!)
stream->time_base = (AVRational){1, 1000}; // 推薦毫秒單位
// 或者 MPEG TS 常用:
stream->time_base = (AVRational){1, 90000};
容器格式 | 建議 time_base |
---|---|
MP4、FLV | {1, 1000} (毫秒) |
MPEG-TS | {1, 90000} |
MKV | {1, 1000} 或自動適配 |
所有
AVPacket->pts/dts
必須用av_rescale_q()
將幀時間戳從 codec_ctx 的 time_base 轉換為 stream 的 time_base。
時間戳換算方式(必做)
pkt.pts = av_rescale_q(frame->pts, codec_ctx->time_base, stream->time_base);
pkt.dts = pkt.pts;
如果有 B 幀,還需考慮 DTS 與 PTS 的排序差異。
音視頻同步建議
音頻和視頻流的時間戳必須使用統一時間軸對齊,即都轉為同一 stream time_base 參與比較:
video_pts_in_ms = av_rescale_q(video_pkt.pts, video_stream->time_base, {1, 1000});
audio_pts_in_ms = av_rescale_q(audio_pkt.pts, audio_stream->time_base, {1, 1000});
設置錯誤的后果
錯誤類型 | 可能后果 |
---|---|
time_base 設置過小或過大 | 時間戳溢出、精度不足、播放不同步 |
編解碼器與封裝器 time_base 不匹配 | 封裝時間戳錯誤、seek異常、播放異常 |
不進行時間基轉換 | 時間戳錯亂,播放器無法正確識別幀時間 |
不同 stream 使用不統一單位 | 音視頻同步失敗,seek 錯位 |
總結
模塊 | 推薦時間基 | 備注 |
---|---|---|
視頻編碼器 | {1, fps} | 如 25fps = {1, 25} |
音頻編碼器 | {1, sample_rate} | 如 48kHz = {1, 48000} |
視頻流封裝 | {1, 1000} | 單位為毫秒,通用適配性好 |
音頻流封裝 | {1, 1000} | 同上 |
MPEG TS 封裝 | {1, 90000} | 對應 MPEG 時間基 |
時間戳轉換函數 | av_rescale_q() | 必須用于編碼→封裝間的換算 |
影響
控制解碼順序(DTS)
描述:
- DTS 決定幀在解碼器中何時被解碼,尤其在存在 B 幀(雙向預測幀)時。
影響:
- 解碼順序錯誤將導致解碼失敗或花屏,因為 B 幀依賴前后幀數據,必須在參考幀解碼后才能處理。
- 沒有正確處理 DTS 會導致視頻數據丟幀或無法還原圖像。
控制播放順序(PTS)
描述:
- PTS 決定幀在播放器中何時顯示。
影響:
- 如果 PTS 錯亂,視頻會播放順序錯亂、跳幀、畫面抖動。
- 音視頻同步將失敗,導致“嘴型不對”,“聲畫不同步”。
音視頻同步(PTS)
描述:
- 音頻和視頻通過 PTS 對齊,實現聲畫同步。
影響:
- 若視頻幀 PTS 落后于音頻幀,會導致“聲音先到畫面后到”;
- 若 PTS 提前或延遲跳變,導致“卡頓”、“快進”或“時間線錯亂”。
播放器緩沖管理(PTS + DTS)
描述:
播放器使用 PTS 判斷是否需要緩沖、跳幀或提前渲染;用 DTS 提前解碼數據填充緩沖區。
影響:
- DTS 錯誤可能導致解碼器提前耗盡幀,導致播放中斷。
- PTS 不連續或跳變會導致播放卡頓、緩沖策略錯誤。
B幀重排序處理(PTS ≠ DTS)
描述:
編碼器會將幀重排序以壓縮效率最大化(I-P-B結構),導致 PTS ≠ DTS。
影響:
- 解碼器必須根據 DTS 順序輸入數據,但播放器必須按 PTS 順序顯示。
- 如播放器或解碼器未處理重排序,會導致播放異常,如花屏、跳幀。
封裝格式要求(TS、MP4、FLV等)
描述:
不同封裝格式對 PTS 和 DTS 有不同要求:
- MPEG-TS 通常包含 PTS 和 DTS;
- MP4 要求明確記錄幀的 PTS 和 DTS;
- FLV 僅含 PTS,要求解碼器自己推算 DTS。
影響:
- 若格式需要 DTS 而未提供,播放器解碼會出錯;
- 在流媒體中時間戳錯誤影響服務端/客戶端播放穩定性。
播放器時間軸與播放速率控制(PTS)
描述:
播放器以 PTS 為時間基準,控制渲染速率(幀率)、快進/慢放等行為。
影響:
- 時間戳不單調遞增將破壞時間軸,導致跳幀、時間錯亂;
- 快進時無法準確跳轉至目標 PTS 會導致 seek 錯位。
硬件解碼器行為(DTS)
描述:
一些硬件解碼器(如 GPU 解碼)依賴 DTS 正確排序,確保流水線處理。
影響:
- 錯誤 DTS 會導致解碼器 crash、丟幀、處理錯誤。
轉碼與重新封裝流程依賴 PTS/DTS
描述:
轉碼器/復用器如 FFmpeg 需要依靠 PTS/DTS 判斷幀間順序與時長。
影響:
- 時間戳錯誤會導致輸出文件亂序、播放錯亂或封裝失敗。
時間基轉換和流同步影響(PTS)
描述:
不同編碼器/封裝器的 time_base 不同,PTS/DTS 需要合理縮放轉換。
影響:
- 轉換錯誤會導致輸出幀率不對、時間戳跳變或超大。
對比
影響點 | PTS(播放) | DTS(解碼) |
---|---|---|
控制顯示順序 | 主要依據 | 不相關 |
控制解碼順序 | 不相關 | 主要依據 |
B幀重排序 | 決定實際顯示順序 | 決定輸入解碼順序 |
音視頻同步 | 用于聲畫對齊 | 無直接作用 |
播放器緩沖控制 | 決定渲染點 | 提前解碼填充緩沖區 |
seek/快進精準定位 | 關鍵依據 | 不直接參與 |
格式封裝要求 | 必需項 | 某些格式必須 |
硬件解碼器正確性 | 依賴 DTS 重排序結果 | 必須嚴格準確 |
轉碼/封裝時間戳計算 | 控制封裝播放順序 | 控制幀順序完整性 |