在使用sdk-c viewer端進行拉流的過程中,viewer端拉取的是視頻幀和音頻幀,不會在播放器中播放,所以要根據收到的流來判斷拉流過程是否穩定流暢。
我這邊采用的算法是:依據相鄰幀之間的時間間隔是否落在期望值的 ±20% 范圍內。
音頻幀、視頻幀的日志打印如下:
07:19:26.263 VERBOSE sampleAudioFrameHandler(): Audio Frame received. TrackId: 140092278368896, Size: 160, Flags 3210729368
2025-06-12 07:19:26.283 VERBOSE sampleAudioFrameHandler(): Audio Frame received. TrackId: 140092278368896, Size: 160, Flags 3210729368
2025-06-12 07:19:26.298 VERBOSE sampleVideoFrameHandler(): Video Frame received. TrackId: 140092278368896, Size: 4458, Flags 3210729368
2025-06-12 07:19:26.303 VERBOSE sampleAudioFrameHandler(): Audio Frame received. TrackId: 140092278368896, Size: 160, Flags 3210729368
2025-06-12 07:19:26.323 VERBOSE sampleAudioFrameHandler(): Audio Frame received. TrackId: 140092278368896, Size: 160, Flags 3210729368
2025-06-12 07:19:26.338 VERBOSE sampleVideoFrameHandler(): Video Frame received. TrackId: 140092278368896, Size: 263, Flags 3210729368
2025-06-12 07:19:26.343 VERBOSE sampleAudioFrameHandler(): Audio Frame received. TrackId: 140092278368896, Size: 160, Flags 3210729368
2025-06-12 07:19:26.363 VERBOSE sampleAudioFrameHandler(): Audio Frame received. TrackId: 140092278368896, Size: 160, Flags 3210729368
假設期望幀間隔是 20ms,那接收的幀間隔必須在lower和upper之間:
- lower = 16ms (-20%)
- upper = 24ms (+20%)
之后遍歷相鄰的時間戳,計算差值即時間間隔:
- 如果任何一個間隔超出 [16, 24]ms 范圍,就返回 1(表示不穩定)
- 如果所有間隔都在范圍內,最后返回 0(表示穩定)
這里有一點需要注意,如果時間間隔大于最大值很好理解,肯定是卡頓了,那么小于最小值為什么也被認定為不穩定呢,時間差越短不是越流暢嗎?這里解釋一下:
比如視頻幀率是 25fps,對應每幀間隔約 40ms。如果某幾幀突然變成 20ms,就不是“按幀率穩定輸出”的表現。幀率不穩,會導致:
- 音視頻不同步
- 抖動(jitter)
- 丟幀或重復幀等問題
流媒體播放是“節奏感”不是“越快越好”
一個穩定的視頻流(比如 25fps)應該每 40ms 一幀。如果突然來一幀只間隔了 10ms,會導致播放器不知道該怎么同步音視頻,甚至可能觸發跳幀。過快或過慢,都是對穩定性的破壞。
時間間隔短,可能是網絡包聚合延遲之后一次性發送多幀的情況。
舉個例子:
假設有個節拍器本來每 1 秒“噠”一下,現在突然 0.1 秒“噠噠噠”3 次,那么它可能出故障了!
代碼實現
# 判斷幀時間是否穩定(±20%)
is_stable() {local expected_ms=$1shiftlocal -a times=("$@")local lower=$((expected_ms * 8 / 10))local upper=$((expected_ms * 12 / 10))for ((i = 1; i < ${#times[@]}; i++)); dolocal diff=$((times[i] - times[i-1]))if (( diff < lower || diff > upper )); thenreturn 1fidonereturn 0
}
第一個參數 $1:是期望的幀間隔(單位是毫秒),比如:
- 音頻幀:20 毫秒
- 視頻幀:40 毫秒
后續參數:是一系列時間戳(以毫秒為單位),代表每幀的接收時間。這些時間戳是在分析viewer端日志的過程中,獲取到的n幀視頻幀時間戳和n幀音頻幀時間戳。
后續參數跟這行代碼相關:
local -a times=("$@")
這行代碼是將函數傳入的參數中除了第一個參數外的所有參數,作為一個數組存入 times 變量中。因為前面 shift 過一次了,所以 $@ 就是“剩余的所有時間戳列表”。
例如:
is_stable 20 1000 1020 980 1010
進入函數后:
$1 = 20(期望間隔)
shift 移除 $1 后
$@ 就變成了:1000 1020 980 1010
times=(“$@”) 等價于:times=(1000 1020 980 1010)
如果去掉 -a,也不會出錯,但那樣會變成普通字符串變量,只存第一個參數值:
local times=("$@") # 結果是 times=1000
-a 是告訴 Bash:“我要定義一個數組變量”。
總結就是
部分 | 含義 |
---|---|
local | 聲明這是一個函數內的局部變量,不影響外部環境 |
-a | 表示這個變量是一個數組類型 |
times | 數組變量的名字 |
=(“$@”) | 把函數剩余的所有參數(即 $@)作為數組元素賦值給 times |
前一篇:https://blog.csdn.net/zhang_jiamin/article/details/149053832?spm=1011.2415.3001.5331