WebRTC H264 拉流渲染灰屏問題總結(jié)(一)

1. 前言

前段時間在處理公司屏幕共享功能的時候遇到一個問題, 視頻拉流渲染的時候偶爾會出現(xiàn)灰屏, 下面是個例子.

1-1

出現(xiàn)問題是有偶現(xiàn)的, 隨機的, 但頻率并不低, 嚴(yán)重的影響了觀看的體驗.

針對灰屏問題進行了一些調(diào)研, 最終解決了這個問題(目前沒有復(fù)現(xiàn)), 通過解決這個問題還是發(fā)現(xiàn)了很多知識盲區(qū)和沒掌握的細節(jié)問題, 特此做一個總結(jié).

2. 概念同步

2.1 H264

2.1.1 SPS

Sequence Paramater Set - 序列參數(shù)集
SPS 中保存了一組編碼視頻序列 (Coded Video Sequence)的全局參數(shù), 因此該類型保存的是和編碼序列相關(guān)的參數(shù)

2.1.2 PPS

Picture Paramater Set - 圖像參數(shù)集
該類型保存了整體圖像相關(guān)的參數(shù)

2.1.3 IDR

Instantaneous Decoding Refresh - 即時解碼刷新
IDR幀實質(zhì)也是I幀, 使用幀內(nèi)預(yù)測. IDR幀的作用是立即刷新缰犁,會導(dǎo)致 DPB(Decoded Picture Buffer參考幀列表) 清空枷遂,而 I幀不會. 所以 IDR幀承擔(dān)了隨機訪問功能, 一個新的 IDR幀開始, 可以重新算一個新的 GOP 開始編碼, 播放器永遠可以從一個 IDR幀播放, 因為在它之后沒有任何幀引用之前的幀.

如果一個視頻中沒有 IDR幀, 這個視頻是不能隨機訪問的. 所有位于 IDR幀后的 B幀和 P幀都不能參考 IDR幀以前的幀, 而普通 I幀后的 B幀和 P幀仍然可以參考 I幀之前的其他幀. IDR幀阻斷了誤差的積累,而 I幀并沒有阻斷誤差的積累.

2.1.3 GOP

Group of picture - 圖像組, 通常指兩個 I幀之間的幀數(shù)
一個 GOP 序列的第一個圖像叫做 IDR 圖像(立即刷新圖像, IDR 圖像都是 I 幀圖像劈榨,但 I幀不一定都是 IDR幀

2.2 WebRTC 拉流邏輯

WebRTC 接收到媒體數(shù)據(jù)的 udp 包后, 會經(jīng)過 packet_buffer, 這里負責(zé)組幀成完整幀的邏輯判斷, 只有完整幀才會繼續(xù)走下面的解碼渲染邏輯.

3. 發(fā)現(xiàn)問題

3.1 問題分析

當(dāng)看到渲染出現(xiàn)灰屏的時候首先懷疑是不是推流的問題, 但推流通常會因為碼率過低而導(dǎo)致圖片編碼質(zhì)量很低導(dǎo)致的模糊, 基本不會出現(xiàn)這種還有局部很清晰的情況, 所以從拉流一端繼續(xù)排查.

拉流端可能出現(xiàn)這類問題無非兩種問題: 數(shù)據(jù)錯誤, 數(shù)據(jù)丟失.

  • 數(shù)據(jù)錯誤
    當(dāng)出現(xiàn)錯誤的數(shù)據(jù)大概率是因為程序 bug, 導(dǎo)致交給解碼器的數(shù)據(jù)并不正確, 但通常這這會出現(xiàn)大面積色塊的問題, 并不會出現(xiàn)類似灰屏這種問題.

  • 數(shù)據(jù)丟失
    組幀邏輯如果有 bug 會導(dǎo)致不完整的幀交給解碼器, 出現(xiàn)異常情況.
    WebRTC 的組幀判斷的邏輯還是比較健壯的, 應(yīng)該不會出現(xiàn)丟部分?jǐn)?shù)據(jù)的問題.

繼續(xù)觀察顯現(xiàn), 出現(xiàn)的灰屏的時長基本符合我們設(shè)置的 GOP 時長, 那么問題大概率出現(xiàn)在關(guān)鍵幀刷新的地方 ( 結(jié)合拉流邏輯里對 H264 判定 IDR 幀 ).

為了更好的控制碼率, 我在 WebRTC 里集成了 x264 編碼器, 和默認的 openh264 有很多參數(shù)配置還是有區(qū)別的, 然后對比了一下兩個編碼器關(guān)于關(guān)鍵幀的一些設(shè)置發(fā)現(xiàn)了一些問題, 下面具體針對問題展開.

3.2 對比編碼器配置

3.2.1 OpenH264

typedef enum {
  CONSTANT_ID = 0,           ///< constant id in SPS/PPS
  INCREASING_ID = 0x01,      ///< SPS/PPS id increases at each IDR
  SPS_LISTING  = 0x02,       ///< using SPS in the existing list if possible
  SPS_LISTING_AND_PPS_INCREASING  = 0x03,
  SPS_PPS_LISTING  = 0x06,
} EParameterSetStrategy;
  // Reuse SPS id if possible. This helps to avoid reset of chromium HW decoder
  // on each key-frame.
  // Note that WebRTC resets encoder on resolution change which makes all
  // EParameterSetStrategy modes except INCREASING_ID (default) essentially
  // equivalent to CONSTANT_ID.
  encoder_params.eSpsPpsIdStrategy = SPS_LISTING;

OpenH264 的編碼器對于Sps/Pps 的設(shè)置比較豐富, 具體使用上是對 Sps/Pps 采用盡量重用的方式.

3.2.2 x264

int b_repeat_headers;       /* put SPS/PPS before each keyframe */
param.b_repeat_headers      = 1;

因為我們有可能在推流過程中改變分辨率, 所以采用的是每個關(guān)鍵幀都需要攜帶 Sps/Pps 才能完成解碼.

3.2.3 WebRTC 組幀邏輯

// modules/video_coding/packet_buffer.cc
...
// sps_pps_idr_is_h264_keyframe_ 開關(guān), 默認是 false.
// 當(dāng)缺失 Sps/Pps 的時候也有可能會被認為是 IDR幀. 
          if ((sps_pps_idr_is_h264_keyframe_ && has_h264_idr && has_h264_sps &&
               has_h264_pps) ||
              (!sps_pps_idr_is_h264_keyframe_ && has_h264_idr)) {
            is_h264_keyframe = true;
            // Store the resolution of key frame which is the packet with
            // smallest index and valid resolution; typically its IDR or SPS
            // packet; there may be packet preceeding this packet, IDR's
            // resolution will be applied to them.
            if (buffer_[start_index]->width() > 0 &&
                buffer_[start_index]->height() > 0) {
              idr_width = buffer_[start_index]->width();
              idr_height = buffer_[start_index]->height();
            }
          }
...

// 如果通過上面的邏輯判定不是關(guān)鍵幀才會判斷是否存在丟包情況
// 假如一個 IDR幀的 Sps/Pps 包發(fā)生丟包, 在這樣的邏輯下是有可能進行解碼
// 因為缺少 Sps/Pps 信息, 解碼器內(nèi)部會以普通的 I幀進行處理, 不會清空 DPB(Decoded Picture Buffer參考幀列表)
        // If this is not a keyframe, make sure there are no gaps in the packet
        // sequence numbers up until this point.
        if (!is_h264_keyframe && missing_packets_.upper_bound(start_seq_num) !=
                                     missing_packets_.begin()) {
          return found_frames;
        }

到這里可以大概率的懷疑是因為這個邏輯導(dǎo)致的灰屏. 主要原因:

  1. x264 的 Sps/Pps 邏輯和 OpenH264 不同
  2. 拉流渲染的時候關(guān)鍵幀的 Sps/Pps 包發(fā)生丟包或者亂序, 組幀的地方正好符合組幀邏輯, 進行了解碼.

下面針對這個猜測進行修改嘗試.

4. 嘗試解決

既然有了猜測, 那主要的修改就是在于如何打開這個開關(guān).

// video\/rtp_video_stream_receiver2.cc
...
  if (codec_params.count(cricket::kH264FmtpSpsPpsIdrInKeyframe) ||
      field_trial::IsEnabled("WebRTC-SpsPpsIdrIsH264Keyframe")) {
    packet_buffer_.ForceSpsPpsIdrIsH264Keyframe();
  }

可以看到這里有兩種打開方式:

  1. 通過 sdp 的的音頻 fmtp 增加 sps-pps-idr-in-keyframe
  2. 通過 WebRTC 的全局控制開關(guān)

為了能更好的兼容各種情況, 我們采用在 sdp 里攜帶動態(tài)控制開關(guān), 這樣可以針對不同的推流選擇性的開啟這個功能.

經(jīng)過線上的測試, 打開開關(guān)后確實沒有再發(fā)現(xiàn)有灰屏的問題, 說明這個控制是有效的.

5. TODO

雖然看上去是修改了這個問題, 但其實還是靠猜測和一些無法100% 可控的驗證手段, 有幾個方面還可以繼續(xù)展開調(diào)研, 可以放倒后面繼續(xù)做.

  • 通過自己模擬丟包或者亂序去復(fù)現(xiàn)問題
  • 解碼器針對丟失 Sps/Pps 后的處理邏輯
  • 編碼器的設(shè)置是否也可以規(guī)避這個問題
  • 編碼器的 Sps/Pps 的變化原理是什么
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市是尖,隨后出現(xiàn)的幾起案子懒鉴,更是在濱河造成了極大的恐慌,老刑警劉巖隶债,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異跑筝,居然都是意外死亡死讹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門曲梗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赞警,“玉大人,你說我怎么就攤上這事虏两±⒌” “怎么了?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵定罢,是天一觀的道長笤虫。 經(jīng)常有香客問我,道長祖凫,這世上最難降的妖魔是什么琼蚯? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮惠况,結(jié)果婚禮上遭庶,老公的妹妹穿的比我還像新娘。我一直安慰自己稠屠,他們只是感情好峦睡,可當(dāng)我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布翎苫。 她就那樣靜靜地躺著,像睡著了一般赐俗。 火紅的嫁衣襯著肌膚如雪拉队。 梳的紋絲不亂的頭發(fā)上弊知,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天阻逮,我揣著相機與錄音,去河邊找鬼秩彤。 笑死叔扼,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的漫雷。 我是一名探鬼主播瓜富,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼降盹!你這毒婦竟也來了与柑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蓄坏,失蹤者是張志新(化名)和其女友劉穎价捧,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涡戳,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡结蟋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了渔彰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嵌屎。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖恍涂,靈堂內(nèi)的尸體忽然破棺而出宝惰,到底是詐尸還是另有隱情,我是刑警寧澤再沧,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布尼夺,位于F島的核電站,受9級特大地震影響产园,放射性物質(zhì)發(fā)生泄漏汞斧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一什燕、第九天 我趴在偏房一處隱蔽的房頂上張望粘勒。 院中可真熱鬧,春花似錦屎即、人聲如沸庙睡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乘陪。三九已至统台,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間啡邑,已是汗流浹背贱勃。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谤逼,地道東北人贵扰。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像流部,于是被迫代替她去往敵國和親戚绕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,974評論 2 355

推薦閱讀更多精彩內(nèi)容