1. 程式人生 > >讓WebRTC支援H264編解碼

讓WebRTC支援H264編解碼

https://blog.csdn.net/foruok/article/details/69525039

最近實驗了下如何讓WebRTC支援H264編碼,記錄下,供有需要的人蔘考。

說明一下,我是在 Ubuntu Server 14.04 下編譯的 WebRTC ,使用 native(C++) api 開發 WebRTC 應用。所以我的調整都是基於 native 程式碼。

最終的效果是瀏覽器可以用H264傳送視訊,也可以接收H264視訊。

注意,WebRTC 使用 OpenH264 來做 encoder (見 h264_encoder_impl.cc),使用 ffmpeg 來做 decoder (見 h264_decoder_impl.cc )。
程式碼版本

本文對應的程式碼是2017年2月8號的,可以使用 gclient revinfo -a來檢視具體版本,如下:

編譯選項調整

WebRTC可以支援H264,但在Linux下編譯時,預設沒有開啟。

rtc_use_h264,這個開關控制了是否使用 H264 (對應C++程式碼中的巨集 WEBRTC_USE_H264),在 webrtc/webrtc.gni 檔案中定義:

rtc_use_h264 = proprietary_codecs && !is_android && !is_ios

    1

proprietary_codecs 在 build/config/features.gni 中定義:

proprietary_codecs = is_chrome_branded || is_chromecast

    1

我在 Linux 下編譯,branded 預設是 Chromium ,所以,proprietary_codecs 預設就是 false 。

想來想去,只好通過 gn gen 時傳入 args 來調整比較方便,使用下面的命令來生成 ninja 構建檔案:

gn gen out/h264Debug --args="proprietary_codecs=true"

    1

執行完畢後,可以使用下列命令驗證一下:

gn args out/h264Debug --list=proprietary_codecs
gn args out/h264Debug --list=rtc_use_h264

    1
    2

看到 Current Value 為 true,就說明這個選項已經生效了。

開啟 rtc_use_h264 ,OpenH264 的編碼支援就使能了。

WebRTC內部會使用 ffmpeg 來解碼 H264 (見 h264_decoder_impl.cc ),與 ffmpeg 相關的還有一個選項——rtc_initialize_ffmpeg,這個也得為 true ,否則 ffmpeg 的 avcodec 不會初始化,用不成。

rtc_initialize_ffmpeg 定義在 webrtc/webrtc.gni 中定義:

rtc_initialize_ffmpeg = !build_with_crhome

    1

因為我們為 native 開發而編譯,build_with_chrome 預設為 false ,所以 rtc_initialize_ffmpeg 預設為 true ,不用調整。

rtc_initialize_ffmpeg 開關對應一個 C++ 程式碼中的巨集 WEBRTC_INITIALIZE_FFMPEG 。

要使用 ffmpeg 的 h264 decoder 功能,還需要修改一個巨集: FFMPEG_H264_DECODER。在 config.h 檔案中,路徑是 third_party/chromium/config/chromium/linux/x64。原來定義如下:

#define CONFIG_H264_DECODER 0

    1

修改為 1 即可。這樣 avcodec_register_all() 方法才會把 H264 decoder 註冊到系統中。

等下,實際上還有一部分非常重要的工作要做。因為 linux 下編譯 WebRtc ,預設生成的 ninja 構建檔案中,沒有 ffmpeg 的 h264 decoder 對應的原始碼,所以即便你開啟 FFMPEG_H264_DECODER 也不管用,必須得修改 third_party/ffmpeg/ffmpeg_generated.gni 檔案,找到包含 h264的那些條件,開啟即可。

    注:因為我一開始編譯時沒有開啟 H264 支援,所以在修改了ffmpeg_generated.gni 檔案後,使用 gn gen 生成 ninja 構建檔案時,指定了一個新的目錄,然後把 ffmpeg 相關的 ninja 檔案(三個),拷貝到了原來的構建目錄中,然後使用 ninja ffmpeg 命令來編譯出 so 檔案。

codec 的順序調整

網頁使用 WebRTC 傳送 SDP ,進行協商時,預設的 codec 順序是:

    VP8
    VP9
    H264

在 C++ 程式碼裡,會預設選擇第一個來匹配(從PeerConnection::CreateAnswer/SetRemoteDescription兩個方法跟進去,可以看到)。所以,我們要修改 C++ 程式碼,來改變這個選擇邏輯。

WebRtcVideoChannel2(webrtcvideoengine2.cc)使用的 codec ,來自 InternalEncoderFactory類(internalencoderfactory.cc),不管是作為傳送端還是接收端,編碼格式都來自這裡。

在InternalEncoderFactory的建構函式裡,可以調整 codec 的順序,預設程式碼如下:

supported_codecs_.push_back(cricket::VideoCodec(kVp8CodecName));
if (webrtc::VP9Encoder::IsSupported())
  supported_codecs_.push_back(cricket::VideoCodec(kVp9CodecName));
if (webrtc::H264Encoder::IsSupported()) {
  cricket::VideoCodec codec(kH264CodecName);
  // TODO(magjed): Move setting these parameters into webrtc::H264Encoder
  // instead.
  codec.SetParam(kH264FmtpProfileLevelId,
                 kH264ProfileLevelConstrainedBaseline);
  codec.SetParam(kH264FmtpLevelAsymmetryAllowed, "1");
  supported_codecs_.push_back(std::move(codec));
}

supported_codecs_.push_back(cricket::VideoCodec(kRedCodecName));
supported_codecs_.push_back(cricket::VideoCodec(kUlpfecCodecName));
....

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

只要把 H264 那個 codec 調整到前面即可。

做了這個調整,Native app 作為傳送視訊的一端,在 SDP 協商時, H264 的支援就會放在前面,另外一端如果支援 H264 解碼,就會優先選擇 H264 格式,兩邊就能以 H264 來互動視訊流了。

瀏覽器作為傳送視訊的一端時,它發過來的視訊格式順序是 VP8、VP9、H264,Native C++程式碼中會根據這個順序來調整本地的 codec 的順序,程式碼在 mediasession.cc 中:

template <class C>
static void NegotiateCodecs(const std::vector<C>& local_codecs,
                            const std::vector<C>& offered_codecs,
                            std::vector<C>* negotiated_codecs) {
  for (const C& ours : local_codecs) {
    C theirs;
    // Note that we intentionally only find one matching codec for each of our
    // local codecs, in case the remote offer contains duplicate codecs.
    if (FindMatchingCodec(local_codecs, offered_codecs, ours, &theirs)) {
      C negotiated = ours;
      negotiated.IntersectFeedbackParams(theirs);
      if (IsRtxCodec(negotiated)) {
        const auto apt_it =
            theirs.params.find(kCodecParamAssociatedPayloadType);
        // FindMatchingCodec shouldn't return something with no apt value.
        RTC_DCHECK(apt_it != theirs.params.end());
        negotiated.SetParam(kCodecParamAssociatedPayloadType, apt_it->second);
      }
      if (CodecNamesEq(ours.name.c_str(), kH264CodecName)) {
        webrtc::H264::GenerateProfileLevelIdForAnswer(
            ours.params, theirs.params, &negotiated.params);
      }
      negotiated.id = theirs.id;
      negotiated.name = theirs.name;
      negotiated_codecs->push_back(std::move(negotiated));
    }
  }
  // RFC3264: Although the answerer MAY list the formats in their desired
  // order of preference, it is RECOMMENDED that unless there is a
  // specific reason, the answerer list formats in the same relative order
  // they were present in the offer.
  std::unordered_map<int, int> payload_type_preferences;
  int preference = static_cast<int>(offered_codecs.size() + 1);
  for (const C& codec : offered_codecs) {
    payload_type_preferences[codec.id] = preference--;
  }
  std::sort(negotiated_codecs->begin(), negotiated_codecs->end(),
            [&payload_type_preferences](const C& a, const C& b) {
              return payload_type_preferences[a.id] >
                     payload_type_preferences[b.id];
            });
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42

最後那個 sort 呼叫,根據傳送端的 codec 順序重新調整了我們支援的解碼格式的順序。所以,我們在這裡也需要修改一下,把排序的部分去掉,或者針對 H264 去掉。
重新編譯

使用下列命令,可以編譯特定模組:

ninja pc (針對 mediasession.cc )
ninja media (針對 internalencoderfactory.cc 和 webrtcvideoengine2.cc )
ninja ffmpeg (針對 ffmpeg )

    1
    2
    3

然後再編譯你自己的 native app 。
---------------------
作者:foruok
來源:CSDN
原文:https://blog.csdn.net/foruok/article/details/69525039
版權宣告:本文為博主原創文章,轉載請附上博文連結!