webrtc之Android视频质量提升:保帧率降分辨率

前言:

上篇文章《webrtc之Android视频质量提升:保帧率降码率》](https://www.jianshu.com/p/65470d7e0cb1)介绍了webrtc码率自适应中关于动态调节码率的策略。这章将介绍一下webrtc码率自适应中动态调节分辨率的策略。

流程:

首先在文件PeerConnectionClient.java的createMediaConstraintsInternal函数中执行了如下代码。设置了CPU_OVERUSE_DETECTION_CONSTRANIT 位ture。设置了这个参数为ture后webrtc会通过检测cpu进行分辨率或者是帧率的调节,同时会启动码率自适应动态调节分辨率策略。
CPU_OVERUSE_DETECTION_CONSTRANIT在文件开始处被定义位:
private static final String CPU_OVERUSE_DETECTION_CONSTRANIT = "googCpuOveruseDetection"; 关于MediaConstraints的参数定义参加文件mediaconstraintsinterface.cc。

 private void createMediaConstraintsInternal() {
     // Create peer connection constraints.
     pcConstraints = new MediaConstraints();
     pcConstraints.mandatory.add(
             new MediaConstraints.KeyValuePair(CPU_OVERUSE_DETECTION_CONSTRANIT, "true")
     );

同时在文件PeerConnectionClient.java的函数createPeerConnectionInternal中,创建peerConnection是传递进去。

private void createPeerConnectionInternal(EglBase.Context renderEGLContext) {
...
    peerConnection = factory.createPeerConnection(rtcConfig, pcConstraints, pcObserver);
...

通过一系列传递,这个参数最终在文件webrtcvideoengine.cc的RecreateWebRtcStream函数中的GetDegradationPreference()被引用。

void WebRtcVideoChannel::WebRtcVideoSendStream::RecreateWebRtcStream() {

 if (source_) {
   stream_->SetSource(this, GetDegradationPreference());
 }

进入文件webrtcvideoengine.cc的GetDegradationPreference函数,这个函数中引用到了enable_cpu_overuse_detection_这个变量,这个变量就是上面传递过来的"googCpuOveruseDetection"。

webrtc::VideoSendStream::DegradationPreference
WebRtcVideoChannel::WebRtcVideoSendStream::GetDegradationPreference() const {
  // Do not adapt resolution for screen content as this will likely
  // result in blurry and unreadable text.
  // |this| acts like a VideoSource to make sure SinkWants are handled on the
  // correct thread.
  DegradationPreference degradation_preference;
  if (!enable_cpu_overuse_detection_) {
    degradation_preference = DegradationPreference::kDegradationDisabled;
  } else {
    if (parameters_.options.is_screencast.value_or(false)) {
      degradation_preference = DegradationPreference::kMaintainResolution;
    } else if (webrtc::field_trial::IsEnabled(
                   "WebRTC-Video-BalancedDegradation")) {
      degradation_preference = DegradationPreference::kBalanced;
    } else {
      degradation_preference = DegradationPreference::kMaintainFramerate;
    }
  }
  return degradation_preference;
}

通过判断enable_cpu_overuse_detection_来设置不同的degradation_preference,degradation_preference的类型为:DegradationPreference,是一个enum类型,包含的选项有:

// Based on the spec in
// https://w3c.github.io/webrtc-pc/#idl-def-rtcdegradationpreference.
// These options are enforced on a best-effort basis. For instance, all of
// these options may suffer some frame drops in order to avoid queuing.
// TODO(sprang): Look into possibility of more strictly enforcing the
// maintain-framerate option.
enum class DegradationPreference {
  // Don't take any actions based on over-utilization signals.
  kDegradationDisabled,
  // On over-use, request lower frame rate, possibly causing frame drops.
  kMaintainResolution,
  // On over-use, request lower resolution, possibly causing down-scaling.
  kMaintainFramerate,
  // Try to strike a "pleasing" balance between frame rate or resolution.
  kBalanced,
};

可见kMaintainFramerate选项是保帧率降分辨率,而这里degradation_preference被赋值的就是 DegradationPreference::kMaintainFramerate。

->进入文件video_send_stream.cc的SetSource函数:

void VideoSendStream::SetSource(
    rtc::VideoSourceInterface<webrtc::VideoFrame>* source,
    const DegradationPreference& degradation_preference) {
  RTC_DCHECK_RUN_ON(&thread_checker_);
  video_stream_encoder_->SetSource(source, degradation_preference);
}

->进入video_stream_encoder.cc的SetSource函数中,在这里 degradation_preference_ 被赋值degradation_preference,degradation_preference的值为DegradationPreference::kMaintainFramerate,是一个保帧率降分辨率的策略。

void VideoStreamEncoder::SetSource(
    rtc::VideoSourceInterface<VideoFrame>* source,
    const VideoSendStream::DegradationPreference& degradation_preference) {
  RTC_DCHECK_RUN_ON(&thread_checker_);
  //source_proxy_的degradation_preference_ 也被赋值degradation_preference。
  source_proxy_->SetSource(source, degradation_preference);
  encoder_queue_.PostTask([this, degradation_preference] {
    RTC_DCHECK_RUN_ON(&encoder_queue_);
    if (degradation_preference_ != degradation_preference) {
      // Reset adaptation state, so that we're not tricked into thinking there's
      // an already pending request of the same type.
      last_adaptation_request_.reset();
      if (degradation_preference ==
              VideoSendStream::DegradationPreference::kBalanced ||
          degradation_preference_ ==
              VideoSendStream::DegradationPreference::kBalanced) {
        // TODO(asapersson): Consider removing |adapt_counters_| map and use one
        // AdaptCounter for all modes.
        source_proxy_->ResetPixelFpsCount();
        adapt_counters_.clear();
      }
    }
    degradation_preference_ = degradation_preference;
    bool allow_scaling = IsResolutionScalingEnabled(degradation_preference_);
    initial_rampup_ = allow_scaling ? 0 : kMaxInitialFramedrop;
    ConfigureQualityScaler();
    if (!IsFramerateScalingEnabled(degradation_preference) &&
        max_framerate_ != -1) {
      // If frame rate scaling is no longer allowed, remove any potential
      // allowance for longer frame intervals.
      overuse_detector_->OnTargetFramerateUpdated(max_framerate_);
    }
  });
}

进入文件video_stream_encoder.cc的ConfigureQualityScaler函数中:

void VideoStreamEncoder::ConfigureQualityScaler() {
  RTC_DCHECK_RUN_ON(&encoder_queue_);
  const auto scaling_settings = settings_.encoder->GetScalingSettings();
 //通过degradation_preference_来判断是否quality_scaling_allowed。
 //这里quality_scaling_allowed为ture。
  const bool quality_scaling_allowed =
      IsResolutionScalingEnabled(degradation_preference_) &&
      scaling_settings.enabled;

  // TODO:huping  statistics.
  RTC_LOG(LS_INFO) << "statistics quality quality_scaling_allowed:" << quality_scaling_allowed
                   << ", scaling_settings.enabled:" << scaling_settings.enabled
                   << ", IsResolutionScalingEnabled:" << IsResolutionScalingEnabled(degradation_preference_)
                   << ", degradation_preference_:" << (int)degradation_preference_;
                   //<< ", scaling_settings.threshold.low:" << scaling_settings.thresholds->low
                   //<< ", scaling_settings.threshold.high:" << scaling_settings.thresholds->high;

  //通过quality_scaling_allowed这个标志来判断是否使用QualityScaler。
  //这里quality_scaling_allowed为ture。
  //QualityScaler通过统计、计算编码后的每幅图像的量化参数(QP,Quantization  
  //Parameter,相当于图像的复杂度),当一系列图像的平均QP超过阈值时会调整分
  //辨率(H264的合法范围是24~37),超过37要降分辨率,低于24要提高分辨率。
  if (quality_scaling_allowed) {
    if (quality_scaler_.get() == nullptr) {
      // Quality scaler has not already been configured.
      // Drop frames and scale down until desired quality is achieved.
      if (scaling_settings.thresholds) {
        quality_scaler_.reset(
            new QualityScaler(this, *(scaling_settings.thresholds)));
        RTC_LOG(LS_INFO) << "statistics quality quality_scaler_ new 1";
      } else {
        quality_scaler_.reset(new QualityScaler(this, codec_type_));
        RTC_LOG(LS_INFO) << "statistics quality quality_scaler_ new 2";
      }
    }
  } else {
    quality_scaler_.reset(nullptr);
    initial_rampup_ = kMaxInitialFramedrop;
  }

  stats_proxy_->SetAdaptationStats(GetActiveCounts(kCpu),
                                   GetActiveCounts(kQuality));
}

进入文件quality_scaler.cc的QualityScaler::QualityScaler函数:

QualityScaler::QualityScaler(AdaptationObserverInterface* observer,
                             VideoCodecType codec_type)
    : QualityScaler(observer, CodecTypeToDefaultThresholds(codec_type)) {}
// Protected ctor, should not be called directly.
QualityScaler::QualityScaler(AdaptationObserverInterface* observer,
                             VideoEncoder::QpThresholds thresholds,
                             int64_t sampling_period)
    : check_qp_task_(nullptr),
      observer_(observer),
      sampling_period_ms_(sampling_period),
      fast_rampup_(true),
      // Arbitrarily choose size based on 30 fps for 5 seconds.
      average_qp_(5 * 30),
      framedrop_percent_(5 * 30),
      thresholds_(thresholds) {
  RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
  RTC_DCHECK(observer_ != nullptr);
  //这里 check_qp_task_被初始化,同时这个qp检测任务被启动。
  check_qp_task_ = new CheckQPTask(this);
  RTC_LOG(LS_INFO) << "statistics quality QP thresholds: low: " << thresholds_.low
                   << ", high: " << thresholds_.high;
}

进入文件quality_scaler.cc的QualityScaler::CheckQPTask类中,可以看出CheckQPTask在被初始的时候PostDelayedTask,这是一个周期性的任务,周期是scaler_->GetSamplingPeriodMs()。这个任务所执行的操作在Run函数中被定义,执行QualityScaler的CheckQP函数。

class QualityScaler::CheckQPTask : public rtc::QueuedTask {
 public:
  explicit CheckQPTask(QualityScaler* scaler) : scaler_(scaler) {
    RTC_LOG(LS_INFO) << "Created CheckQPTask. Scheduling on queue...";
    rtc::TaskQueue::Current()->PostDelayedTask(
        std::unique_ptr<rtc::QueuedTask>(this), scaler_->GetSamplingPeriodMs());
  }
  void Stop() {
    RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
    RTC_LOG(LS_INFO) << "Stopping QP Check task.";
    stop_ = true;
  }

 private:
  bool Run() override {
    RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); 
    if (stop_)
      return true;  // TaskQueue will free this task.
    scaler_->CheckQP();
    rtc::TaskQueue::Current()->PostDelayedTask(
        std::unique_ptr<rtc::QueuedTask>(this), scaler_->GetSamplingPeriodMs());
    return false;  // Retain the task in order to reuse it.
  }
    
  QualityScaler* const scaler_;
  bool stop_ = false;
  rtc::SequencedTaskChecker task_checker_;
};

进入quality_scaler.cc文件的QualityScaler::CheckQP函数。
QualityScaler通过统计、计算编码后的每幅图像的量化参数(QP,Quantization
Parameter,相当于图像的复杂度),当一系列图像的平均QP超过阈值时会调整分
辨率(H264的合法范围是24~37),超过37要降分辨率,低于24要提高分辨率。

void QualityScaler::CheckQP() {
  RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
  // Should be set through InitEncode -> Should be set by now.
  RTC_DCHECK_GE(thresholds_.low, 0);

  // If we have not observed at least this many frames we can't
  // make a good scaling decision.
  if (framedrop_percent_.size() < kMinFramesNeededToScale)
    return;

  // Check if we should scale down due to high frame drop.
  //获得丢帧的百分比,当丢帧百分比大于kFramedropPercentThreshold
  //(static const int kFramedropPercentThreshold = 60;),进行 ReportQPHigh()。
  const rtc::Optional<int> drop_rate = framedrop_percent_.GetAverage();
  if (drop_rate && *drop_rate >= kFramedropPercentThreshold) {
    ReportQPHigh();
    return;
  }

  // Check if we should scale up or down based on QP.
  //获得qp平均值
  const rtc::Optional<int> avg_qp = average_qp_.GetAverage();
  if (avg_qp) {
    RTC_LOG(LS_INFO) << "Checking average QP " << *avg_qp;
    if (*avg_qp > thresholds_.high) {
      ReportQPHigh();
      return;
    }
    if (*avg_qp <= thresholds_.low) {
      // QP has been low. We want to try a higher resolution.
      ReportQPLow();
      return;
    }
  }
}

丢帧百分比的统计首先是在文件video_sender.cc的VideoSender::AddVideoFrame函数:

// Add one raw video frame to the encoder, blocking.
int32_t VideoSender::AddVideoFrame(const VideoFrame& videoFrame,
                                   const CodecSpecificInfo* codecSpecificInfo) {

if (_mediaOpt.DropFrame()) {
  RTC_LOG(LS_INFO) << "statistics bitrate track Drop Frame "
                      << " rtt " << encoder_params.rtt
                      << " input frame rate " << encoder_params.input_frame_rate
                      << " loss rate " << encoder_params.loss_rate
                      << " target bitrate " << encoder_params.target_bitrate.get_sum_bps();
  post_encode_callback_->OnDroppedFrame(
      EncodedImageCallback::DropReason::kDroppedByMediaOptimizations);
  return VCM_OK;
}

然后进入文件video_stream_encoder.cc的VideoStreamEncoder::OnDroppedFrame函数

void VideoStreamEncoder::OnDroppedFrame(DropReason reason) {
  switch (reason) {
    case DropReason::kDroppedByMediaOptimizations:
      stats_proxy_->OnFrameDroppedByMediaOptimizations();
      encoder_queue_.PostTask([this] {
        RTC_DCHECK_RUN_ON(&encoder_queue_);
        if (quality_scaler_)
          quality_scaler_->ReportDroppedFrame();
      });
      break;
    case DropReason::kDroppedByEncoder:
      stats_proxy_->OnFrameDroppedByEncoder();
      break;
  }
}

最后进入quality_scaler.cc的QualityScaler::ReportDroppedFrame函数更新 framedrop_percent_。这里传入的100表示的是该帧100%被丢,因为计算的是丢帧百分比,所以统计采集到来和编码后的每一帧,丢了采样就是100,没丢采样就是0。

void QualityScaler::ReportDroppedFrame() {
  RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
  framedrop_percent_.AddSample(100);
}

framedrop_percent_的类型是MovingAverage,这个类型的成员函数如下。
这个类型计算平均值过程:
1.通过数量初始化 sum_history_,其类型是std::vector<int>。
2.通过AddSample添加样本。在这个函数中统计样本添加的次数,
  然后统计当前样本总数,最后将当前样本总数插入到sum_history_,
  位置为count_ % sum_history_.size()。
3.通过GetAverage求平均值,传入的样本数为当前count_和sum_history_.size() - 1
  中的最小值。
分析过程可得该类型计算的最近的 sum_history_.size() -1 的个样本的平均值。

MovingAverage::MovingAverage(size_t s) : sum_history_(s + 1, 0) {}

void MovingAverage::AddSample(int sample) {
  count_++;
  sum_ += sample;
  sum_history_[count_ % sum_history_.size()] = sum_;
}

rtc::Optional<int> MovingAverage::GetAverage() const {
  return GetAverage(size());
}

rtc::Optional<int> MovingAverage::GetAverage(size_t num_samples) const {
  if (num_samples > size() || num_samples == 0)
    return rtc::nullopt;
  //当前总的样本数 - num_samples之前统计的样本数 = num_samples的样本总数。
  int sum = sum_ - sum_history_[(count_ - num_samples) % sum_history_.size()];
  return sum / static_cast<int>(num_samples);
}

void MovingAverage::Reset() {
  count_ = 0;
  sum_ = 0;
  std::fill(sum_history_.begin(), sum_history_.end(), 0);
}

size_t MovingAverage::size() const {
  return std::min(count_, sum_history_.size() - 1);
}

我们再看一下关于qp统计的过程。
首先在文件video_stream_encoder.cc的VideoStreamEncoder::OnEncodedImage函数,统计编码后的每帧图像的qp。

EncodedImageCallback::Result VideoStreamEncoder::OnEncodedImage(
    const EncodedImage& encoded_image,
    const CodecSpecificInfo* codec_specific_info,
    const RTPFragmentationHeader* fragmentation) {
  // Encoded is called on whatever thread the real encoder implementation run
  // on. In the case of hardware encoders, there might be several encoders
  // running in parallel on different threads.
  stats_proxy_->OnSendEncodedImage(encoded_image, codec_specific_info);

  EncodedImageCallback::Result result =
      sink_->OnEncodedImage(encoded_image, codec_specific_info, fragmentation);

  int64_t time_sent_us = rtc::TimeMicros();
  uint32_t timestamp = encoded_image._timeStamp;
  const int qp = encoded_image.qp_;
  encoder_queue_.PostTask([this, timestamp, time_sent_us, qp] {
    RTC_DCHECK_RUN_ON(&encoder_queue_);
    overuse_detector_->FrameSent(timestamp, time_sent_us);
    if (quality_scaler_ && qp >= 0)
      quality_scaler_->ReportQP(qp);
  });

  return result;
}

然后进入文件quality_scaler.cc的QualityScaler::ReportQP函数进行qp统计,同时这个函数还统计了丢帧百分比采样为0。丢了采样就是100,没丢采样就是0。

void QualityScaler::ReportQP(int qp) {
  RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
  //丢帧百分比采样时0。
  framedrop_percent_.AddSample(0);
  average_qp_.AddSample(qp);
}

好了,我们了解了qp和丢帧百分比的统计过程,我们再回到quality_scaler.cc的QualityScaler::CheckQP函数,这个函数通过平均丢帧百分比和平均qp值与阈值的对比来判断是否需要调节分辨率和帧率。

// Check if we should scale down due to high frame drop.
const rtc::Optional<int> drop_rate = framedrop_percent_.GetAverage();
if (drop_rate && *drop_rate >= kFramedropPercentThreshold) {
  ReportQPHigh();
  return;
}

// Check if we should scale up or down based on QP.
const rtc::Optional<int> avg_qp = average_qp_.GetAverage();
if (avg_qp) {
  RTC_LOG(LS_INFO) << "Checking average QP " << *avg_qp;
  if (*avg_qp > thresholds_.high) {
    ReportQPHigh();
    return;
  }
  if (*avg_qp <= thresholds_.low) {
    // QP has been low. We want to try a higher resolution.
    ReportQPLow();
    return;
  }
}

QualityScaler通过统计、计算编码后的每幅图像的量化参数(QP,Quantization Parameter,相当于图像的复杂度),当一系列图像的平均QP超过阈值时会调整分辨率(H264的合法范围是24~37),超过37要降分辨率,低于24要提高分辨率。策略如下图所示:


图片.png

图片.png

qp的变化通过回调进行反馈。在函数ReportQPHigh中会修改fast_rampup_ = false; ,目的是分辨率一旦降低后,CheckQP调用的周期将变长,再提高分辨率的话,需要等很长时间。

void QualityScaler::ReportQPLow() {
  RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
  ClearSamples();
  observer_->AdaptUp(AdaptationObserverInterface::AdaptReason::kQuality);
}
void QualityScaler::ReportQPHigh() {
  RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
  ClearSamples();
  observer_->AdaptDown(AdaptationObserverInterface::AdaptReason::kQuality);
  // If we've scaled down, wait longer before scaling up again.
  if (fast_rampup_) {
    fast_rampup_ = false;
  }
}

上面调用回调,来到了文件video_stream_encoder.cc的VideoStreamEncoder::AdaptUp函数或者是VideoStreamEncoder::AdaptDown,这里以VideoStreamEncoder::AdaptDown为例进行介绍。

void VideoStreamEncoder::AdaptDown(AdaptReason reason) {
  RTC_DCHECK_RUN_ON(&encoder_queue_);
  AdaptationRequest adaptation_request = {
      //在函数VideoStreamEncoder::EncodeVideoFrame被赋值,如果采集模块过来的
      //图像分辨率发现了变化。
      last_frame_info_->pixel_count(),
      stats_proxy_->GetStats().input_frame_rate,
      AdaptationRequest::Mode::kAdaptDown};

  bool downgrade_requested =
      last_adaptation_request_ &&
      last_adaptation_request_->mode_ == AdaptationRequest::Mode::kAdaptDown;

  ...  

  switch (degradation_preference_) {
    case VideoSendStream::DegradationPreference::kBalanced: {
      // Try scale down framerate, if lower.
      int fps = MinFps(last_frame_info_->pixel_count());
      if (source_proxy_->RestrictFramerate(fps)) {
        GetAdaptCounter().IncrementFramerate(reason);
        break;
      }
      // Scale down resolution.
      FALLTHROUGH();
    }
   //degradation_preference_的值为
   //VideoSendStream::DegradationPreference::kMaintainFramerate(保帧率降分辨
   //率),所以执行这个case。
    case VideoSendStream::DegradationPreference::kMaintainFramerate: {
      // Scale down resolution.
      bool min_pixels_reached = false;
      if (!source_proxy_->RequestResolutionLowerThan(
              adaptation_request.input_pixel_count_,
              //在文件video_encoder.h的VideoEncoder类的ScalingSettings结构图被初始
              //化为:const int min_pixels_per_frame = 320 * 180;
              settings_.encoder->GetScalingSettings().min_pixels_per_frame,
              &min_pixels_reached)) {
        if (min_pixels_reached)
          stats_proxy_->OnMinPixelLimitReached();
        return;
      }
      GetAdaptCounter().IncrementResolution(reason);
      break;
    }
    case VideoSendStream::DegradationPreference::kMaintainResolution: {
      // Scale down framerate.
      const int requested_framerate = source_proxy_->RequestFramerateLowerThan(
          adaptation_request.framerate_fps_);
      if (requested_framerate == -1)
        return;
      RTC_DCHECK_NE(max_framerate_, -1);
      overuse_detector_->OnTargetFramerateUpdated(
          std::min(max_framerate_, requested_framerate));
      GetAdaptCounter().IncrementFramerate(reason);
      break;
    }
    case VideoSendStream::DegradationPreference::kDegradationDisabled:
      RTC_NOTREACHED();
  }

进入文件video_stream_encoder.cc的VideoStreamEncoder::VideoSourceProxy类的RequestResolutionLowerThan函数。

bool RequestResolutionLowerThan(int pixel_count,
                                int min_pixels_per_frame,
                                bool* min_pixels_reached) {

  // TODO:huping  statistics.
  RTC_LOG(LS_INFO) << "statistics quality pixel_count:" << pixel_count
                   << ", min_pixels_per_frame:" << min_pixels_per_frame
                   << ", min_pixels_reached:" << min_pixels_reached;
  // Called on the encoder task queue.
  rtc::CritScope lock(&crit_);
  if (!source_ || !IsResolutionScalingEnabled(degradation_preference_)) {
    // This can happen since |degradation_preference_| is set on libjingle's
    // worker thread but the adaptation is done on the encoder task queue.
    return false;
  }
  // The input video frame size will have a resolution less than or equal to
  // |max_pixel_count| depending on how the source can scale the frame size.
  //视频帧的像素将缩放到原来的3/5。
  const int pixels_wanted = (pixel_count * 3) / 5;
  if (pixels_wanted >= sink_wants_.max_pixel_count) {
    return false;
  }
  if (pixels_wanted < min_pixels_per_frame) {
    *min_pixels_reached = true;
    return false;
  }
  RTC_LOG(LS_INFO) << "statistics quality Scaling down resolution, max pixels: "
                   << pixels_wanted;
  sink_wants_.max_pixel_count = pixels_wanted;
  sink_wants_.target_pixel_count = rtc::Optional<int>();
  source_->AddOrUpdateSink(video_stream_encoder_,
                           GetActiveSinkWantsInternal());
  return true;
}

进入文件webrtcvideoengine.cc的WebRtcVideoChannel::WebRtcVideoSendStream::AddOrUpdateSink函数:

void WebRtcVideoChannel::WebRtcVideoSendStream::AddOrUpdateSink(
    rtc::VideoSinkInterface<webrtc::VideoFrame>* sink,
    const rtc::VideoSinkWants& wants) {
  if (worker_thread_ == rtc::Thread::Current()) {
    // AddOrUpdateSink is called on |worker_thread_| if this is the first
    // registration of |sink|.
    RTC_DCHECK_RUN_ON(&thread_checker_);
    encoder_sink_ = sink;
    source_->AddOrUpdateSink(encoder_sink_, wants);
  } else {
    // Subsequent calls to AddOrUpdateSink will happen on the encoder task
    // queue.
    invoker_.AsyncInvoke<void>(
        RTC_FROM_HERE, worker_thread_, [this, sink, wants] {
          RTC_DCHECK_RUN_ON(&thread_checker_);
          // |sink| may be invalidated after this task was posted since
          // RemoveSink is called on the worker thread.
          bool encoder_sink_valid = (sink == encoder_sink_);
          if (source_ && encoder_sink_valid) {
            source_->AddOrUpdateSink(encoder_sink_, wants);
          }
        });
  }
}

进入文件videotrack.cc的VideoTrack类的AddOrUpdateSink函数:

void VideoTrack::AddOrUpdateSink(
                                 rtc::VideoSinkInterface<VideoFrame>* sink,
                                 const rtc::VideoSinkWants& wants) {
  RTC_DCHECK(worker_thread_->IsCurrent());
  VideoSourceBase::AddOrUpdateSink(sink, wants);
  rtc::VideoSinkWants modified_wants = wants;
  modified_wants.black_frames = !enabled();
  //video_source_的真正类型是AndroidVideoTrackSource,在VideoTrack构造函数中被初始化。
  video_source_->AddOrUpdateSink(sink, modified_wants);
}

AndroidVideoTrackSource继承自AdaptedVideoTrackSource,所以这里执行文件adaptedvideotracksource.cc的AdaptedVideoTrackSource类的AddOrUpdateSink。

void AdaptedVideoTrackSource::AddOrUpdateSink(
    rtc::VideoSinkInterface<webrtc::VideoFrame>* sink,
    const rtc::VideoSinkWants& wants) {
  RTC_DCHECK(thread_checker_.CalledOnValidThread());
 //sink的真正类型是VideoStreamEncoder,这里更新了该sink的wants。
  broadcaster_.AddOrUpdateSink(sink, wants);
  OnSinkWantsChanged(broadcaster_.wants());
}

更新video_adapter_的分辨率和帧率相关的各个参数。

void AdaptedVideoTrackSource::OnSinkWantsChanged(
    const rtc::VideoSinkWants& wants) {
  RTC_DCHECK(thread_checker_.CalledOnValidThread());
  video_adapter_.OnResolutionFramerateRequest(
      wants.target_pixel_count, wants.max_pixel_count, wants.max_framerate_fps);
}

分辨率相关的各个参数已经更新了,我们现在才采集模块看一下如何使用这些参数。
文件androidvideotracksource.cc的AndroidVideoTrackSource::OnFrameCaptured函数,这个函数是图像的一个入口,从这里一直传递到编码器。在这个函数中调用AdaptFrame进行获取要调节至的分辨率,调用CropAndScale进行图像的具体缩放。

void AndroidVideoTrackSource::OnFrameCaptured(JNIEnv* jni,
                                              int width,
                                              int height,
                                              int64_t timestamp_ns,
                                              VideoRotation rotation,
                                              jobject j_video_frame_buffer) {
  RTC_DCHECK(camera_thread_checker_.CalledOnValidThread());

  int64_t camera_time_us = timestamp_ns / rtc::kNumNanosecsPerMicrosec;
  int64_t translated_camera_time_us =
      timestamp_aligner_.TranslateTimestamp(camera_time_us, rtc::TimeMicros());

  // TODO:huping change.
  int adapted_width = 0;
  int adapted_height = 0;
  int crop_width = 0;
  int crop_height = 0;
  int crop_x = 0;
  int crop_y = 0;

  // TODO:huping  statistics.
  RTC_LOG(LS_INFO) << "statistics quality width:" << width
                   << ", height:" << height
                   << ", crop_width:" << crop_width << ", crop_height:" << crop_height
                   << ", crop_x:" << crop_x << ", crop_y:" << crop_y
                   << ", adapted_width:" << adapted_width << ", adapted_height:" << adapted_height;

  if (!AdaptFrame(width, height, camera_time_us, &adapted_width,
                  &adapted_height, &crop_width, &crop_height, &crop_x,
                  &crop_y)) {
    return;
  }

  RTC_LOG(LS_INFO) << "statistics quality width:" << width
                   << ", height:" << height << ", crop_width:" << crop_width
                   << ", crop_height:" << crop_height << ", crop_x:" << crop_x
                   << ", crop_y:" << crop_y
                   << ", adapted_width:" << adapted_width << ", adapted_height:" << adapted_height;

  rtc::scoped_refptr<VideoFrameBuffer> buffer =
      AndroidVideoBuffer::Create(jni, j_video_frame_buffer)
          ->CropAndScale(jni, crop_x, crop_y, crop_width, crop_height,
                         adapted_width, adapted_height);

  // AdaptedVideoTrackSource handles applying rotation for I420 frames.
  if (apply_rotation() && rotation != kVideoRotation_0) {
    buffer = buffer->ToI420();
  }
  OnFrame(VideoFrame(buffer, rotation, translated_camera_time_us));
}

进入文件adaptedvideotracksource.cc的AdaptedVideoTrackSource::AdaptFrame的函数

bool AdaptedVideoTrackSource::AdaptFrame(int width,
                                         int height,
                                         int64_t time_us,
                                         int* out_width,
                                         int* out_height,
                                         int* crop_width,
                                         int* crop_height,
                                         int* crop_x,
                                         int* crop_y) {
  {
    rtc::CritScope lock(&stats_crit_);
    stats_ = Stats{width, height};
  }

  if (!broadcaster_.frame_wanted()) {
    return false;
  }

  //video_adapter_的分辨率和帧率相关的各个参数在前面已经被更新,这里进行获取
  //要调节至的分辨率
  if (!video_adapter_.AdaptFrameResolution(
          width, height, time_us * rtc::kNumNanosecsPerMicrosec,
          crop_width, crop_height, out_width, out_height)) {
    broadcaster_.OnDiscardedFrame();
    // VideoAdapter dropped the frame.
    return false;
  }

  *crop_x = (width - *crop_width) / 2;
  *crop_y = (height - *crop_height) / 2;
  return true;
}

进入文件videoadapter.cc的VideoAdapter::AdaptFrameResolution函数,该函数调用 FindScale获取将要调节至的分辨率,然后通过指针类型的out_width和out_width返回给调用函数。

bool VideoAdapter::AdaptFrameResolution(int in_width,
                                        int in_height,
                                        int64_t in_timestamp_ns,
                                        int* cropped_width,
                                        int* cropped_height,
                                        int* out_width,
                                        int* out_width) {
  rtc::CritScope cs(&critical_section_);
  ++frames_in_;

  // The max output pixel count is the minimum of the requests from
  // OnOutputFormatRequest and OnResolutionRequest.
  int max_pixel_count = resolution_request_max_pixel_count_;
  if (requested_format_) {
    max_pixel_count = std::min(
        max_pixel_count, requested_format_->width * requested_format_->height);
  }
  int target_pixel_count =
      std::min(resolution_request_target_pixel_count_, max_pixel_count);

  // Drop the input frame if necessary.
  if (max_pixel_count <= 0 || !KeepFrame(in_timestamp_ns)) {
    // Show VAdapt log every 90 frames dropped. (3 seconds)
    if ((frames_in_ - frames_out_) % 90 == 0) {
      // TODO(fbarchard): Reduce to LS_VERBOSE when adapter info is not needed
      // in default calls.
      RTC_LOG(LS_INFO) << "VAdapt Drop Frame: scaled " << frames_scaled_
                       << " / out " << frames_out_ << " / in " << frames_in_
                       << " Changes: " << adaption_changes_
                       << " Input: " << in_width << "x" << in_height
                       << " timestamp: " << in_timestamp_ns << " Output: i"
                       << (requested_format_ ? requested_format_->interval : 0);
    }

    // Drop frame.
    return false;
  }

  // Calculate how the input should be cropped.
  if (!requested_format_ ||
      requested_format_->width == 0 || requested_format_->height == 0) {
    *cropped_width = in_width;
    *cropped_height = in_height;
  } else {
    // Adjust |requested_format_| orientation to match input.
    if ((in_width > in_height) !=
        (requested_format_->width > requested_format_->height)) {
      std::swap(requested_format_->width, requested_format_->height);
    }
    const float requested_aspect =
        requested_format_->width /
        static_cast<float>(requested_format_->height);
    *cropped_width =
        std::min(in_width, static_cast<int>(in_height * requested_aspect));
    *cropped_height =
        std::min(in_height, static_cast<int>(in_width / requested_aspect));
  }
  const Fraction scale = FindScale((*cropped_width) * (*cropped_height),
                                   target_pixel_count, max_pixel_count);
  // Adjust cropping slightly to get even integer output size and a perfect
  // scale factor. Make sure the resulting dimensions are aligned correctly
  // to be nice to hardware encoders.
  *cropped_width =
      roundUp(*cropped_width,
              scale.denominator * required_resolution_alignment_, in_width);
  *cropped_height =
      roundUp(*cropped_height,
              scale.denominator * required_resolution_alignment_, in_height);
  RTC_DCHECK_EQ(0, *cropped_width % scale.denominator);
  RTC_DCHECK_EQ(0, *cropped_height % scale.denominator);

  // Calculate final output size.
  *out_width = *cropped_width / scale.denominator * scale.numerator;
  *out_height = *cropped_height / scale.denominator * scale.numerator;
  RTC_DCHECK_EQ(0, *out_width % required_resolution_alignment_);
  RTC_DCHECK_EQ(0, *out_height % required_resolution_alignment_);

  ++frames_out_;
  if (scale.numerator != scale.denominator)
    ++frames_scaled_;

  if (previous_width_ && (previous_width_ != *out_width ||
                          previous_height_ != *out_height)) {
    ++adaption_changes_;
    RTC_LOG(LS_INFO) << "Frame size changed: scaled " << frames_scaled_
                     << " / out " << frames_out_ << " / in " << frames_in_
                     << " Changes: " << adaption_changes_
                     << " Input: " << in_width << "x" << in_height
                     << " Scale: " << scale.numerator << "/"
                     << scale.denominator << " Output: " << *out_width << "x"
                     << *out_height << " i"
                     << (requested_format_ ? requested_format_->interval : 0);
  }

  previous_width_ = *out_width;
  previous_height_ = *out_height;

  return true;
}

进入文件videoadapter.cc的 FindScale函数。该函数通过交替乘 2/3 and 3/4以缩小分辨率到最接近目标分辨率,然后这个最接近的分辨率。
For instance, starting at 1280x720 will result in
the series (3/4) => 960x540, (1/2) => 640x360, (3/8) => 480x270,
(1/4) => 320x180, (3/16) => 240x125, (1/8) => 160x90.

// Generates a scale factor that makes |input_pixels| close to |target_pixels|,
// but no higher than |max_pixels|.
Fraction FindScale(int input_pixels, int target_pixels, int max_pixels) {
  // This function only makes sense for a positive target.
  RTC_DCHECK_GT(target_pixels, 0);
  RTC_DCHECK_GT(max_pixels, 0);
  RTC_DCHECK_GE(max_pixels, target_pixels);

  // Don't scale up original.
  if (target_pixels >= input_pixels)
    return Fraction{1, 1};

  Fraction current_scale = Fraction{1, 1};
  Fraction best_scale = Fraction{1, 1};
  // The minimum (absolute) difference between the number of output pixels and
  // the target pixel count.
  int min_pixel_diff = std::numeric_limits<int>::max();
  if (input_pixels <= max_pixels) {
    // Start condition for 1/1 case, if it is less than max.
    min_pixel_diff = std::abs(input_pixels - target_pixels);
  }

  // Alternately scale down by 2/3 and 3/4. This results in fractions which are
  // effectively scalable. For instance, starting at 1280x720 will result in
  // the series (3/4) => 960x540, (1/2) => 640x360, (3/8) => 480x270,
  // (1/4) => 320x180, (3/16) => 240x125, (1/8) => 160x90.
  while (current_scale.scale_pixel_count(input_pixels) > target_pixels) {
    if (current_scale.numerator % 3 == 0 &&
        current_scale.denominator % 2 == 0) {
      // Multiply by 2/3.
      current_scale.numerator /= 3;
      current_scale.denominator /= 2;
    } else {
      // Multiply by 3/4.
      current_scale.numerator *= 3;
      current_scale.denominator *= 4;
    }

    int output_pixels = current_scale.scale_pixel_count(input_pixels);
    if (output_pixels <= max_pixels) {
      int diff = std::abs(target_pixels - output_pixels);
      if (diff < min_pixel_diff) {
        min_pixel_diff = diff;
        best_scale = current_scale;
      }
    }
  }
  return best_scale;
}

有了要调节至的分辨率,现在要做的就是进行缩放到该分辨率。回到文件androidvideotracksource.cc的AndroidVideoTrackSource::OnFrameCaptured函数。调用如下代码进行缩放。

rtc::scoped_refptr<VideoFrameBuffer> buffer =
    AndroidVideoBuffer::Create(jni, j_video_frame_buffer)
        ->CropAndScale(jni, crop_x, crop_y, crop_width, crop_height,
                       adapted_width, adapted_height);

具体缩放经过如下几个函数

rtc::scoped_refptr<AndroidVideoBuffer> AndroidVideoBuffer::Create(
    JNIEnv* jni,
    jobject j_video_frame_buffer) {
  Java_Buffer_retain(jni, j_video_frame_buffer);
  return Adopt(jni, j_video_frame_buffer);
}
rtc::scoped_refptr<AndroidVideoBuffer> AndroidVideoBuffer::Adopt(
    JNIEnv* jni,
    jobject j_video_frame_buffer) {
  return new rtc::RefCountedObject<AndroidVideoBuffer>(jni,
                                                       j_video_frame_buffer);
}
AndroidVideoBuffer::AndroidVideoBuffer(JNIEnv* jni,
                                       jobject j_video_frame_buffer)
    : width_(Java_Buffer_getWidth(jni, j_video_frame_buffer)),
      height_(Java_Buffer_getHeight(jni, j_video_frame_buffer)),
      j_video_frame_buffer_(jni, j_video_frame_buffer) {}
rtc::scoped_refptr<AndroidVideoBuffer> AndroidVideoBuffer::CropAndScale(
    JNIEnv* jni,
    int crop_x,
    int crop_y,
    int crop_width,
    int crop_height,
    int scale_width,
    int scale_height) {
  return Adopt(jni, Java_Buffer_cropAndScale(
                        jni, *j_video_frame_buffer_, crop_x, crop_y, crop_width,
                        crop_height, scale_width, scale_height));
}

webrtc最为诡谲之调用,尼玛该函数在文件out/Release/gen/sdk/android/generated_video_jni/jni/VideoFrame_jni.h中。反射通过c++调用java层代码,进行图像缩放。作者很懒没有进一步研究,道友如果感兴趣可以深入研究。

static base::android::ScopedJavaLocalRef<jobject>
    Java_Buffer_cropAndScale(JNIEnv* env, const base::android::JavaRef<jobject>&
    obj, JniIntWrapper cropX,
    JniIntWrapper cropY,
    JniIntWrapper cropWidth,
    JniIntWrapper cropHeight,
    JniIntWrapper scaleWidth,
    JniIntWrapper scaleHeight) {
  CHECK_CLAZZ(env, obj.obj(),
      org_webrtc_VideoFrame_00024Buffer_clazz(env), NULL);
  jmethodID method_id =
      base::android::MethodID::LazyGet<
      base::android::MethodID::TYPE_INSTANCE>(
      env, org_webrtc_VideoFrame_00024Buffer_clazz(env),
      "cropAndScale",
"("
"I"
"I"
"I"
"I"
"I"
"I"
")"
"Lorg/webrtc/VideoFrame$Buffer;",
      &g_org_webrtc_VideoFrame_00024Buffer_cropAndScale);

  jobject ret =
      env->CallObjectMethod(obj.obj(),
          method_id, as_jint(cropX), as_jint(cropY), as_jint(cropWidth),
              as_jint(cropHeight), as_jint(scaleWidth), as_jint(scaleHeight));
  jni_generator::CheckException(env);
  return base::android::ScopedJavaLocalRef<jobject>(env, ret);
}

到这里整个流程基本完成。

后记

通过熟悉动态调节这个分辨率的流程,我们对webrtc应对弱网的码率自适应有了更深刻的理解。这些都是降低码率的执行单元,而驱动模块是如果统筹规划的,需要进一步研究,这些更为复杂也更为核心。望自己能够有时间,有信心能够潜心研究。
再者在文件video_stream_encoder.cc的VideoStreamEncoder构造函数中,会初始化变量overuse_detector_,其类型是OveruseFrameDetector,作用是判断cpu使用是否过载,如果是则降低分辨率或者是帧率,否则恢复。判断的依据是编码的平均duration / 采集的平均duration得到的值与一low阈值和一个high阈值的对比。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容