diff options
Diffstat (limited to 'third_party/libwebrtc/media/engine/simulcast_encoder_adapter.cc')
-rw-r--r-- | third_party/libwebrtc/media/engine/simulcast_encoder_adapter.cc | 946 |
1 files changed, 946 insertions, 0 deletions
diff --git a/third_party/libwebrtc/media/engine/simulcast_encoder_adapter.cc b/third_party/libwebrtc/media/engine/simulcast_encoder_adapter.cc new file mode 100644 index 0000000000..4a53b8027c --- /dev/null +++ b/third_party/libwebrtc/media/engine/simulcast_encoder_adapter.cc @@ -0,0 +1,946 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "media/engine/simulcast_encoder_adapter.h" + +#include <stdio.h> +#include <string.h> + +#include <algorithm> +#include <cstdint> +#include <string> +#include <utility> + +#include "absl/algorithm/container.h" +#include "api/scoped_refptr.h" +#include "api/video/i420_buffer.h" +#include "api/video/video_codec_constants.h" +#include "api/video/video_frame_buffer.h" +#include "api/video/video_rotation.h" +#include "api/video_codecs/video_encoder.h" +#include "api/video_codecs/video_encoder_factory.h" +#include "api/video_codecs/video_encoder_software_fallback_wrapper.h" +#include "media/base/video_common.h" +#include "modules/video_coding/include/video_error_codes.h" +#include "modules/video_coding/utility/simulcast_rate_allocator.h" +#include "rtc_base/checks.h" +#include "rtc_base/experiments/rate_control_settings.h" +#include "rtc_base/logging.h" +#include "system_wrappers/include/field_trial.h" + +namespace { + +const unsigned int kDefaultMinQp = 2; +const unsigned int kDefaultMaxQp = 56; +// Max qp for lowest spatial resolution when doing simulcast. +const unsigned int kLowestResMaxQp = 45; + +absl::optional<unsigned int> GetScreenshareBoostedQpValue() { + std::string experiment_group = + webrtc::field_trial::FindFullName("WebRTC-BoostedScreenshareQp"); + unsigned int qp; + if (sscanf(experiment_group.c_str(), "%u", &qp) != 1) + return absl::nullopt; + qp = std::min(qp, 63u); + qp = std::max(qp, 1u); + return qp; +} + +uint32_t SumStreamMaxBitrate(int streams, const webrtc::VideoCodec& codec) { + uint32_t bitrate_sum = 0; + for (int i = 0; i < streams; ++i) { + bitrate_sum += codec.simulcastStream[i].maxBitrate; + } + return bitrate_sum; +} + +int CountAllStreams(const webrtc::VideoCodec& codec) { + int total_streams_count = + codec.numberOfSimulcastStreams < 1 ? 1 : codec.numberOfSimulcastStreams; + uint32_t simulcast_max_bitrate = + SumStreamMaxBitrate(total_streams_count, codec); + if (simulcast_max_bitrate == 0) { + total_streams_count = 1; + } + return total_streams_count; +} + +int CountActiveStreams(const webrtc::VideoCodec& codec) { + if (codec.numberOfSimulcastStreams < 1) { + return 1; + } + int total_streams_count = CountAllStreams(codec); + int active_streams_count = 0; + for (int i = 0; i < total_streams_count; ++i) { + if (codec.simulcastStream[i].active) { + ++active_streams_count; + } + } + return active_streams_count; +} + +int VerifyCodec(const webrtc::VideoCodec* inst) { + if (inst == nullptr) { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + if (inst->maxFramerate < 1) { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + // allow zero to represent an unspecified maxBitRate + if (inst->maxBitrate > 0 && inst->startBitrate > inst->maxBitrate) { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + if (inst->width <= 1 || inst->height <= 1) { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + if (inst->codecType == webrtc::kVideoCodecVP8 && + inst->VP8().automaticResizeOn && CountActiveStreams(*inst) > 1) { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + return WEBRTC_VIDEO_CODEC_OK; +} + +bool StreamQualityCompare(const webrtc::SimulcastStream& a, + const webrtc::SimulcastStream& b) { + return std::tie(a.height, a.width, a.maxBitrate, a.maxFramerate) < + std::tie(b.height, b.width, b.maxBitrate, b.maxFramerate); +} + +void GetLowestAndHighestQualityStreamIndixes( + rtc::ArrayView<webrtc::SimulcastStream> streams, + int* lowest_quality_stream_idx, + int* highest_quality_stream_idx) { + const auto lowest_highest_quality_streams = + absl::c_minmax_element(streams, StreamQualityCompare); + *lowest_quality_stream_idx = + std::distance(streams.begin(), lowest_highest_quality_streams.first); + *highest_quality_stream_idx = + std::distance(streams.begin(), lowest_highest_quality_streams.second); +} + +std::vector<uint32_t> GetStreamStartBitratesKbps( + const webrtc::VideoCodec& codec) { + std::vector<uint32_t> start_bitrates; + std::unique_ptr<webrtc::VideoBitrateAllocator> rate_allocator = + std::make_unique<webrtc::SimulcastRateAllocator>(codec); + webrtc::VideoBitrateAllocation allocation = + rate_allocator->Allocate(webrtc::VideoBitrateAllocationParameters( + codec.startBitrate * 1000, codec.maxFramerate)); + + int total_streams_count = CountAllStreams(codec); + for (int i = 0; i < total_streams_count; ++i) { + uint32_t stream_bitrate = allocation.GetSpatialLayerSum(i) / 1000; + start_bitrates.push_back(stream_bitrate); + } + return start_bitrates; +} + +} // namespace + +namespace webrtc { + +SimulcastEncoderAdapter::EncoderContext::EncoderContext( + std::unique_ptr<VideoEncoder> encoder, + bool prefer_temporal_support, + VideoEncoder::EncoderInfo primary_info, + VideoEncoder::EncoderInfo fallback_info) + : encoder_(std::move(encoder)), + prefer_temporal_support_(prefer_temporal_support), + primary_info_(std::move(primary_info)), + fallback_info_(std::move(fallback_info)) {} + +void SimulcastEncoderAdapter::EncoderContext::Release() { + if (encoder_) { + encoder_->Release(); + encoder_->RegisterEncodeCompleteCallback(nullptr); + } +} + +SimulcastEncoderAdapter::StreamContext::StreamContext( + SimulcastEncoderAdapter* parent, + std::unique_ptr<EncoderContext> encoder_context, + std::unique_ptr<FramerateController> framerate_controller, + int stream_idx, + uint16_t width, + uint16_t height, + bool is_paused) + : parent_(parent), + encoder_context_(std::move(encoder_context)), + framerate_controller_(std::move(framerate_controller)), + stream_idx_(stream_idx), + width_(width), + height_(height), + is_keyframe_needed_(false), + is_paused_(is_paused) { + if (parent_) { + encoder_context_->encoder().RegisterEncodeCompleteCallback(this); + } +} + +SimulcastEncoderAdapter::StreamContext::StreamContext(StreamContext&& rhs) + : parent_(rhs.parent_), + encoder_context_(std::move(rhs.encoder_context_)), + framerate_controller_(std::move(rhs.framerate_controller_)), + stream_idx_(rhs.stream_idx_), + width_(rhs.width_), + height_(rhs.height_), + is_keyframe_needed_(rhs.is_keyframe_needed_), + is_paused_(rhs.is_paused_) { + if (parent_) { + encoder_context_->encoder().RegisterEncodeCompleteCallback(this); + } +} + +SimulcastEncoderAdapter::StreamContext::~StreamContext() { + if (encoder_context_) { + encoder_context_->Release(); + } +} + +std::unique_ptr<SimulcastEncoderAdapter::EncoderContext> +SimulcastEncoderAdapter::StreamContext::ReleaseEncoderContext() && { + encoder_context_->Release(); + return std::move(encoder_context_); +} + +void SimulcastEncoderAdapter::StreamContext::OnKeyframe(Timestamp timestamp) { + is_keyframe_needed_ = false; + if (framerate_controller_) { + framerate_controller_->KeepFrame(timestamp.us() * 1000); + } +} + +bool SimulcastEncoderAdapter::StreamContext::ShouldDropFrame( + Timestamp timestamp) { + if (!framerate_controller_) { + return false; + } + return framerate_controller_->ShouldDropFrame(timestamp.us() * 1000); +} + +EncodedImageCallback::Result +SimulcastEncoderAdapter::StreamContext::OnEncodedImage( + const EncodedImage& encoded_image, + const CodecSpecificInfo* codec_specific_info) { + RTC_CHECK(parent_); // If null, this method should never be called. + return parent_->OnEncodedImage(stream_idx_, encoded_image, + codec_specific_info); +} + +void SimulcastEncoderAdapter::StreamContext::OnDroppedFrame( + DropReason /*reason*/) { + RTC_CHECK(parent_); // If null, this method should never be called. + parent_->OnDroppedFrame(stream_idx_); +} + +SimulcastEncoderAdapter::SimulcastEncoderAdapter(VideoEncoderFactory* factory, + const SdpVideoFormat& format) + : SimulcastEncoderAdapter(factory, nullptr, format) {} + +SimulcastEncoderAdapter::SimulcastEncoderAdapter( + VideoEncoderFactory* primary_factory, + VideoEncoderFactory* fallback_factory, + const SdpVideoFormat& format) + : inited_(0), + primary_encoder_factory_(primary_factory), + fallback_encoder_factory_(fallback_factory), + video_format_(format), + total_streams_count_(0), + bypass_mode_(false), + encoded_complete_callback_(nullptr), + experimental_boosted_screenshare_qp_(GetScreenshareBoostedQpValue()), + boost_base_layer_quality_(RateControlSettings::ParseFromFieldTrials() + .Vp8BoostBaseLayerQuality()), + prefer_temporal_support_on_base_layer_(field_trial::IsEnabled( + "WebRTC-Video-PreferTemporalSupportOnBaseLayer")) { + RTC_DCHECK(primary_factory); + + // The adapter is typically created on the worker thread, but operated on + // the encoder task queue. + encoder_queue_.Detach(); +} + +SimulcastEncoderAdapter::~SimulcastEncoderAdapter() { + RTC_DCHECK(!Initialized()); + DestroyStoredEncoders(); +} + +void SimulcastEncoderAdapter::SetFecControllerOverride( + FecControllerOverride* /*fec_controller_override*/) { + // Ignored. +} + +int SimulcastEncoderAdapter::Release() { + RTC_DCHECK_RUN_ON(&encoder_queue_); + + while (!stream_contexts_.empty()) { + // Move the encoder instances and put it on the `cached_encoder_contexts_` + // where it may possibly be reused from (ordering does not matter). + cached_encoder_contexts_.push_front( + std::move(stream_contexts_.back()).ReleaseEncoderContext()); + stream_contexts_.pop_back(); + } + + bypass_mode_ = false; + + // It's legal to move the encoder to another queue now. + encoder_queue_.Detach(); + + inited_.store(0); + + return WEBRTC_VIDEO_CODEC_OK; +} + +int SimulcastEncoderAdapter::InitEncode( + const VideoCodec* inst, + const VideoEncoder::Settings& settings) { + RTC_DCHECK_RUN_ON(&encoder_queue_); + + if (settings.number_of_cores < 1) { + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + + int ret = VerifyCodec(inst); + if (ret < 0) { + return ret; + } + + Release(); + + codec_ = *inst; + total_streams_count_ = CountAllStreams(*inst); + + // TODO(ronghuawu): Remove once this is handled in LibvpxVp8Encoder. + if (codec_.qpMax < kDefaultMinQp) { + codec_.qpMax = kDefaultMaxQp; + } + + bool is_legacy_singlecast = codec_.numberOfSimulcastStreams == 0; + int lowest_quality_stream_idx = 0; + int highest_quality_stream_idx = 0; + if (!is_legacy_singlecast) { + GetLowestAndHighestQualityStreamIndixes( + rtc::ArrayView<SimulcastStream>(codec_.simulcastStream, + total_streams_count_), + &lowest_quality_stream_idx, &highest_quality_stream_idx); + } + + std::unique_ptr<EncoderContext> encoder_context = FetchOrCreateEncoderContext( + /*is_lowest_quality_stream=*/( + is_legacy_singlecast || + codec_.simulcastStream[lowest_quality_stream_idx].active)); + if (encoder_context == nullptr) { + return WEBRTC_VIDEO_CODEC_MEMORY; + } + + // Two distinct scenarios: + // * Singlecast (total_streams_count == 1) or simulcast with simulcast-capable + // underlaying encoder implementation if active_streams_count > 1. SEA + // operates in bypass mode: original settings are passed to the underlaying + // encoder, frame encode complete callback is not intercepted. + // * Multi-encoder simulcast or singlecast if layers are deactivated + // (active_streams_count >= 1). SEA creates N=active_streams_count encoders + // and configures each to produce a single stream. + + int active_streams_count = CountActiveStreams(*inst); + // If we only have a single active layer it is better to create an encoder + // with only one configured layer than creating it with all-but-one disabled + // layers because that way we control scaling. + bool separate_encoders_needed = + !encoder_context->encoder().GetEncoderInfo().supports_simulcast || + active_streams_count == 1; + // Singlecast or simulcast with simulcast-capable underlaying encoder. + if (total_streams_count_ == 1 || !separate_encoders_needed) { + int ret = encoder_context->encoder().InitEncode(&codec_, settings); + if (ret >= 0) { + stream_contexts_.emplace_back( + /*parent=*/nullptr, std::move(encoder_context), + /*framerate_controller=*/nullptr, /*stream_idx=*/0, codec_.width, + codec_.height, /*is_paused=*/active_streams_count == 0); + bypass_mode_ = true; + + DestroyStoredEncoders(); + inited_.store(1); + return WEBRTC_VIDEO_CODEC_OK; + } + + encoder_context->Release(); + if (total_streams_count_ == 1) { + // Failed to initialize singlecast encoder. + return ret; + } + } + + // Multi-encoder simulcast or singlecast (deactivated layers). + std::vector<uint32_t> stream_start_bitrate_kbps = + GetStreamStartBitratesKbps(codec_); + + for (int stream_idx = 0; stream_idx < total_streams_count_; ++stream_idx) { + if (!is_legacy_singlecast && !codec_.simulcastStream[stream_idx].active) { + continue; + } + + if (encoder_context == nullptr) { + encoder_context = FetchOrCreateEncoderContext( + /*is_lowest_quality_stream=*/stream_idx == lowest_quality_stream_idx); + } + if (encoder_context == nullptr) { + Release(); + return WEBRTC_VIDEO_CODEC_MEMORY; + } + + VideoCodec stream_codec = MakeStreamCodec( + codec_, stream_idx, stream_start_bitrate_kbps[stream_idx], + /*is_lowest_quality_stream=*/stream_idx == lowest_quality_stream_idx, + /*is_highest_quality_stream=*/stream_idx == highest_quality_stream_idx); + + int ret = encoder_context->encoder().InitEncode(&stream_codec, settings); + if (ret < 0) { + encoder_context.reset(); + Release(); + return ret; + } + + // Intercept frame encode complete callback only for upper streams, where + // we need to set a correct stream index. Set `parent` to nullptr for the + // lowest stream to bypass the callback. + SimulcastEncoderAdapter* parent = stream_idx > 0 ? this : nullptr; + + bool is_paused = stream_start_bitrate_kbps[stream_idx] == 0; + stream_contexts_.emplace_back( + parent, std::move(encoder_context), + std::make_unique<FramerateController>(stream_codec.maxFramerate), + stream_idx, stream_codec.width, stream_codec.height, is_paused); + } + + // To save memory, don't store encoders that we don't use. + DestroyStoredEncoders(); + + inited_.store(1); + return WEBRTC_VIDEO_CODEC_OK; +} + +int SimulcastEncoderAdapter::Encode( + const VideoFrame& input_image, + const std::vector<VideoFrameType>* frame_types) { + RTC_DCHECK_RUN_ON(&encoder_queue_); + + if (!Initialized()) { + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + if (encoded_complete_callback_ == nullptr) { + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + + if (encoder_info_override_.requested_resolution_alignment()) { + const int alignment = + *encoder_info_override_.requested_resolution_alignment(); + if (input_image.width() % alignment != 0 || + input_image.height() % alignment != 0) { + RTC_LOG(LS_WARNING) << "Frame " << input_image.width() << "x" + << input_image.height() << " not divisible by " + << alignment; + return WEBRTC_VIDEO_CODEC_ERROR; + } + if (encoder_info_override_.apply_alignment_to_all_simulcast_layers()) { + for (const auto& layer : stream_contexts_) { + if (layer.width() % alignment != 0 || layer.height() % alignment != 0) { + RTC_LOG(LS_WARNING) + << "Codec " << layer.width() << "x" << layer.height() + << " not divisible by " << alignment; + return WEBRTC_VIDEO_CODEC_ERROR; + } + } + } + } + + // All active streams should generate a key frame if + // a key frame is requested by any stream. + bool is_keyframe_needed = false; + if (frame_types) { + for (const auto& frame_type : *frame_types) { + if (frame_type == VideoFrameType::kVideoFrameKey) { + is_keyframe_needed = true; + break; + } + } + } + + if (!is_keyframe_needed) { + for (const auto& layer : stream_contexts_) { + if (layer.is_keyframe_needed()) { + is_keyframe_needed = true; + break; + } + } + } + + // Temporary thay may hold the result of texture to i420 buffer conversion. + rtc::scoped_refptr<VideoFrameBuffer> src_buffer; + int src_width = input_image.width(); + int src_height = input_image.height(); + + for (auto& layer : stream_contexts_) { + // Don't encode frames in resolutions that we don't intend to send. + if (layer.is_paused()) { + continue; + } + + // Convert timestamp from RTP 90kHz clock. + const Timestamp frame_timestamp = + Timestamp::Micros((1000 * input_image.timestamp()) / 90); + + // If adapter is passed through and only one sw encoder does simulcast, + // frame types for all streams should be passed to the encoder unchanged. + // Otherwise a single per-encoder frame type is passed. + std::vector<VideoFrameType> stream_frame_types( + bypass_mode_ ? total_streams_count_ : 1); + if (is_keyframe_needed) { + std::fill(stream_frame_types.begin(), stream_frame_types.end(), + VideoFrameType::kVideoFrameKey); + layer.OnKeyframe(frame_timestamp); + } else { + if (layer.ShouldDropFrame(frame_timestamp)) { + continue; + } + std::fill(stream_frame_types.begin(), stream_frame_types.end(), + VideoFrameType::kVideoFrameDelta); + } + + // If scaling isn't required, because the input resolution + // matches the destination or the input image is empty (e.g. + // a keyframe request for encoders with internal camera + // sources) or the source image has a native handle, pass the image on + // directly. Otherwise, we'll scale it to match what the encoder expects + // (below). + // For texture frames, the underlying encoder is expected to be able to + // correctly sample/scale the source texture. + // TODO(perkj): ensure that works going forward, and figure out how this + // affects webrtc:5683. + if ((layer.width() == src_width && layer.height() == src_height) || + (input_image.video_frame_buffer()->type() == + VideoFrameBuffer::Type::kNative && + layer.encoder().GetEncoderInfo().supports_native_handle)) { + int ret = layer.encoder().Encode(input_image, &stream_frame_types); + if (ret != WEBRTC_VIDEO_CODEC_OK) { + return ret; + } + } else { + if (src_buffer == nullptr) { + src_buffer = input_image.video_frame_buffer(); + } + rtc::scoped_refptr<VideoFrameBuffer> dst_buffer = + src_buffer->Scale(layer.width(), layer.height()); + if (!dst_buffer) { + RTC_LOG(LS_ERROR) << "Failed to scale video frame"; + return WEBRTC_VIDEO_CODEC_ENCODER_FAILURE; + } + + // UpdateRect is not propagated to lower simulcast layers currently. + // TODO(ilnik): Consider scaling UpdateRect together with the buffer. + VideoFrame frame(input_image); + frame.set_video_frame_buffer(dst_buffer); + frame.set_rotation(webrtc::kVideoRotation_0); + frame.set_update_rect( + VideoFrame::UpdateRect{0, 0, frame.width(), frame.height()}); + int ret = layer.encoder().Encode(frame, &stream_frame_types); + if (ret != WEBRTC_VIDEO_CODEC_OK) { + return ret; + } + } + } + + return WEBRTC_VIDEO_CODEC_OK; +} + +int SimulcastEncoderAdapter::RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) { + RTC_DCHECK_RUN_ON(&encoder_queue_); + encoded_complete_callback_ = callback; + if (!stream_contexts_.empty() && stream_contexts_.front().stream_idx() == 0) { + // Bypass frame encode complete callback for the lowest layer since there is + // no need to override frame's spatial index. + stream_contexts_.front().encoder().RegisterEncodeCompleteCallback(callback); + } + return WEBRTC_VIDEO_CODEC_OK; +} + +void SimulcastEncoderAdapter::SetRates( + const RateControlParameters& parameters) { + RTC_DCHECK_RUN_ON(&encoder_queue_); + + if (!Initialized()) { + RTC_LOG(LS_WARNING) << "SetRates while not initialized"; + return; + } + + if (parameters.framerate_fps < 1.0) { + RTC_LOG(LS_WARNING) << "Invalid framerate: " << parameters.framerate_fps; + return; + } + + codec_.maxFramerate = static_cast<uint32_t>(parameters.framerate_fps + 0.5); + + if (bypass_mode_) { + stream_contexts_.front().encoder().SetRates(parameters); + return; + } + + for (StreamContext& layer_context : stream_contexts_) { + int stream_idx = layer_context.stream_idx(); + uint32_t stream_bitrate_kbps = + parameters.bitrate.GetSpatialLayerSum(stream_idx) / 1000; + + // Need a key frame if we have not sent this stream before. + if (stream_bitrate_kbps > 0 && layer_context.is_paused()) { + layer_context.set_is_keyframe_needed(); + } + layer_context.set_is_paused(stream_bitrate_kbps == 0); + + // Slice the temporal layers out of the full allocation and pass it on to + // the encoder handling the current simulcast stream. + RateControlParameters stream_parameters = parameters; + stream_parameters.bitrate = VideoBitrateAllocation(); + for (int i = 0; i < kMaxTemporalStreams; ++i) { + if (parameters.bitrate.HasBitrate(stream_idx, i)) { + stream_parameters.bitrate.SetBitrate( + 0, i, parameters.bitrate.GetBitrate(stream_idx, i)); + } + } + + // Assign link allocation proportionally to spatial layer allocation. + if (!parameters.bandwidth_allocation.IsZero() && + parameters.bitrate.get_sum_bps() > 0) { + stream_parameters.bandwidth_allocation = + DataRate::BitsPerSec((parameters.bandwidth_allocation.bps() * + stream_parameters.bitrate.get_sum_bps()) / + parameters.bitrate.get_sum_bps()); + // Make sure we don't allocate bandwidth lower than target bitrate. + if (stream_parameters.bandwidth_allocation.bps() < + stream_parameters.bitrate.get_sum_bps()) { + stream_parameters.bandwidth_allocation = + DataRate::BitsPerSec(stream_parameters.bitrate.get_sum_bps()); + } + } + + stream_parameters.framerate_fps = std::min<double>( + parameters.framerate_fps, + layer_context.target_fps().value_or(parameters.framerate_fps)); + + layer_context.encoder().SetRates(stream_parameters); + } +} + +void SimulcastEncoderAdapter::OnPacketLossRateUpdate(float packet_loss_rate) { + for (auto& c : stream_contexts_) { + c.encoder().OnPacketLossRateUpdate(packet_loss_rate); + } +} + +void SimulcastEncoderAdapter::OnRttUpdate(int64_t rtt_ms) { + for (auto& c : stream_contexts_) { + c.encoder().OnRttUpdate(rtt_ms); + } +} + +void SimulcastEncoderAdapter::OnLossNotification( + const LossNotification& loss_notification) { + for (auto& c : stream_contexts_) { + c.encoder().OnLossNotification(loss_notification); + } +} + +// TODO(brandtr): Add task checker to this member function, when all encoder +// callbacks are coming in on the encoder queue. +EncodedImageCallback::Result SimulcastEncoderAdapter::OnEncodedImage( + size_t stream_idx, + const EncodedImage& encodedImage, + const CodecSpecificInfo* codecSpecificInfo) { + EncodedImage stream_image(encodedImage); + CodecSpecificInfo stream_codec_specific = *codecSpecificInfo; + + stream_image.SetSpatialIndex(stream_idx); + + return encoded_complete_callback_->OnEncodedImage(stream_image, + &stream_codec_specific); +} + +void SimulcastEncoderAdapter::OnDroppedFrame(size_t stream_idx) { + // Not yet implemented. +} + +bool SimulcastEncoderAdapter::Initialized() const { + return inited_.load() == 1; +} + +void SimulcastEncoderAdapter::DestroyStoredEncoders() { + while (!cached_encoder_contexts_.empty()) { + cached_encoder_contexts_.pop_back(); + } +} + +std::unique_ptr<SimulcastEncoderAdapter::EncoderContext> +SimulcastEncoderAdapter::FetchOrCreateEncoderContext( + bool is_lowest_quality_stream) const { + bool prefer_temporal_support = fallback_encoder_factory_ != nullptr && + is_lowest_quality_stream && + prefer_temporal_support_on_base_layer_; + + // Toggling of `prefer_temporal_support` requires encoder recreation. Find + // and reuse encoder with desired `prefer_temporal_support`. Otherwise, if + // there is no such encoder in the cache, create a new instance. + auto encoder_context_iter = + std::find_if(cached_encoder_contexts_.begin(), + cached_encoder_contexts_.end(), [&](auto& encoder_context) { + return encoder_context->prefer_temporal_support() == + prefer_temporal_support; + }); + + std::unique_ptr<SimulcastEncoderAdapter::EncoderContext> encoder_context; + if (encoder_context_iter != cached_encoder_contexts_.end()) { + encoder_context = std::move(*encoder_context_iter); + cached_encoder_contexts_.erase(encoder_context_iter); + } else { + std::unique_ptr<VideoEncoder> primary_encoder = + primary_encoder_factory_->CreateVideoEncoder(video_format_); + + std::unique_ptr<VideoEncoder> fallback_encoder; + if (fallback_encoder_factory_ != nullptr) { + fallback_encoder = + fallback_encoder_factory_->CreateVideoEncoder(video_format_); + } + + std::unique_ptr<VideoEncoder> encoder; + VideoEncoder::EncoderInfo primary_info; + VideoEncoder::EncoderInfo fallback_info; + + if (primary_encoder != nullptr) { + primary_info = primary_encoder->GetEncoderInfo(); + fallback_info = primary_info; + + if (fallback_encoder == nullptr) { + encoder = std::move(primary_encoder); + } else { + encoder = CreateVideoEncoderSoftwareFallbackWrapper( + std::move(fallback_encoder), std::move(primary_encoder), + prefer_temporal_support); + } + } else if (fallback_encoder != nullptr) { + RTC_LOG(LS_WARNING) << "Failed to create primary " << video_format_.name + << " encoder. Use fallback encoder."; + fallback_info = fallback_encoder->GetEncoderInfo(); + primary_info = fallback_info; + encoder = std::move(fallback_encoder); + } else { + RTC_LOG(LS_ERROR) << "Failed to create primary and fallback " + << video_format_.name << " encoders."; + return nullptr; + } + + encoder_context = std::make_unique<SimulcastEncoderAdapter::EncoderContext>( + std::move(encoder), prefer_temporal_support, primary_info, + fallback_info); + } + + encoder_context->encoder().RegisterEncodeCompleteCallback( + encoded_complete_callback_); + return encoder_context; +} + +webrtc::VideoCodec SimulcastEncoderAdapter::MakeStreamCodec( + const webrtc::VideoCodec& codec, + int stream_idx, + uint32_t start_bitrate_kbps, + bool is_lowest_quality_stream, + bool is_highest_quality_stream) { + webrtc::VideoCodec codec_params = codec; + const SimulcastStream& stream_params = codec.simulcastStream[stream_idx]; + + codec_params.numberOfSimulcastStreams = 0; + codec_params.width = stream_params.width; + codec_params.height = stream_params.height; + codec_params.maxBitrate = stream_params.maxBitrate; + codec_params.minBitrate = stream_params.minBitrate; + codec_params.maxFramerate = stream_params.maxFramerate; + codec_params.qpMax = stream_params.qpMax; + codec_params.active = stream_params.active; + // Settings that are based on stream/resolution. + if (is_lowest_quality_stream) { + // Settings for lowest spatial resolutions. + if (codec.mode == VideoCodecMode::kScreensharing) { + if (experimental_boosted_screenshare_qp_) { + codec_params.qpMax = *experimental_boosted_screenshare_qp_; + } + } else if (boost_base_layer_quality_) { + codec_params.qpMax = kLowestResMaxQp; + } + } + if (codec.codecType == webrtc::kVideoCodecVP8) { + codec_params.VP8()->numberOfTemporalLayers = + stream_params.numberOfTemporalLayers; + if (!is_highest_quality_stream) { + // For resolutions below CIF, set the codec `complexity` parameter to + // kComplexityHigher, which maps to cpu_used = -4. + int pixels_per_frame = codec_params.width * codec_params.height; + if (pixels_per_frame < 352 * 288) { + codec_params.SetVideoEncoderComplexity( + webrtc::VideoCodecComplexity::kComplexityHigher); + } + // Turn off denoising for all streams but the highest resolution. + codec_params.VP8()->denoisingOn = false; + } + } else if (codec.codecType == webrtc::kVideoCodecH264) { + codec_params.H264()->numberOfTemporalLayers = + stream_params.numberOfTemporalLayers; + } + + // Cap start bitrate to the min bitrate in order to avoid strange codec + // behavior. + codec_params.startBitrate = + std::max(stream_params.minBitrate, start_bitrate_kbps); + + // Legacy screenshare mode is only enabled for the first simulcast layer + codec_params.legacy_conference_mode = + codec.legacy_conference_mode && stream_idx == 0; + + return codec_params; +} + +void SimulcastEncoderAdapter::OverrideFromFieldTrial( + VideoEncoder::EncoderInfo* info) const { + if (encoder_info_override_.requested_resolution_alignment()) { + info->requested_resolution_alignment = cricket::LeastCommonMultiple( + info->requested_resolution_alignment, + *encoder_info_override_.requested_resolution_alignment()); + info->apply_alignment_to_all_simulcast_layers = + info->apply_alignment_to_all_simulcast_layers || + encoder_info_override_.apply_alignment_to_all_simulcast_layers(); + } + // Override resolution bitrate limits unless they're set already. + if (info->resolution_bitrate_limits.empty() && + !encoder_info_override_.resolution_bitrate_limits().empty()) { + info->resolution_bitrate_limits = + encoder_info_override_.resolution_bitrate_limits(); + } +} + +VideoEncoder::EncoderInfo SimulcastEncoderAdapter::GetEncoderInfo() const { + if (stream_contexts_.size() == 1) { + // Not using simulcast adapting functionality, just pass through. + VideoEncoder::EncoderInfo info = + stream_contexts_.front().encoder().GetEncoderInfo(); + OverrideFromFieldTrial(&info); + return info; + } + + VideoEncoder::EncoderInfo encoder_info; + encoder_info.implementation_name = "SimulcastEncoderAdapter"; + encoder_info.requested_resolution_alignment = 1; + encoder_info.apply_alignment_to_all_simulcast_layers = false; + encoder_info.supports_native_handle = true; + encoder_info.scaling_settings.thresholds = absl::nullopt; + + if (stream_contexts_.empty()) { + // GetEncoderInfo queried before InitEncode. Only alignment info is needed + // to be filled. + // Create one encoder and query it. + + std::unique_ptr<SimulcastEncoderAdapter::EncoderContext> encoder_context = + FetchOrCreateEncoderContext(/*is_lowest_quality_stream=*/true); + if (encoder_context == nullptr) { + return encoder_info; + } + + const VideoEncoder::EncoderInfo& primary_info = + encoder_context->PrimaryInfo(); + const VideoEncoder::EncoderInfo& fallback_info = + encoder_context->FallbackInfo(); + + encoder_info.requested_resolution_alignment = cricket::LeastCommonMultiple( + primary_info.requested_resolution_alignment, + fallback_info.requested_resolution_alignment); + + encoder_info.apply_alignment_to_all_simulcast_layers = + primary_info.apply_alignment_to_all_simulcast_layers || + fallback_info.apply_alignment_to_all_simulcast_layers; + + if (!primary_info.supports_simulcast || !fallback_info.supports_simulcast) { + encoder_info.apply_alignment_to_all_simulcast_layers = true; + } + + cached_encoder_contexts_.emplace_back(std::move(encoder_context)); + + OverrideFromFieldTrial(&encoder_info); + return encoder_info; + } + + encoder_info.scaling_settings = VideoEncoder::ScalingSettings::kOff; + + for (size_t i = 0; i < stream_contexts_.size(); ++i) { + VideoEncoder::EncoderInfo encoder_impl_info = + stream_contexts_[i].encoder().GetEncoderInfo(); + if (i == 0) { + // Encoder name indicates names of all sub-encoders. + encoder_info.implementation_name += " ("; + encoder_info.implementation_name += encoder_impl_info.implementation_name; + + encoder_info.supports_native_handle = + encoder_impl_info.supports_native_handle; + encoder_info.has_trusted_rate_controller = + encoder_impl_info.has_trusted_rate_controller; + encoder_info.is_hardware_accelerated = + encoder_impl_info.is_hardware_accelerated; + encoder_info.is_qp_trusted = encoder_impl_info.is_qp_trusted; + } else { + encoder_info.implementation_name += ", "; + encoder_info.implementation_name += encoder_impl_info.implementation_name; + + // Native handle supported if any encoder supports it. + encoder_info.supports_native_handle |= + encoder_impl_info.supports_native_handle; + + // Trusted rate controller only if all encoders have it. + encoder_info.has_trusted_rate_controller &= + encoder_impl_info.has_trusted_rate_controller; + + // Uses hardware support if any of the encoders uses it. + // For example, if we are having issues with down-scaling due to + // pipelining delay in HW encoders we need higher encoder usage + // thresholds in CPU adaptation. + encoder_info.is_hardware_accelerated |= + encoder_impl_info.is_hardware_accelerated; + + // Treat QP from frame/slice/tile header as average QP only if all + // encoders report it as average QP. + encoder_info.is_qp_trusted = + encoder_info.is_qp_trusted.value_or(true) && + encoder_impl_info.is_qp_trusted.value_or(true); + } + encoder_info.fps_allocation[i] = encoder_impl_info.fps_allocation[0]; + encoder_info.requested_resolution_alignment = cricket::LeastCommonMultiple( + encoder_info.requested_resolution_alignment, + encoder_impl_info.requested_resolution_alignment); + // request alignment on all layers if any of the encoders may need it, or + // if any non-top layer encoder requests a non-trivial alignment. + if (encoder_impl_info.apply_alignment_to_all_simulcast_layers || + (encoder_impl_info.requested_resolution_alignment > 1 && + (codec_.simulcastStream[i].height < codec_.height || + codec_.simulcastStream[i].width < codec_.width))) { + encoder_info.apply_alignment_to_all_simulcast_layers = true; + } + } + encoder_info.implementation_name += ")"; + + OverrideFromFieldTrial(&encoder_info); + + return encoder_info; +} + +} // namespace webrtc |