webrtc-SVC+simulcast改造

1.simulcast+SVC打开

std::string str[] = {"low", "medium", "high"};
    std::string msid[] = {"l", "m", "h"};
    //double pri = 0.8;
    //添加初始化参数,在此处设置时域层数,push 多少webrtc::RtpEncodingParameters就是多少层simulcast
    webrtc::RtpTransceiverInit rtpTI;
     for (int i = 3; i >= 1; i--) {
      webrtc::RtpEncodingParameters videoEncoding;
      videoEncoding.rid = str[i-1];
      //videoEncoding.max_bitrate_bps = 3 * i * 100 * 1000;
      //videoEncoding.bitrate_priority = pri;
      videoEncoding.num_temporal_layers = 3;
      rtpTI.send_encodings.push_back(videoEncoding);
      //pri -= 0.2;
      rtpTI.stream_ids.push_back(msid[i-1]); //这个是SDP中msid参数的名字
    }
    //单层simulcast的时候设置时域层数
    //rtpTI.stream_ids.push_back("cam");
    //webrtc::RtpEncodingParameters videoEncoding;
    //videoEncoding.rid = str[2];
    //videoEncoding.num_temporal_layers = 3;
    //rtpTI.send_encodings.push_back(videoEncoding);

    auto ret = peer_connection_->AddTransceiver(video_track_,rtpTI);
    //这个可以获取当前设置的参数
    webrtc::RtpParameters para =
        peer_connection_->GetSenders()[1]->GetParameters();

2.设置编码优先顺序(编码选择)

src/media/engine/internal_encoder_factory.cc

std::vector<SdpVideoFormat> InternalEncoderFactory::GetSupportedFormats()
    const {
  std::vector<SdpVideoFormat> supported_codecs;
  //for (const webrtc::SdpVideoFormat& format : webrtc::SupportedH264Codecs())
  //  supported_codecs.push_back(format);
  // for (const webrtc::SdpVideoFormat& format : webrtc::SupportedVP9Codecs())
  //   supported_codecs.push_back(format);
  supported_codecs.push_back(SdpVideoFormat(cricket::kVp8CodecName));
  return supported_codecs;
}

3.设置H264可通过AddTransceiver参数初始化支持SVC

增加对H264的支持:
src/media/engine/webrtc_video_engine.cc

bool IsTemporalLayersSupported(const std::string& codec_name) {
  return absl::EqualsIgnoreCase(codec_name, kVp8CodecName) ||
         absl::EqualsIgnoreCase(codec_name, kVp9CodecName) ||
         absl::EqualsIgnoreCase(codec_name, kH264CodecName);
}

4.关于simulcast和svc的常量参数设置

src/api/video/video_codec_constans.h

enum : int { kMaxEncoderBuffers = 8 };
enum : int { kMaxSimulcastStreams = 3 };
enum : int { kMaxSpatialLayers = 5 };
enum : int { kMaxTemporalStreams = 4 };

5.simulcast

media_session.cc
    
    static bool AddStreamParams(){
    ...
      //这是对应的流的信息 
      StreamParams stream_param =
              sender.rids.empty()
                  ?
                  // Signal SSRCs and legacy simulcast (if requested).
                  //老版本planb. rids为空,使用num_simulcast_layer来创建,内部调用GenerateSsrcs                   
                  CreateStreamParamsForNewSenderWithSsrcs(
                      sender, rtcp_cname, include_rtx_streams,
                      include_flexfec_stream, ssrc_generator)
                  :
                  // Signal RIDs and spec-compliant simulcast (if requested).
                  CreateStreamParamsForNewSenderWithRids(sender, rtcp_cname);
    
    ...
    }

AddTransceiver只能在webrtc::SdpSemantics::kUnifiedPlan模式下,这个在CreatePeerConnection时设置进去,目前设置完simulcast参数后,数据未推上去,应该是服务端暂未支持。

webrtc::SdpSemantics::kPlanB模式下对应的是AddTrack,但是设置simulcast层数是在CreateOffer设置进去,webrtc::PeerConnectionInterface::RTCOfferAnswerOptions.num_simulcast_layers,但是此时根据抓包和断点看到的是只有2层,RTCOfferAnswerOptions里面默认是两层。
注:webrtc会根据当前视频的分辨率,以及预设的常量来决定实际的simulcast层数,例如640x480-位于(960,540)和(640,320),所以参数是设置为(640,320),最多2层simulcast

void Conductor::ConnectToPeer(int peer_id) {
  //RTC_DCHECK(peer_id_ == -1);
  //RTC_DCHECK(peer_id != -1);

  if (peer_connection_.get()) {
    main_wnd_->MessageBox(
        "Error", "We only support connecting to one peer at a time", true);
    return;
  }

  if (InitializePeerConnection()) {
    webrtc::PeerConnectionInterface::RTCOfferAnswerOptions options =
        webrtc::PeerConnectionInterface::RTCOfferAnswerOptions();
    options.num_simulcast_layers = 3;
    peer_id_ = peer_id;
    peer_connection_->CreateOffer(
        this, options);
  } else {
    main_wnd_->MessageBox("Error", "Failed to initialize PeerConnection", true);
  }
}

src/media/engine/simulcast.cc

// These tables describe from which resolution we can use how many
// simulcast layers at what bitrates (maximum, target, and minimum).
// Important!! Keep this table from high resolution to low resolution.
// clang-format off
const SimulcastFormat kSimulcastFormats[] = {
  {1920, 1080, 3, 5000, 4000, 800},
  {1280, 720, 3, 2500, 2500, 600},
  {960, 540, 3, 1200, 1200, 350},
  {640, 360, 2, 700, 500, 150},
  {480, 270, 2, 450, 350, 150},
  {320, 180, 1, 200, 150, 30},
  {0, 0, 1, 200, 150, 30}
};

FindSimulcastFormatIndex:
{
  ...
  for (uint32_t i = 0; i < arraysize(kSimulcastFormats); ++i) {
    if (width * height >=
        kSimulcastFormats[i].width * kSimulcastFormats[i].height) {
      return i;
    }
  }
  ...
}
ReconfigureEncoder
          ↓
CreateEncoderStreams
          ↓
GetSimulcastConfig
          ↓
LimitSimulcastLayerCount
          ↓
FindSimulcastFormatIndex

6.simulcast码率设置

编码相关的设置在src/video/video_stream_encoder.cc-ReconfigureEncoder

ReconfigureEncoder
          ↓
CreateEncoderStreams
          ↓
GetSimulcastConfig
          ↓
GetNormalSimulcastLayers 设置宽高码率,同时会设置默认时域层
    DefaultNumberOfTemporalLayers
    FindSimulcastMaxBitrateBps
    FindSimulcastTargetBitrateBps
    FindSimulcastMinBitrateBps
    kDefaultVideoMaxFramerate = 60

计算simulcast码率的时候使用双线性插值:

const int total_pixels_up =
      kSimulcastFormats[index - 1].width * kSimulcastFormats[index - 1].height;
  const int total_pixels_down =
      kSimulcastFormats[index].width * kSimulcastFormats[index].height;
  const int total_pixels = width * height;
  const float rate = (total_pixels_up - total_pixels) /
                     static_cast<float>(total_pixels_up - total_pixels_down);
  SimulcastFormat res;
  res.width = width;
  res.height = height;
  res.max_layers = kSimulcastFormats[index].max_layers;
  res.max_bitrate_kbps =
      kSimulcastFormats[index - 1].max_bitrate_kbps * (1.0 - rate) +
      kSimulcastFormats[index].max_bitrate_kbps * rate;

在设置0层simulcast时,如果打开了kUseBaseHeavyVP8TL3RateAllocationFieldTrial("WebRTC-UseBaseHeavyVP8TL3RateAllocation"),最大码率和目标码率将会乘以系数以适应0时域层

// If alternative temporal rate allocation is selected, adjust the
      // bitrate of the lowest simulcast stream so that absolute bitrate for
      // the base temporal layer matches the bitrate for the base temporal
      // layer with the default 3 simulcast streams. Otherwise we risk a
      // higher threshold for receiving a feed at all.
      
    if (num_temporal_layers == 3) {
        if (webrtc::field_trial::IsEnabled(
                kUseBaseHeavyVP8TL3RateAllocationFieldTrial)) {
          // Base heavy allocation increases TL0 bitrate from 40% to 60%.
          rate_factor = 0.4 / 0.6;
        }
      } else {
        rate_factor =
            webrtc::SimulcastRateAllocator::GetTemporalRateAllocation(3, 0) /
            webrtc::SimulcastRateAllocator::GetTemporalRateAllocation(
                num_temporal_layers, 0);
      }
static const float
    kLayerRateAllocation[kMaxTemporalStreams][kMaxTemporalStreams] = {
        {1.0f, 1.0f, 1.0f, 1.0f},  // 1 layer
        {0.6f, 1.0f, 1.0f, 1.0f},  // 2 layers {60%, 40%}
        {0.4f, 0.6f, 1.0f, 1.0f},  // 3 layers {40%, 20%, 40%}
        {0.25f, 0.4f, 0.6f, 1.0f}  // 4 layers {25%, 15%, 20%, 40%}
};

static const float kBaseHeavy3TlRateAllocation[kMaxTemporalStreams] = {
    0.6f, 0.8f, 1.0f, 1.0f  // 3 layers {60%, 20%, 20%}
};

7.时域层码率设置

ReconfigureEncoder
          ↓
EncoderSimulcastProxy::InitEncode
          ↓
LibvpxVp8Encoder::InitEncode
          ↓
SimulcastRateAllocator::Allocate
          ↓
SimulcastRateAllocator::GetTemporalRateAllocation 获取对应simulcast层数的时域层比率乘以目标码率

时域层的所有码率总和是当前simulcast层的目标码率

std::vector<uint32_t> SimulcastRateAllocator::DefaultTemporalLayerAllocation(
    int bitrate_kbps,
    int max_bitrate_kbps,
    int simulcast_id) const {
  const size_t num_temporal_layers = NumTemporalStreams(simulcast_id);
  std::vector<uint32_t> bitrates;
  for (size_t i = 0; i < num_temporal_layers; ++i) {
    float layer_bitrate =
        bitrate_kbps * GetTemporalRateAllocation(num_temporal_layers, i);
    bitrates.push_back(static_cast<uint32_t>(layer_bitrate + 0.5));
  }

  // Allocation table is of aggregates, transform to individual rates.
  uint32_t sum = 0;
  for (size_t i = 0; i < num_temporal_layers; ++i) {
    uint32_t layer_bitrate = bitrates[i];
    RTC_DCHECK_LE(sum, bitrates[i]);
    bitrates[i] -= sum;
    sum = layer_bitrate;

    if (sum >= static_cast<uint32_t>(bitrate_kbps)) {
      // Sum adds up; any subsequent layers will be 0.
      bitrates.resize(i + 1);
      break;
    }
  }

  return bitrates;
}