Encoder switching based on network and/or resolution conditions.
In this CL: - Renamed EncoderFailureCallback to EncoderSwitchRequestCallback. An encoder switch request can now also be made with a configuration that specifies which codec/implementation to switch to. - Added "WebRTC-NetworkCondition-EncoderSwitch" field trial that specifies switching conditions and desired codec to switch to. - Added checks to trigger the switch based on these conditions. Bug: webrtc:10795 Change-Id: I9d3a9a39a7c4827915a40bdceed10b581d70b90a Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/151900 Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org> Reviewed-by: Niels Moller <nisse@webrtc.org> Commit-Queue: Philip Eliasson <philipel@webrtc.org> Cr-Commit-Position: refs/heads/master@{#29196}
This commit is contained in:
parent
73ceed58f8
commit
d9cc8c08dc
@ -11,16 +11,29 @@
|
||||
#ifndef API_VIDEO_VIDEO_STREAM_ENCODER_SETTINGS_H_
|
||||
#define API_VIDEO_VIDEO_STREAM_ENCODER_SETTINGS_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "api/video/video_bitrate_allocator_factory.h"
|
||||
#include "api/video_codecs/video_encoder.h"
|
||||
#include "api/video_codecs/video_encoder_factory.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class EncoderFailureCallback {
|
||||
class EncoderSwitchRequestCallback {
|
||||
public:
|
||||
virtual ~EncoderFailureCallback() {}
|
||||
virtual void OnEncoderFailure() = 0;
|
||||
virtual ~EncoderSwitchRequestCallback() {}
|
||||
|
||||
struct Config {
|
||||
std::string codec_name;
|
||||
absl::optional<std::string> param;
|
||||
absl::optional<std::string> value;
|
||||
};
|
||||
|
||||
// Requests that encoder fallback is performed.
|
||||
virtual void RequestEncoderFallback() = 0;
|
||||
|
||||
// Requests that a switch to a specific encoder is performed.
|
||||
virtual void RequestEncoderSwitch(const Config& conf) = 0;
|
||||
};
|
||||
|
||||
struct VideoStreamEncoderSettings {
|
||||
@ -35,8 +48,8 @@ struct VideoStreamEncoderSettings {
|
||||
// Ownership stays with WebrtcVideoEngine (delegated from PeerConnection).
|
||||
VideoEncoderFactory* encoder_factory = nullptr;
|
||||
|
||||
// Notifies the WebRtcVideoChannel that the currently used encoder is broken.
|
||||
EncoderFailureCallback* encoder_failure_callback = nullptr;
|
||||
// Requests the WebRtcVideoChannel to perform a codec switch.
|
||||
EncoderSwitchRequestCallback* encoder_switch_request_callback = nullptr;
|
||||
|
||||
// Ownership stays with WebrtcVideoEngine (delegated from PeerConnection).
|
||||
VideoBitrateAllocatorFactory* bitrate_allocator_factory = nullptr;
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
#include "media/engine/webrtc_voice_engine.h"
|
||||
#include "rtc_base/copy_on_write_buffer.h"
|
||||
#include "rtc_base/experiments/field_trial_parser.h"
|
||||
#include "rtc_base/experiments/field_trial_units.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/strings/string_builder.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
@ -738,7 +739,7 @@ bool WebRtcVideoChannel::SetSendParameters(const VideoSendParameters& params) {
|
||||
return ApplyChangedParams(changed_params);
|
||||
}
|
||||
|
||||
void WebRtcVideoChannel::OnEncoderFailure() {
|
||||
void WebRtcVideoChannel::RequestEncoderFallback() {
|
||||
invoker_.AsyncInvoke<void>(
|
||||
RTC_FROM_HERE, worker_thread_, [this] {
|
||||
RTC_DCHECK_RUN_ON(&thread_checker_);
|
||||
@ -756,6 +757,45 @@ void WebRtcVideoChannel::OnEncoderFailure() {
|
||||
});
|
||||
}
|
||||
|
||||
void WebRtcVideoChannel::RequestEncoderSwitch(
|
||||
const EncoderSwitchRequestCallback::Config& conf) {
|
||||
invoker_.AsyncInvoke<void>(RTC_FROM_HERE, worker_thread_, [this, conf] {
|
||||
RTC_DCHECK_RUN_ON(&thread_checker_);
|
||||
|
||||
for (VideoCodecSettings codec_setting : negotiated_codecs_) {
|
||||
if (codec_setting.codec.name == conf.codec_name) {
|
||||
if (conf.param) {
|
||||
auto it = codec_setting.codec.params.find(*conf.param);
|
||||
|
||||
if (it == codec_setting.codec.params.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (conf.value && it->second != *conf.value) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (send_codec_ == codec_setting) {
|
||||
// Already using this codec, no switch required.
|
||||
return;
|
||||
}
|
||||
|
||||
ChangedSendParameters params;
|
||||
params.send_codec = codec_setting;
|
||||
ApplyChangedParams(params);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
RTC_LOG(LS_WARNING) << "Requested encoder with codec_name:"
|
||||
<< conf.codec_name
|
||||
<< ", param:" << conf.param.value_or("none")
|
||||
<< " and value:" << conf.value.value_or("none")
|
||||
<< "not found. No switch performed.";
|
||||
});
|
||||
}
|
||||
|
||||
bool WebRtcVideoChannel::ApplyChangedParams(
|
||||
const ChangedSendParameters& changed_params) {
|
||||
RTC_DCHECK_RUN_ON(&thread_checker_);
|
||||
@ -1172,7 +1212,7 @@ bool WebRtcVideoChannel::AddSendStream(const StreamParams& sp) {
|
||||
config.encoder_settings.encoder_factory = encoder_factory_;
|
||||
config.encoder_settings.bitrate_allocator_factory =
|
||||
bitrate_allocator_factory_;
|
||||
config.encoder_settings.encoder_failure_callback = this;
|
||||
config.encoder_settings.encoder_switch_request_callback = this;
|
||||
config.crypto_options = crypto_options_;
|
||||
config.rtp.extmap_allow_mixed = ExtmapAllowMixed();
|
||||
config.rtcp_report_interval_ms = video_config_.rtcp_report_interval_ms;
|
||||
|
||||
@ -122,7 +122,7 @@ class WebRtcVideoEngine : public VideoEngineInterface {
|
||||
|
||||
class WebRtcVideoChannel : public VideoMediaChannel,
|
||||
public webrtc::Transport,
|
||||
public webrtc::EncoderFailureCallback {
|
||||
public webrtc::EncoderSwitchRequestCallback {
|
||||
public:
|
||||
WebRtcVideoChannel(
|
||||
webrtc::Call* call,
|
||||
@ -221,8 +221,10 @@ class WebRtcVideoChannel : public VideoMediaChannel,
|
||||
// This method does nothing unless unknown_ssrc_packet_buffer_ is configured.
|
||||
void BackfillBufferedPackets(rtc::ArrayView<const uint32_t> ssrcs);
|
||||
|
||||
// Implements webrtc::EncoderFailureCallback.
|
||||
void OnEncoderFailure() override;
|
||||
// Implements webrtc::EncoderSwitchRequestCallback.
|
||||
void RequestEncoderFallback() override;
|
||||
void RequestEncoderSwitch(
|
||||
const EncoderSwitchRequestCallback::Config& conf) override;
|
||||
|
||||
private:
|
||||
class WebRtcVideoReceiveStream;
|
||||
|
||||
@ -61,8 +61,11 @@
|
||||
#include "test/gmock.h"
|
||||
#include "test/rtp_header_parser.h"
|
||||
|
||||
using ::testing::Contains;
|
||||
using ::testing::Eq;
|
||||
using ::testing::Field;
|
||||
using ::testing::IsEmpty;
|
||||
using ::testing::Pair;
|
||||
using ::testing::SizeIs;
|
||||
using webrtc::BitrateConstraints;
|
||||
using webrtc::RtpExtension;
|
||||
@ -2112,7 +2115,7 @@ TEST_F(WebRtcVideoChannelBaseTest, TwoStreamsSendAndReceive) {
|
||||
TwoStreamsSendAndReceive(codec);
|
||||
}
|
||||
|
||||
TEST_F(WebRtcVideoChannelBaseTest, OnEncoderFailure) {
|
||||
TEST_F(WebRtcVideoChannelBaseTest, RequestEncoderFallback) {
|
||||
cricket::VideoSendParameters parameters;
|
||||
parameters.codecs.push_back(GetEngineCodec("VP9"));
|
||||
parameters.codecs.push_back(GetEngineCodec("VP8"));
|
||||
@ -2122,20 +2125,95 @@ TEST_F(WebRtcVideoChannelBaseTest, OnEncoderFailure) {
|
||||
ASSERT_TRUE(channel_->GetSendCodec(&codec));
|
||||
EXPECT_EQ("VP9", codec.name);
|
||||
|
||||
// OnEncoderFailure will post a task to the worker thread (which is also
|
||||
// RequestEncoderFallback will post a task to the worker thread (which is also
|
||||
// the current thread), hence the ProcessMessages call.
|
||||
channel_->OnEncoderFailure();
|
||||
channel_->RequestEncoderFallback();
|
||||
rtc::Thread::Current()->ProcessMessages(30);
|
||||
ASSERT_TRUE(channel_->GetSendCodec(&codec));
|
||||
EXPECT_EQ("VP8", codec.name);
|
||||
|
||||
// No other codec to fall back to, keep using VP8.
|
||||
channel_->OnEncoderFailure();
|
||||
channel_->RequestEncoderFallback();
|
||||
rtc::Thread::Current()->ProcessMessages(30);
|
||||
ASSERT_TRUE(channel_->GetSendCodec(&codec));
|
||||
EXPECT_EQ("VP8", codec.name);
|
||||
}
|
||||
|
||||
TEST_F(WebRtcVideoChannelBaseTest, RequestEncoderSwitchWithConfig) {
|
||||
const std::string kParam = "the-param";
|
||||
const std::string kPing = "ping";
|
||||
const std::string kPong = "pong";
|
||||
|
||||
cricket::VideoSendParameters parameters;
|
||||
VideoCodec vp9 = GetEngineCodec("VP9");
|
||||
vp9.params[kParam] = kPong;
|
||||
parameters.codecs.push_back(vp9);
|
||||
|
||||
VideoCodec vp8 = GetEngineCodec("VP8");
|
||||
vp8.params[kParam] = kPing;
|
||||
parameters.codecs.push_back(vp8);
|
||||
|
||||
EXPECT_TRUE(channel_->SetSendParameters(parameters));
|
||||
|
||||
VideoCodec codec;
|
||||
ASSERT_TRUE(channel_->GetSendCodec(&codec));
|
||||
EXPECT_THAT(codec.name, Eq("VP9"));
|
||||
|
||||
// RequestEncoderSwitch will post a task to the worker thread (which is also
|
||||
// the current thread), hence the ProcessMessages call.
|
||||
webrtc::EncoderSwitchRequestCallback::Config conf1{"VP8", kParam, kPing};
|
||||
channel_->RequestEncoderSwitch(conf1);
|
||||
rtc::Thread::Current()->ProcessMessages(30);
|
||||
ASSERT_TRUE(channel_->GetSendCodec(&codec));
|
||||
EXPECT_THAT(codec.name, Eq("VP8"));
|
||||
EXPECT_THAT(codec.params, Contains(Pair(kParam, kPing)));
|
||||
|
||||
webrtc::EncoderSwitchRequestCallback::Config conf2{"VP9", kParam, kPong};
|
||||
channel_->RequestEncoderSwitch(conf2);
|
||||
rtc::Thread::Current()->ProcessMessages(30);
|
||||
ASSERT_TRUE(channel_->GetSendCodec(&codec));
|
||||
EXPECT_THAT(codec.name, Eq("VP9"));
|
||||
EXPECT_THAT(codec.params, Contains(Pair(kParam, kPong)));
|
||||
}
|
||||
|
||||
TEST_F(WebRtcVideoChannelBaseTest, RequestEncoderSwitchIncorrectParam) {
|
||||
const std::string kParam = "the-param";
|
||||
const std::string kPing = "ping";
|
||||
const std::string kPong = "pong";
|
||||
|
||||
cricket::VideoSendParameters parameters;
|
||||
VideoCodec vp9 = GetEngineCodec("VP9");
|
||||
vp9.params[kParam] = kPong;
|
||||
parameters.codecs.push_back(vp9);
|
||||
|
||||
VideoCodec vp8 = GetEngineCodec("VP8");
|
||||
vp8.params[kParam] = kPing;
|
||||
parameters.codecs.push_back(vp8);
|
||||
|
||||
EXPECT_TRUE(channel_->SetSendParameters(parameters));
|
||||
|
||||
VideoCodec codec;
|
||||
ASSERT_TRUE(channel_->GetSendCodec(&codec));
|
||||
EXPECT_THAT(codec.name, Eq("VP9"));
|
||||
|
||||
// RequestEncoderSwitch will post a task to the worker thread (which is also
|
||||
// the current thread), hence the ProcessMessages call.
|
||||
webrtc::EncoderSwitchRequestCallback::Config conf1{"VP8", kParam, kPing};
|
||||
channel_->RequestEncoderSwitch(conf1);
|
||||
rtc::Thread::Current()->ProcessMessages(30);
|
||||
ASSERT_TRUE(channel_->GetSendCodec(&codec));
|
||||
EXPECT_THAT(codec.name, Eq("VP8"));
|
||||
EXPECT_THAT(codec.params, Contains(Pair(kParam, kPing)));
|
||||
|
||||
// Incorrect conf2.value, expect no codec switch.
|
||||
webrtc::EncoderSwitchRequestCallback::Config conf2{"VP9", kParam, kPing};
|
||||
channel_->RequestEncoderSwitch(conf2);
|
||||
rtc::Thread::Current()->ProcessMessages(30);
|
||||
ASSERT_TRUE(channel_->GetSendCodec(&codec));
|
||||
EXPECT_THAT(codec.name, Eq("VP8"));
|
||||
EXPECT_THAT(codec.params, Contains(Pair(kParam, kPing)));
|
||||
}
|
||||
|
||||
class WebRtcVideoChannelTest : public WebRtcVideoEngineTest {
|
||||
public:
|
||||
WebRtcVideoChannelTest() : WebRtcVideoChannelTest("") {}
|
||||
|
||||
@ -526,7 +526,9 @@ VideoStreamEncoder::VideoStreamEncoder(
|
||||
next_frame_id_(0),
|
||||
encoder_queue_(task_queue_factory->CreateTaskQueue(
|
||||
"EncoderQueue",
|
||||
TaskQueueFactory::Priority::NORMAL)) {
|
||||
TaskQueueFactory::Priority::NORMAL)),
|
||||
encoder_switch_experiment_(ParseEncoderSwitchFieldTrial()),
|
||||
encoder_switch_requested_(false) {
|
||||
RTC_DCHECK(encoder_stats_observer);
|
||||
RTC_DCHECK(overuse_detector_);
|
||||
RTC_DCHECK_GE(number_of_cores, 1);
|
||||
@ -720,6 +722,19 @@ GetEncoderBitrateLimits(const VideoEncoder::EncoderInfo& encoder_info,
|
||||
// "soft" reconfiguration.
|
||||
void VideoStreamEncoder::ReconfigureEncoder() {
|
||||
RTC_DCHECK(pending_encoder_reconfiguration_);
|
||||
|
||||
if (encoder_switch_experiment_.IsPixelCountBelowThreshold(
|
||||
last_frame_info_->width * last_frame_info_->height) &&
|
||||
!encoder_switch_requested_ && settings_.encoder_switch_request_callback) {
|
||||
EncoderSwitchRequestCallback::Config conf;
|
||||
conf.codec_name = encoder_switch_experiment_.to_codec;
|
||||
conf.param = encoder_switch_experiment_.to_param;
|
||||
conf.value = encoder_switch_experiment_.to_value;
|
||||
settings_.encoder_switch_request_callback->RequestEncoderSwitch(conf);
|
||||
|
||||
encoder_switch_requested_ = true;
|
||||
}
|
||||
|
||||
std::vector<VideoStream> streams =
|
||||
encoder_config_.video_stream_factory->CreateEncoderStreams(
|
||||
last_frame_info_->width, last_frame_info_->height, encoder_config_);
|
||||
@ -841,6 +856,8 @@ void VideoStreamEncoder::ReconfigureEncoder() {
|
||||
}
|
||||
send_codec_ = codec;
|
||||
|
||||
encoder_switch_experiment_.SetCodec(send_codec_.codecType);
|
||||
|
||||
// Keep the same encoder, as long as the video_format is unchanged.
|
||||
// Encoder creation block is split in two since EncoderInfo needed to start
|
||||
// CPU adaptation with the correct settings should be polled after
|
||||
@ -1484,9 +1501,9 @@ void VideoStreamEncoder::EncodeVideoFrame(const VideoFrame& video_frame,
|
||||
if (encode_status == WEBRTC_VIDEO_CODEC_ENCODER_FAILURE) {
|
||||
RTC_LOG(LS_ERROR) << "Encoder failed, failing encoder format: "
|
||||
<< encoder_config_.video_format.ToString();
|
||||
if (settings_.encoder_failure_callback) {
|
||||
if (settings_.encoder_switch_request_callback) {
|
||||
encoder_failed_ = true;
|
||||
settings_.encoder_failure_callback->OnEncoderFailure();
|
||||
settings_.encoder_switch_request_callback->RequestEncoderFallback();
|
||||
} else {
|
||||
RTC_LOG(LS_ERROR)
|
||||
<< "Encoder failed but no encoder fallback callback is registered";
|
||||
@ -1721,6 +1738,18 @@ void VideoStreamEncoder::OnBitrateUpdated(DataRate target_bitrate,
|
||||
return;
|
||||
}
|
||||
RTC_DCHECK_RUN_ON(&encoder_queue_);
|
||||
|
||||
if (encoder_switch_experiment_.IsBitrateBelowThreshold(target_bitrate) &&
|
||||
settings_.encoder_switch_request_callback && !encoder_switch_requested_) {
|
||||
EncoderSwitchRequestCallback::Config conf;
|
||||
conf.codec_name = encoder_switch_experiment_.to_codec;
|
||||
conf.param = encoder_switch_experiment_.to_param;
|
||||
conf.value = encoder_switch_experiment_.to_value;
|
||||
settings_.encoder_switch_request_callback->RequestEncoderSwitch(conf);
|
||||
|
||||
encoder_switch_requested_ = true;
|
||||
}
|
||||
|
||||
RTC_DCHECK(sink_) << "sink_ must be set before the encoder is active.";
|
||||
|
||||
RTC_LOG(LS_VERBOSE) << "OnBitrateUpdated, bitrate " << target_bitrate.bps()
|
||||
@ -2232,4 +2261,110 @@ std::string VideoStreamEncoder::AdaptCounter::ToString(
|
||||
return ss.Release();
|
||||
}
|
||||
|
||||
bool VideoStreamEncoder::EncoderSwitchExperiment::IsBitrateBelowThreshold(
|
||||
const DataRate& target_bitrate) {
|
||||
DataRate rate =
|
||||
DataRate::kbps(bitrate_filter.Apply(1.0, target_bitrate.kbps()));
|
||||
return current_thresholds.bitrate && rate < *current_thresholds.bitrate;
|
||||
}
|
||||
|
||||
bool VideoStreamEncoder::EncoderSwitchExperiment::IsPixelCountBelowThreshold(
|
||||
int pixel_count) const {
|
||||
return current_thresholds.pixel_count &&
|
||||
pixel_count < *current_thresholds.pixel_count;
|
||||
}
|
||||
|
||||
void VideoStreamEncoder::EncoderSwitchExperiment::SetCodec(
|
||||
VideoCodecType codec) {
|
||||
auto it = codec_thresholds.find(codec);
|
||||
if (it == codec_thresholds.end()) {
|
||||
current_thresholds = {};
|
||||
} else {
|
||||
current_thresholds = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
VideoStreamEncoder::EncoderSwitchExperiment
|
||||
VideoStreamEncoder::ParseEncoderSwitchFieldTrial() const {
|
||||
EncoderSwitchExperiment result;
|
||||
|
||||
// Each "codec threshold" have the format
|
||||
// "<codec name>;<bitrate kbps>;<pixel count>", and are separated by the "|"
|
||||
// character.
|
||||
webrtc::FieldTrialOptional<std::string> codec_thresholds_string{
|
||||
"codec_thresholds"};
|
||||
webrtc::FieldTrialOptional<std::string> to_codec{"to_codec"};
|
||||
webrtc::FieldTrialOptional<std::string> to_param{"to_param"};
|
||||
webrtc::FieldTrialOptional<std::string> to_value{"to_value"};
|
||||
webrtc::FieldTrialOptional<double> window{"window"};
|
||||
|
||||
webrtc::ParseFieldTrial(
|
||||
{&codec_thresholds_string, &to_codec, &to_param, &to_value, &window},
|
||||
webrtc::field_trial::FindFullName(
|
||||
"WebRTC-NetworkCondition-EncoderSwitch"));
|
||||
|
||||
if (!codec_thresholds_string || !to_codec || !window) {
|
||||
return {};
|
||||
}
|
||||
|
||||
result.bitrate_filter.Reset(1.0 - 1.0 / *window);
|
||||
result.to_codec = *to_codec;
|
||||
result.to_param = to_param.GetOptional();
|
||||
result.to_value = to_value.GetOptional();
|
||||
|
||||
std::vector<std::string> codecs_thresholds;
|
||||
if (rtc::split(*codec_thresholds_string, '|', &codecs_thresholds) == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
for (const std::string& codec_threshold : codecs_thresholds) {
|
||||
std::vector<std::string> thresholds_split;
|
||||
if (rtc::split(codec_threshold, ';', &thresholds_split) != 3) {
|
||||
return {};
|
||||
}
|
||||
|
||||
VideoCodecType codec = PayloadStringToCodecType(thresholds_split[0]);
|
||||
int bitrate_kbps;
|
||||
rtc::FromString(thresholds_split[1], &bitrate_kbps);
|
||||
int pixel_count;
|
||||
rtc::FromString(thresholds_split[2], &pixel_count);
|
||||
|
||||
if (bitrate_kbps > 0) {
|
||||
result.codec_thresholds[codec].bitrate = DataRate::kbps(bitrate_kbps);
|
||||
}
|
||||
|
||||
if (pixel_count > 0) {
|
||||
result.codec_thresholds[codec].pixel_count = pixel_count;
|
||||
}
|
||||
|
||||
if (!result.codec_thresholds[codec].bitrate &&
|
||||
!result.codec_thresholds[codec].pixel_count) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
rtc::StringBuilder ss;
|
||||
ss << "Successfully parsed WebRTC-NetworkCondition-EncoderSwitch field "
|
||||
"trial."
|
||||
<< " to_codec:" << result.to_codec
|
||||
<< " to_param:" << result.to_param.value_or("<none>")
|
||||
<< " to_value:" << result.to_value.value_or("<none>")
|
||||
<< " codec_thresholds:";
|
||||
|
||||
for (auto kv : result.codec_thresholds) {
|
||||
std::string codec_name = CodecTypeToPayloadString(kv.first);
|
||||
std::string bitrate = kv.second.bitrate
|
||||
? std::to_string(kv.second.bitrate->kbps())
|
||||
: "<none>";
|
||||
std::string pixels = kv.second.pixel_count
|
||||
? std::to_string(*kv.second.pixel_count)
|
||||
: "<none>";
|
||||
ss << " (" << codec_name << ":" << bitrate << ":" << pixels << ")";
|
||||
}
|
||||
|
||||
RTC_LOG(LS_INFO) << ss.str();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/video/video_bitrate_allocator.h"
|
||||
#include "api/video/video_rotation.h"
|
||||
#include "api/video/video_sink_interface.h"
|
||||
@ -32,6 +33,7 @@
|
||||
#include "rtc_base/experiments/balanced_degradation_settings.h"
|
||||
#include "rtc_base/experiments/quality_scaler_settings.h"
|
||||
#include "rtc_base/experiments/rate_control_settings.h"
|
||||
#include "rtc_base/numerics/exp_filter.h"
|
||||
#include "rtc_base/race_checker.h"
|
||||
#include "rtc_base/rate_statistics.h"
|
||||
#include "rtc_base/synchronization/sequence_checker.h"
|
||||
@ -390,6 +392,42 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface,
|
||||
// destroyed first to make sure no tasks are run that use other members.
|
||||
rtc::TaskQueue encoder_queue_;
|
||||
|
||||
struct EncoderSwitchExperiment {
|
||||
struct Thresholds {
|
||||
absl::optional<DataRate> bitrate;
|
||||
absl::optional<int> pixel_count;
|
||||
};
|
||||
|
||||
// Codec --> switching thresholds
|
||||
std::map<VideoCodecType, Thresholds> codec_thresholds;
|
||||
|
||||
// To smooth out the target bitrate so that we don't trigger a switch
|
||||
// too easily.
|
||||
rtc::ExpFilter bitrate_filter{1.0};
|
||||
|
||||
// Codec/implementation to switch to
|
||||
std::string to_codec;
|
||||
absl::optional<std::string> to_param;
|
||||
absl::optional<std::string> to_value;
|
||||
|
||||
// Thresholds for the currently used codecs.
|
||||
Thresholds current_thresholds;
|
||||
|
||||
// Updates the |bitrate_filter|, so not const.
|
||||
bool IsBitrateBelowThreshold(const DataRate& target_bitrate);
|
||||
bool IsPixelCountBelowThreshold(int pixel_count) const;
|
||||
void SetCodec(VideoCodecType codec);
|
||||
};
|
||||
|
||||
EncoderSwitchExperiment ParseEncoderSwitchFieldTrial() const;
|
||||
|
||||
EncoderSwitchExperiment encoder_switch_experiment_
|
||||
RTC_GUARDED_BY(&encoder_queue_);
|
||||
|
||||
// An encoder switch is only requested once, this variable is used to keep
|
||||
// track of whether a request has been made or not.
|
||||
bool encoder_switch_requested_ RTC_GUARDED_BY(&encoder_queue_);
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(VideoStreamEncoder);
|
||||
};
|
||||
|
||||
|
||||
@ -49,6 +49,9 @@ namespace webrtc {
|
||||
|
||||
using ScaleReason = AdaptationObserverInterface::AdaptReason;
|
||||
using ::testing::_;
|
||||
using ::testing::AllOf;
|
||||
using ::testing::Field;
|
||||
using ::testing::StrictMock;
|
||||
|
||||
namespace {
|
||||
const int kMinPixelsPerFrame = 320 * 180;
|
||||
@ -4787,4 +4790,89 @@ TEST_F(VideoStreamEncoderTest, BandwidthAllocationLowerBound) {
|
||||
video_stream_encoder_->Stop();
|
||||
}
|
||||
|
||||
struct MockEncoderSwitchRequestCallback : public EncoderSwitchRequestCallback {
|
||||
MOCK_METHOD0(RequestEncoderFallback, void());
|
||||
MOCK_METHOD1(RequestEncoderSwitch, void(const Config& conf));
|
||||
};
|
||||
|
||||
TEST_F(VideoStreamEncoderTest, BitrateEncoderSwitch) {
|
||||
constexpr int kDontCare = 100;
|
||||
|
||||
StrictMock<MockEncoderSwitchRequestCallback> switch_callback;
|
||||
video_send_config_.encoder_settings.encoder_switch_request_callback =
|
||||
&switch_callback;
|
||||
VideoEncoderConfig encoder_config = video_encoder_config_.Copy();
|
||||
encoder_config.codec_type = kVideoCodecVP8;
|
||||
webrtc::test::ScopedFieldTrials field_trial(
|
||||
"WebRTC-NetworkCondition-EncoderSwitch/"
|
||||
"codec_thresholds:VP8;100;-1|H264;-1;30000,"
|
||||
"to_codec:AV1,to_param:ping,to_value:pong,window:2.0/");
|
||||
|
||||
// Reset encoder for new configuration to take effect.
|
||||
ConfigureEncoder(std::move(encoder_config));
|
||||
|
||||
// Send one frame to trigger ReconfigureEncoder.
|
||||
video_source_.IncomingCapturedFrame(
|
||||
CreateFrame(kDontCare, kDontCare, kDontCare));
|
||||
|
||||
using Config = EncoderSwitchRequestCallback::Config;
|
||||
EXPECT_CALL(switch_callback,
|
||||
RequestEncoderSwitch(AllOf(Field(&Config::codec_name, "AV1"),
|
||||
Field(&Config::param, "ping"),
|
||||
Field(&Config::value, "pong"))));
|
||||
|
||||
video_stream_encoder_->OnBitrateUpdated(
|
||||
/*target_bitrate=*/DataRate::kbps(50),
|
||||
/*stable_target_bitrate=*/DataRate::kbps(kDontCare),
|
||||
/*link_allocation=*/DataRate::kbps(kDontCare),
|
||||
/*fraction_lost=*/0,
|
||||
/*rtt_ms=*/0);
|
||||
|
||||
video_stream_encoder_->Stop();
|
||||
}
|
||||
|
||||
TEST_F(VideoStreamEncoderTest, ResolutionEncoderSwitch) {
|
||||
constexpr int kSufficientBitrateToNotDrop = 1000;
|
||||
constexpr int kHighRes = 500;
|
||||
constexpr int kLowRes = 100;
|
||||
|
||||
StrictMock<MockEncoderSwitchRequestCallback> switch_callback;
|
||||
video_send_config_.encoder_settings.encoder_switch_request_callback =
|
||||
&switch_callback;
|
||||
webrtc::test::ScopedFieldTrials field_trial(
|
||||
"WebRTC-NetworkCondition-EncoderSwitch/"
|
||||
"codec_thresholds:VP8;120;-1|H264;-1;30000,"
|
||||
"to_codec:AV1,to_param:ping,to_value:pong,window:2.0/");
|
||||
VideoEncoderConfig encoder_config = video_encoder_config_.Copy();
|
||||
encoder_config.codec_type = kVideoCodecH264;
|
||||
|
||||
// Reset encoder for new configuration to take effect.
|
||||
ConfigureEncoder(std::move(encoder_config));
|
||||
|
||||
// The VideoStreamEncoder needs some bitrate before it can start encoding,
|
||||
// setting some bitrate so that subsequent calls to WaitForEncodedFrame does
|
||||
// not fail.
|
||||
video_stream_encoder_->OnBitrateUpdated(
|
||||
/*target_bitrate=*/DataRate::kbps(kSufficientBitrateToNotDrop),
|
||||
/*stable_target_bitrate=*/DataRate::kbps(kSufficientBitrateToNotDrop),
|
||||
/*link_allocation=*/DataRate::kbps(kSufficientBitrateToNotDrop),
|
||||
/*fraction_lost=*/0,
|
||||
/*rtt_ms=*/0);
|
||||
|
||||
// Send one frame to trigger ReconfigureEncoder.
|
||||
video_source_.IncomingCapturedFrame(CreateFrame(1, kHighRes, kHighRes));
|
||||
WaitForEncodedFrame(1);
|
||||
|
||||
using Config = EncoderSwitchRequestCallback::Config;
|
||||
EXPECT_CALL(switch_callback,
|
||||
RequestEncoderSwitch(AllOf(Field(&Config::codec_name, "AV1"),
|
||||
Field(&Config::param, "ping"),
|
||||
Field(&Config::value, "pong"))));
|
||||
|
||||
video_source_.IncomingCapturedFrame(CreateFrame(2, kLowRes, kLowRes));
|
||||
WaitForEncodedFrame(2);
|
||||
|
||||
video_stream_encoder_->Stop();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user