
NOTREACHED() and NOTREACHED_IN_MIGRATION() are both CHECK-fatal now. The former is [[noreturn]] so this CL also performs dead-code removal after the NOTREACHED(). This CL does not attempt to do additional rewrites of any surrounding code, like: if (!foo) { NOTREACHED(); } to CHECK(foo); Those transforms take a non-trivial amount of time (and there are thousands of instances). Cleanup can be left as an exercise for the reader. Bug: 40580068 Low-Coverage-Reason: OTHER Should-be-unreachable code Change-Id: I8cd87f774e5fec8ec893dcc7286b0979643ea627 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5873742 Reviewed-by: Joe Downing <joedow@chromium.org> Reviewed-by: Daniel Cheng <dcheng@chromium.org> Commit-Queue: Daniel Cheng <dcheng@chromium.org> Auto-Submit: Peter Boström <pbos@chromium.org> Cr-Commit-Position: refs/heads/main@{#1357911}
595 lines
23 KiB
C++
595 lines
23 KiB
C++
// Copyright 2021 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "remoting/protocol/webrtc_video_encoder_wrapper.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "base/functional/bind.h"
|
|
#include "base/logging.h"
|
|
#include "base/memory/ptr_util.h"
|
|
#include "base/strings/string_number_conversions.h"
|
|
#include "base/task/bind_post_task.h"
|
|
#include "base/task/sequenced_task_runner.h"
|
|
#include "base/task/single_thread_task_runner.h"
|
|
#include "base/time/time.h"
|
|
#include "build/build_config.h"
|
|
#include "remoting/base/constants.h"
|
|
#include "remoting/base/session_options.h"
|
|
#include "remoting/codec/webrtc_video_encoder_av1.h"
|
|
#include "remoting/codec/webrtc_video_encoder_vpx.h"
|
|
#include "remoting/protocol/video_stream_event_router.h"
|
|
#include "remoting/protocol/webrtc_video_frame_adapter.h"
|
|
#include "third_party/webrtc/api/video_codecs/av1_profile.h"
|
|
#include "third_party/webrtc/api/video_codecs/sdp_video_format.h"
|
|
#include "third_party/webrtc/api/video_codecs/vp9_profile.h"
|
|
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
|
|
#include "third_party/webrtc/modules/video_coding/include/video_codec_interface.h"
|
|
#include "third_party/webrtc/modules/video_coding/include/video_error_codes.h"
|
|
|
|
#if defined(USE_H264_ENCODER)
|
|
#include "remoting/codec/webrtc_video_encoder_gpu.h"
|
|
#endif
|
|
|
|
namespace remoting::protocol {
|
|
|
|
namespace {
|
|
|
|
// Maximum quantizer at which to encode frames. Lowering this value will
|
|
// improve image quality (in cases of low-bandwidth or large frames) at the
|
|
// cost of latency. Increasing the value will improve latency (in these cases)
|
|
// at the cost of image quality, resulting in longer top-off times.
|
|
const int kMaxQuantizer = 50;
|
|
|
|
// Minimum quantizer at which to encode frames. The value is chosen such that
|
|
// sending higher-quality (lower quantizer) frames would use up bandwidth
|
|
// without any appreciable gain in image quality.
|
|
const int kMinQuantizer = 10;
|
|
|
|
const int64_t kPixelsPerMegapixel = 1000000;
|
|
|
|
// Threshold in number of updated pixels used to detect "big" frames. These
|
|
// frames update significant portion of the screen compared to the preceding
|
|
// frames. For these frames min quantizer may need to be adjusted in order to
|
|
// ensure that they get delivered to the client as soon as possible, in exchange
|
|
// for lower-quality image.
|
|
const int kBigFrameThresholdPixels = 300000;
|
|
|
|
// Estimated size (in bytes per megapixel) of encoded frame at target quantizer
|
|
// value (see kTargetQuantizerForTopOff). Compression ratio varies depending
|
|
// on the image, so this is just a rough estimate. It's used to predict when
|
|
// encoded "big" frame may be too large to be delivered to the client quickly.
|
|
const int kEstimatedBytesPerMegapixel = 100000;
|
|
|
|
// Minimum interval between frames needed to keep the connection alive. The
|
|
// client will request a key-frame if it does not receive any frames for a
|
|
// 3-second period. This is effectively a minimum frame-rate, so the value
|
|
// should not be too small, otherwise the client may waste CPU cycles on
|
|
// processing and rendering lots of identical frames.
|
|
constexpr base::TimeDelta kKeepAliveInterval = base::Seconds(2);
|
|
|
|
// Used to clamp the calculated frame durations to a set of reasonable values.
|
|
constexpr auto kMinFrameDuration = base::Hertz(120);
|
|
constexpr auto kMaxFrameDuration = base::Hertz(15);
|
|
|
|
std::string EncodeResultToString(WebrtcVideoEncoder::EncodeResult result) {
|
|
using EncodeResult = WebrtcVideoEncoder::EncodeResult;
|
|
|
|
switch (result) {
|
|
case EncodeResult::SUCCEEDED:
|
|
return "Succeeded";
|
|
case EncodeResult::FRAME_SIZE_EXCEEDS_CAPABILITY:
|
|
return "Frame size exceeds capability";
|
|
case EncodeResult::UNKNOWN_ERROR:
|
|
return "Unknown error";
|
|
}
|
|
NOTREACHED();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
WebrtcVideoEncoderWrapper::WebrtcVideoEncoderWrapper(
|
|
const webrtc::SdpVideoFormat& format,
|
|
const SessionOptions& session_options,
|
|
scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
|
|
scoped_refptr<base::SingleThreadTaskRunner> encode_task_runner,
|
|
base::WeakPtr<VideoStreamEventRouter> video_stream_event_router)
|
|
: main_task_runner_(main_task_runner),
|
|
encode_task_runner_(encode_task_runner),
|
|
video_stream_event_router_(video_stream_event_router) {
|
|
codec_type_ = webrtc::PayloadStringToCodecType(format.name);
|
|
switch (codec_type_) {
|
|
case webrtc::kVideoCodecVP8:
|
|
VLOG(0) << "Creating VP8 encoder.";
|
|
encoder_ = WebrtcVideoEncoderVpx::CreateForVP8();
|
|
break;
|
|
case webrtc::kVideoCodecVP9: {
|
|
std::optional<webrtc::VP9Profile> sdp_profile =
|
|
webrtc::ParseSdpForVP9Profile(format.parameters);
|
|
auto profile = sdp_profile.value_or(webrtc::VP9Profile::kProfile0);
|
|
std::optional<int> speed = session_options.GetInt("Vp9-Encoder-Speed");
|
|
|
|
VLOG(0) << "Creating VP9 encoder - Profile: "
|
|
<< webrtc::VP9ProfileToString(profile) << ", Speed: "
|
|
<< (speed.has_value() ? base::NumberToString(*speed) : "default");
|
|
|
|
encoder_ = WebrtcVideoEncoderVpx::CreateForVP9();
|
|
// We use the Profile value in the SDP to indicate whether I444 color
|
|
// (aka lossless) should be used as profile 0 only supports I420.
|
|
encoder_->SetLosslessColor(profile == webrtc::VP9Profile::kProfile1);
|
|
if (speed.has_value()) {
|
|
encoder_->SetEncoderSpeed(*speed);
|
|
}
|
|
break;
|
|
}
|
|
case webrtc::kVideoCodecAV1: {
|
|
std::optional<webrtc::AV1Profile> sdp_profile =
|
|
webrtc::ParseSdpForAV1Profile(format.parameters);
|
|
auto profile = sdp_profile.value_or(webrtc::AV1Profile::kProfile0);
|
|
std::optional<bool> active_map =
|
|
session_options.GetBool("Av1-Active-Map");
|
|
std::optional<int> speed = session_options.GetInt("Av1-Encoder-Speed");
|
|
|
|
VLOG(0) << "Creating AV1 encoder - Profile: "
|
|
<< webrtc::AV1ProfileToString(profile) << ", Speed: "
|
|
<< (speed.has_value() ? base::NumberToString(*speed) : "default")
|
|
<< ", ActiveMap: "
|
|
<< (active_map.has_value() ? base::NumberToString(*active_map)
|
|
: "default");
|
|
|
|
encoder_ = std::make_unique<WebrtcVideoEncoderAV1>();
|
|
|
|
// We use the Profile value in the SDP to indicate whether I444 color
|
|
// (aka lossless) should be used as profile 0 only supports I420.
|
|
encoder_->SetLosslessColor(profile == webrtc::AV1Profile::kProfile1);
|
|
if (speed.has_value()) {
|
|
encoder_->SetEncoderSpeed(*speed);
|
|
}
|
|
if (active_map.has_value()) {
|
|
encoder_->SetUseActiveMap(*active_map);
|
|
}
|
|
break;
|
|
}
|
|
case webrtc::kVideoCodecH264:
|
|
#if defined(USE_H264_ENCODER)
|
|
VLOG(0) << "Creating H264 encoder.";
|
|
encoder_ = WebrtcVideoEncoderGpu::CreateForH264();
|
|
#else
|
|
NOTIMPLEMENTED();
|
|
#endif
|
|
break;
|
|
default:
|
|
LOG(FATAL) << "Unknown codec type: " << codec_type_;
|
|
}
|
|
}
|
|
|
|
WebrtcVideoEncoderWrapper::~WebrtcVideoEncoderWrapper() {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
if (encode_pending_) {
|
|
// If the encoder is still running, then delete it on |encode_task_runner_|
|
|
// as it will no longer be called on this sequence and isn't sequence bound.
|
|
encode_task_runner_->DeleteSoon(FROM_HERE, encoder_.release());
|
|
}
|
|
}
|
|
|
|
void WebrtcVideoEncoderWrapper::SetEncoderForTest(
|
|
std::unique_ptr<WebrtcVideoEncoder> encoder) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
encoder_ = std::move(encoder);
|
|
}
|
|
|
|
int32_t WebrtcVideoEncoderWrapper::InitEncode(
|
|
const webrtc::VideoCodec* codec_settings,
|
|
const webrtc::VideoEncoder::Settings& settings) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
DCHECK(codec_settings);
|
|
DCHECK_EQ(codec_settings->codecType, codec_type_);
|
|
|
|
// Validate request is to support a single stream.
|
|
DCHECK_EQ(1, codec_settings->numberOfSimulcastStreams);
|
|
|
|
if (codec_type_ == webrtc::kVideoCodecVP9) {
|
|
// SVC is not supported.
|
|
DCHECK_EQ(1, codec_settings->VP9().numberOfSpatialLayers);
|
|
}
|
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
}
|
|
|
|
int32_t WebrtcVideoEncoderWrapper::RegisterEncodeCompleteCallback(
|
|
webrtc::EncodedImageCallback* callback) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
encoded_callback_ = callback;
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
}
|
|
|
|
int32_t WebrtcVideoEncoderWrapper::Release() {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
encoded_callback_ = nullptr;
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
}
|
|
|
|
int32_t WebrtcVideoEncoderWrapper::Encode(
|
|
const webrtc::VideoFrame& frame,
|
|
const std::vector<webrtc::VideoFrameType>* frame_types) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
// Include all of the pre-processing steps in the total encode time.
|
|
auto encode_start = base::TimeTicks::Now();
|
|
|
|
// Calculate the frame interval before dropping or queueing frames.
|
|
base::Time frame_timestamp = base::Time::NowFromSystemTime();
|
|
if (!last_frame_received_timestamp_.is_null()) {
|
|
current_frame_interval_ = std::clamp(
|
|
base::TimeDelta(frame_timestamp - last_frame_received_timestamp_),
|
|
kMinFrameDuration, kMaxFrameDuration);
|
|
}
|
|
last_frame_received_timestamp_ = frame_timestamp;
|
|
|
|
// Simulcast is unsupported, so only the first vector element is needed.
|
|
bool key_frame_requested =
|
|
(frame_types && !frame_types->empty() &&
|
|
((*frame_types)[0] == webrtc::VideoFrameType::kVideoFrameKey));
|
|
if (key_frame_requested) {
|
|
pending_key_frame_request_ = true;
|
|
}
|
|
|
|
bool webrtc_dropped_frame = false;
|
|
if (next_frame_id_ != frame.id()) {
|
|
webrtc_dropped_frame = true;
|
|
next_frame_id_ = frame.id();
|
|
}
|
|
next_frame_id_++;
|
|
|
|
// WebRTC calls Encode() after each successful capture. If we drop the frame
|
|
// immediately when we are currently encoding instead of storing the frame
|
|
// data, then the encoder would need to wait until the next capture request
|
|
// has succeeded before it can encode another frame, this period can be
|
|
// several milliseconds or more. To reduce this latency, we store the new
|
|
// frame when the encoder is busy so it can be encoded immediately after the
|
|
// encoder finishes the current frame.
|
|
if (encode_pending_) {
|
|
if (pending_frame_) {
|
|
accumulated_update_rect_.Union(pending_frame_->update_rect());
|
|
|
|
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
|
|
FROM_HERE,
|
|
base::BindOnce(&WebrtcVideoEncoderWrapper::NotifyFrameDropped,
|
|
weak_factory_.GetWeakPtr()));
|
|
}
|
|
pending_frame_ = std::make_unique<webrtc::VideoFrame>(frame);
|
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
}
|
|
|
|
// Frames of type kNative are expected to have the adapter that was used to
|
|
// wrap the DesktopFrame, so the downcast should be safe.
|
|
if (frame.video_frame_buffer()->type() !=
|
|
webrtc::VideoFrameBuffer::Type::kNative) {
|
|
LOG(ERROR) << "Only kNative frames are supported.";
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
|
}
|
|
auto* video_frame_adapter =
|
|
static_cast<WebrtcVideoFrameAdapter*>(frame.video_frame_buffer().get());
|
|
|
|
// Store RTP timestamp and FrameStats so they can be added to the
|
|
// EncodedImage and EncodedFrame when encoding is complete.
|
|
rtp_timestamp_ = frame.rtp_timestamp();
|
|
frame_stats_ = video_frame_adapter->TakeFrameStats();
|
|
if (!frame_stats_) {
|
|
// This could happen if WebRTC tried to encode the same frame twice.
|
|
// Taking the frame-stats twice from the same frame-adapter would return
|
|
// nullptr the second time.
|
|
LOG(ERROR) << "Frame provided with missing frame-stats.";
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
|
}
|
|
|
|
if (!screen_id_.has_value()) {
|
|
// Save the screen_id from the first encoded frame, otherwise we won't know
|
|
// which screen_id this encoder is associated with due to the current WebRTC
|
|
// architecture.
|
|
screen_id_ = frame_stats_->screen_id;
|
|
}
|
|
|
|
frame_stats_->encode_started_time = encode_start;
|
|
|
|
auto desktop_frame = video_frame_adapter->TakeDesktopFrame();
|
|
|
|
// If any frames were dropped by WebRTC or by this class, the
|
|
// original DesktopFrame's updated-region should not be used as-is
|
|
// (because that region is the difference between this frame and the
|
|
// previous frame, which the encoder has not seen because it was dropped).
|
|
// In this case, the DesktopFrame's update-region should be set to the
|
|
// union of all the dropped frames' update-rectangles.
|
|
bool this_class_dropped_frame = !accumulated_update_rect_.IsEmpty();
|
|
if (webrtc_dropped_frame || this_class_dropped_frame) {
|
|
// Get the update-rect that WebRTC provides, which will include any
|
|
// accumulated updates from frames that WebRTC dropped.
|
|
auto update_rect = frame.update_rect();
|
|
|
|
// Combine it with any updates from frames dropped by this class.
|
|
update_rect.Union(accumulated_update_rect_);
|
|
|
|
// In case the new frame has a different resolution, ensure the update-rect
|
|
// is constrained by the frame's bounds. On the first frame with a new
|
|
// resolution, WebRTC sets the update-rect to the full area of the frame, so
|
|
// this line will give the correct result in that case. If the resolution
|
|
// did not change (for this frame or any prior dropped frames), the
|
|
// update-region will already be constrained by the resolution, so this line
|
|
// will be a no-op.
|
|
update_rect.Intersect(
|
|
webrtc::VideoFrame::UpdateRect{0, 0, frame.width(), frame.height()});
|
|
|
|
desktop_frame->mutable_updated_region()->SetRect(
|
|
webrtc::DesktopRect::MakeXYWH(update_rect.offset_x,
|
|
update_rect.offset_y, update_rect.width,
|
|
update_rect.height));
|
|
|
|
// The update-region has now been applied to the desktop_frame which is
|
|
// being sent to the encoder, so empty it here.
|
|
accumulated_update_rect_.MakeEmptyUpdate();
|
|
}
|
|
|
|
// Limit the encoding and sending of empty frames to |kKeepAliveInterval|.
|
|
// This is done to save on network bandwidth and CPU usage.
|
|
if (desktop_frame->updated_region().is_empty() && !top_off_active_ &&
|
|
!pending_key_frame_request_ &&
|
|
(encode_start - latest_frame_encode_start_time_ < kKeepAliveInterval)) {
|
|
// Drop the frame. There is no need to track the update-rect as the
|
|
// frame being dropped is empty.
|
|
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
|
|
FROM_HERE,
|
|
base::BindOnce(&WebrtcVideoEncoderWrapper::NotifyFrameDropped,
|
|
weak_factory_.GetWeakPtr()));
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
}
|
|
latest_frame_encode_start_time_ = encode_start;
|
|
|
|
WebrtcVideoEncoder::FrameParams frame_params;
|
|
|
|
// SetRates() must be called prior to Encode(), with a non-zero bitrate.
|
|
DCHECK_NE(0, bitrate_kbps_);
|
|
frame_params.bitrate_kbps = bitrate_kbps_;
|
|
frame_params.duration = current_frame_interval_;
|
|
frame_params.fps = current_frame_interval_.ToHz();
|
|
|
|
frame_params.vpx_min_quantizer =
|
|
ShouldDropQualityForLargeFrame(*desktop_frame) ? kMaxQuantizer
|
|
: kMinQuantizer;
|
|
frame_params.vpx_max_quantizer = kMaxQuantizer;
|
|
frame_params.clear_active_map = !top_off_active_;
|
|
|
|
frame_params.key_frame = pending_key_frame_request_;
|
|
pending_key_frame_request_ = false;
|
|
|
|
encode_pending_ = true;
|
|
|
|
auto encode_callback = base::BindPostTaskToCurrentDefault(base::BindOnce(
|
|
&WebrtcVideoEncoderWrapper::OnFrameEncoded, weak_factory_.GetWeakPtr()));
|
|
encode_task_runner_->PostTask(
|
|
FROM_HERE,
|
|
base::BindOnce(&WebrtcVideoEncoder::Encode,
|
|
base::Unretained(encoder_.get()), std::move(desktop_frame),
|
|
frame_params, std::move(encode_callback)));
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
}
|
|
|
|
void WebrtcVideoEncoderWrapper::SetRates(
|
|
const RateControlParameters& parameters) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
bitrate_kbps_ = parameters.bitrate.get_sum_kbps();
|
|
}
|
|
|
|
void WebrtcVideoEncoderWrapper::OnRttUpdate(int64_t rtt_ms) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
rtt_estimate_ = base::Milliseconds(rtt_ms);
|
|
}
|
|
|
|
webrtc::VideoEncoder::EncoderInfo WebrtcVideoEncoderWrapper::GetEncoderInfo()
|
|
const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
return EncoderInfo();
|
|
}
|
|
|
|
webrtc::EncodedImageCallback::Result
|
|
WebrtcVideoEncoderWrapper::ReturnEncodedFrame(
|
|
const WebrtcVideoEncoder::EncodedFrame& frame) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
// Non-null, because WebRTC registers a callback before calling Encode().
|
|
DCHECK(encoded_callback_);
|
|
|
|
webrtc::EncodedImage encoded_image;
|
|
encoded_image.SetEncodedData(frame.data);
|
|
encoded_image._encodedWidth = frame.dimensions.width();
|
|
encoded_image._encodedHeight = frame.dimensions.height();
|
|
encoded_image._frameType = frame.key_frame
|
|
? webrtc::VideoFrameType::kVideoFrameKey
|
|
: webrtc::VideoFrameType::kVideoFrameDelta;
|
|
encoded_image.SetRtpTimestamp(frame.rtp_timestamp);
|
|
encoded_image.SetPlayoutDelay(webrtc::VideoPlayoutDelay::Minimal());
|
|
encoded_image.content_type_ = webrtc::VideoContentType::SCREENSHARE;
|
|
|
|
webrtc::CodecSpecificInfo codec_specific_info;
|
|
codec_specific_info.codecType = frame.codec;
|
|
if (frame.codec == webrtc::kVideoCodecVP8) {
|
|
webrtc::CodecSpecificInfoVP8* vp8_info =
|
|
&codec_specific_info.codecSpecific.VP8;
|
|
vp8_info->temporalIdx = webrtc::kNoTemporalIdx;
|
|
} else if (frame.codec == webrtc::kVideoCodecVP9) {
|
|
webrtc::CodecSpecificInfoVP9* vp9_info =
|
|
&codec_specific_info.codecSpecific.VP9;
|
|
vp9_info->inter_pic_predicted = !frame.key_frame;
|
|
vp9_info->ss_data_available = frame.key_frame;
|
|
vp9_info->spatial_layer_resolution_present = frame.key_frame;
|
|
if (frame.key_frame) {
|
|
vp9_info->width[0] = frame.dimensions.width();
|
|
vp9_info->height[0] = frame.dimensions.height();
|
|
}
|
|
vp9_info->num_spatial_layers = 1;
|
|
vp9_info->gof_idx = webrtc::kNoGofIdx;
|
|
vp9_info->temporal_idx = webrtc::kNoTemporalIdx;
|
|
vp9_info->flexible_mode = false;
|
|
vp9_info->temporal_up_switch = true;
|
|
vp9_info->inter_layer_predicted = false;
|
|
vp9_info->first_frame_in_picture = true;
|
|
vp9_info->spatial_layer_resolution_present = false;
|
|
} else if (frame.codec == webrtc::kVideoCodecH264) {
|
|
#if defined(USE_H264_ENCODER)
|
|
webrtc::CodecSpecificInfoH264* h264_info =
|
|
&codec_specific_info.codecSpecific.H264;
|
|
h264_info->packetization_mode =
|
|
webrtc::H264PacketizationMode::NonInterleaved;
|
|
#else
|
|
NOTREACHED();
|
|
#endif
|
|
} else if (frame.codec == webrtc::kVideoCodecAV1) {
|
|
// TODO(joedow): Set codec specific params for AV1 here.
|
|
} else {
|
|
NOTREACHED();
|
|
}
|
|
|
|
return encoded_callback_->OnEncodedImage(encoded_image, &codec_specific_info);
|
|
}
|
|
|
|
void WebrtcVideoEncoderWrapper::OnFrameEncoded(
|
|
WebrtcVideoEncoder::EncodeResult encode_result,
|
|
std::unique_ptr<WebrtcVideoEncoder::EncodedFrame> encoded_frame) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
// Keep |encoded_frame| alive until frame-encoded/frame-sent notifications
|
|
// have executed on |main_task_runner_|.
|
|
std::unique_ptr<WebrtcVideoEncoder::EncodedFrame, base::OnTaskRunnerDeleter>
|
|
frame(encoded_frame.release(),
|
|
base::OnTaskRunnerDeleter(main_task_runner_));
|
|
|
|
DCHECK(encode_pending_);
|
|
encode_pending_ = false;
|
|
|
|
// Transfer the cached frame stats into the encoded frame.
|
|
if (frame) {
|
|
// This is non-null because the |encode_pending_| flag ensures that
|
|
// frame-encodings are serialized. So there cannot be 2 consecutive calls to
|
|
// this method without an intervening call to Encode() which sets
|
|
// |frame_stats_| to non-null.
|
|
DCHECK(frame_stats_);
|
|
frame_stats_->encode_ended_time = base::TimeTicks::Now();
|
|
frame_stats_->rtt_estimate = rtt_estimate_;
|
|
frame_stats_->bandwidth_estimate_kbps = bitrate_kbps_;
|
|
// WebrtcFrameSchedulerConstantRate cannot estimate this delay. Set it to 0
|
|
// so the client can still calculate the derived stats.
|
|
frame_stats_->send_pending_delay = base::TimeDelta();
|
|
frame->stats = std::move(frame_stats_);
|
|
|
|
frame->rtp_timestamp = rtp_timestamp_;
|
|
}
|
|
|
|
if (encode_result != WebrtcVideoEncoder::EncodeResult::SUCCEEDED) {
|
|
// TODO(crbug.com/40175068): Store this error and communicate it to WebRTC
|
|
// via the next call to Encode(). The VPX encoders are never expected to
|
|
// return any error, but hardware-decoders such as H264 may fail.
|
|
LOG(ERROR) << "Video encoder returned error "
|
|
<< EncodeResultToString(encode_result);
|
|
NotifyFrameDropped();
|
|
DropPendingFrame();
|
|
return;
|
|
}
|
|
|
|
if (!frame || !frame->data || !frame->data->size()) {
|
|
top_off_active_ = false;
|
|
NotifyFrameDropped();
|
|
DropPendingFrame();
|
|
return;
|
|
}
|
|
|
|
// Top-off until the best quantizer value is reached.
|
|
top_off_active_ = (frame->quantizer > kMinQuantizer);
|
|
|
|
// If there was a successful capture while the encoder was working then there
|
|
// will be a frame waiting to be encoded. Send it to the encoder now that its
|
|
// no longer busy and we've copied the frame stats for the current frame.
|
|
// Note: This function is called here instead of at the end of the function as
|
|
// this saves a few hundred microseconds per frame. It can certainly be moved
|
|
// if ever there is a need but be sure to profile the per-frame cost.
|
|
SchedulePendingFrame();
|
|
|
|
// WARNING: No frame-specific class members should be accessed after this
|
|
// point as they may be updated in Encode() when the pending frame is sent to
|
|
// the encoder.
|
|
|
|
webrtc::EncodedImageCallback::Result send_result = ReturnEncodedFrame(*frame);
|
|
|
|
// std::ref() is used here because base::BindOnce() would otherwise try to
|
|
// copy the referenced frame object, which is move-only. This is safe because
|
|
// base::OnTaskRunnerDeleter posts the frame-deleter task to run after this
|
|
// task has executed.
|
|
main_task_runner_->PostTask(
|
|
FROM_HERE, base::BindOnce(&VideoStreamEventRouter::OnEncodedFrameSent,
|
|
video_stream_event_router_, *screen_id_,
|
|
send_result, std::ref(*frame)));
|
|
}
|
|
|
|
void WebrtcVideoEncoderWrapper::NotifyFrameDropped() {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
DCHECK(encoded_callback_);
|
|
encoded_callback_->OnDroppedFrame(
|
|
webrtc::EncodedImageCallback::DropReason::kDroppedByEncoder);
|
|
}
|
|
|
|
bool WebrtcVideoEncoderWrapper::ShouldDropQualityForLargeFrame(
|
|
const webrtc::DesktopFrame& frame) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
if (codec_type_ != webrtc::kVideoCodecVP8) {
|
|
return false;
|
|
}
|
|
|
|
int64_t updated_area = 0;
|
|
for (webrtc::DesktopRegion::Iterator r(frame.updated_region()); !r.IsAtEnd();
|
|
r.Advance()) {
|
|
updated_area += r.rect().width() * r.rect().height();
|
|
}
|
|
|
|
bool should_drop_quality = false;
|
|
if (updated_area - updated_region_area_.Max() > kBigFrameThresholdPixels) {
|
|
int expected_frame_size =
|
|
updated_area * kEstimatedBytesPerMegapixel / kPixelsPerMegapixel;
|
|
base::TimeDelta expected_send_delay =
|
|
base::Seconds(expected_frame_size * 8 / (bitrate_kbps_ * 1000.0));
|
|
if (expected_send_delay > current_frame_interval_) {
|
|
should_drop_quality = true;
|
|
}
|
|
}
|
|
|
|
updated_region_area_.Record(updated_area);
|
|
return should_drop_quality;
|
|
}
|
|
|
|
void WebrtcVideoEncoderWrapper::SchedulePendingFrame() {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
if (pending_frame_) {
|
|
auto pending_frame = std::move(pending_frame_);
|
|
Encode(*pending_frame, nullptr);
|
|
}
|
|
}
|
|
|
|
void WebrtcVideoEncoderWrapper::DropPendingFrame() {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
if (pending_frame_) {
|
|
pending_frame_.reset();
|
|
NotifyFrameDropped();
|
|
}
|
|
}
|
|
|
|
} // namespace remoting::protocol
|