前言:

上篇文章《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::DegradationPreferenceWebRtcVideoChannel::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* 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* 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(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(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 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 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
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 MovingAverage::GetAverage() const {  return GetAverage(size());}rtc::Optional 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(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 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 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();  source_->AddOrUpdateSink(video_stream_encoder_,                           GetActiveSinkWantsInternal());  return true;}

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

void WebRtcVideoChannel::WebRtcVideoSendStream::AddOrUpdateSink(    rtc::VideoSinkInterface* 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(        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* 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* 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 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(requested_format_->height);    *cropped_width =        std::min(in_width, static_cast(in_height * requested_aspect));    *cropped_height =        std::min(in_height, static_cast(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::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 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::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::Adopt(    JNIEnv* jni,    jobject j_video_frame_buffer) {  return new rtc::RefCountedObject(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::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    Java_Buffer_cropAndScale(JNIEnv* env, const base::android::JavaRef&    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(env, ret);}

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

后记

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

更多相关文章

  1. 【Android开发学习35】GL_TRIANGLE_STRIP之纹理贴图
  2. Android(安卓)Studio在启动时gradle的问题:Download https://serv
  3. cocos2d-x android c++调用java
  4. [Android]资源存储方法
  5. Kotlin For Android介绍
  6. 了解Android(安卓)Studio jniLibs目录
  7. Android(安卓)Webview 使用Html播放音频文件
  8. 【Android】第三方库使用的问题集
  9. (转)#小美化#android 按钮圆角

随机推荐

  1. android练习一之拨号实现
  2. 【Android】Activity 的 Title 中加入进
  3. android sdk 自带的非空非空串判断方法
  4. 【android开发】android操作文件
  5. android一键清除最近应用按钮
  6. Android(安卓)APP 设置启动的第一张图片
  7. android 选择图片(从手机照相机或手机图
  8. Android(安卓)studio引入百度地图时的bug
  9. android java.lang.IllegalStateExceptio
  10. 判断Android客户端网络是否连接