blob: d5e9f9baf5499d49fb1205719dde95432326e565 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/peerconnection/webrtc_video_track_source.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/trace_event/trace_event.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/webrtc/rtc_base/ref_counted_object.h"
namespace {
gfx::Rect CropRectangle(const gfx::Rect& input_rect,
const gfx::Rect& cropping_rect) {
gfx::Rect result(input_rect);
result.Intersect(cropping_rect);
result.Offset(-cropping_rect.x(), -cropping_rect.y());
if (result.x() < 0)
result.set_x(0);
if (result.y() < 0)
result.set_y(0);
return result;
}
gfx::Rect ScaleRectangle(const gfx::Rect& input_rect,
gfx::Size original,
gfx::Size scaled) {
if (input_rect.IsEmpty()) {
return input_rect;
}
gfx::Rect result;
// Rounded down.
result.set_x(input_rect.x() * scaled.width() / original.width());
result.set_y(input_rect.y() * scaled.height() / original.height());
// rounded up.
result.set_width(input_rect.width() * scaled.width() / original.width());
result.set_height(input_rect.height() * scaled.height() / original.height());
// Snap to 2x2 grid because of UV subsampling.
if (result.x() % 2) {
result.set_x(result.x() - 1);
result.set_width(result.width() + 1);
}
if (result.y() % 2) {
result.set_y(result.y() - 1);
result.set_height(result.height() + 1);
}
if (result.width() % 2) {
result.set_width(result.width() + 1);
}
if (result.height() % 2) {
result.set_height(result.height() + 1);
}
// Expand the rect by 2 pixels in each direction, to include any possible
// scaling artifacts.
result.set_x(result.x() - 2);
result.set_y(result.y() - 2);
result.set_width(result.width() + 4);
result.set_height(result.height() + 4);
result.Intersect(gfx::Rect(0, 0, scaled.width(), scaled.height()));
return result;
}
webrtc::VideoRotation GetFrameRotation(const media::VideoFrame* frame) {
if (!frame->metadata().transformation) {
return webrtc::kVideoRotation_0;
}
switch (frame->metadata().transformation->rotation) {
case media::VIDEO_ROTATION_0:
return webrtc::kVideoRotation_0;
case media::VIDEO_ROTATION_90:
return webrtc::kVideoRotation_90;
case media::VIDEO_ROTATION_180:
return webrtc::kVideoRotation_180;
case media::VIDEO_ROTATION_270:
return webrtc::kVideoRotation_270;
default:
return webrtc::kVideoRotation_0;
}
}
} // anonymous namespace
namespace blink {
WebRtcVideoTrackSource::WebRtcVideoTrackSource(
bool is_screencast,
absl::optional<bool> needs_denoising,
media::VideoCaptureFeedbackCB callback,
media::GpuVideoAcceleratorFactories* gpu_factories)
: AdaptedVideoTrackSource(/*required_alignment=*/1),
adapter_resources_(
new WebRtcVideoFrameAdapter::SharedResources(gpu_factories)),
is_screencast_(is_screencast),
needs_denoising_(needs_denoising),
callback_(callback) {
DETACH_FROM_THREAD(thread_checker_);
}
WebRtcVideoTrackSource::~WebRtcVideoTrackSource() = default;
void WebRtcVideoTrackSource::SetCustomFrameAdaptationParamsForTesting(
const FrameAdaptationParams& params) {
custom_frame_adaptation_params_for_testing_ = params;
}
void WebRtcVideoTrackSource::SetSinkWantsForTesting(
const rtc::VideoSinkWants& sink_wants) {
video_adapter()->OnSinkWants(sink_wants);
}
WebRtcVideoTrackSource::SourceState WebRtcVideoTrackSource::state() const {
// TODO(nisse): What's supposed to change this state?
return MediaSourceInterface::SourceState::kLive;
}
bool WebRtcVideoTrackSource::remote() const {
return false;
}
bool WebRtcVideoTrackSource::is_screencast() const {
return is_screencast_;
}
absl::optional<bool> WebRtcVideoTrackSource::needs_denoising() const {
return needs_denoising_;
}
void WebRtcVideoTrackSource::SendFeedback() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (callback_.is_null()) {
return;
}
media::VideoFrameFeedback feedback;
feedback.max_pixels = video_adapter()->GetTargetPixels();
feedback.max_framerate_fps = video_adapter()->GetMaxFramerate();
callback_.Run(feedback);
}
void WebRtcVideoTrackSource::OnFrameCaptured(
scoped_refptr<media::VideoFrame> frame) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
TRACE_EVENT0("media", "WebRtcVideoSource::OnFrameCaptured");
if (!WebRtcVideoFrameAdapter::IsFrameAdaptable(frame.get())) {
// Since connecting sources and sinks do not check the format, we need to
// just ignore formats that we can not handle.
LOG(ERROR) << "We cannot send frame with storage type: "
<< frame->AsHumanReadableString();
NOTREACHED();
return;
}
SendFeedback();
// Compute what rectangular region has changed since the last frame
// that we successfully delivered to the base class method
// rtc::AdaptedVideoTrackSource::OnFrame(). This region is going to be
// relative to the coded frame data, i.e.
// [0, 0, frame->coded_size().width(), frame->coded_size().height()].
base::Optional<int> capture_counter = frame->metadata().capture_counter;
base::Optional<gfx::Rect> update_rect = frame->metadata().capture_update_rect;
const bool has_valid_update_rect =
update_rect.has_value() && capture_counter.has_value() &&
previous_capture_counter_.has_value() &&
(*capture_counter == (*previous_capture_counter_ + 1));
DVLOG(3) << "has_valid_update_rect = " << has_valid_update_rect;
if (capture_counter)
previous_capture_counter_ = capture_counter;
if (has_valid_update_rect) {
if (!accumulated_update_rect_) {
accumulated_update_rect_ = update_rect;
} else {
accumulated_update_rect_->Union(*update_rect);
}
} else {
accumulated_update_rect_ = base::nullopt;
}
if (accumulated_update_rect_) {
DVLOG(3) << "accumulated_update_rect_ = [" << accumulated_update_rect_->x()
<< ", " << accumulated_update_rect_->y() << ", "
<< accumulated_update_rect_->width() << ", "
<< accumulated_update_rect_->height() << "]";
}
// Calculate desired target cropping and scaling of the received frame. Note,
// that the frame may already have some cropping and scaling soft-applied via
// |frame->visible_rect()| and |frame->natural_size()|. The target cropping
// and scaling computed by AdaptFrame() below is going to be applied on top
// of the existing one.
const int orig_width = frame->natural_size().width();
const int orig_height = frame->natural_size().height();
const int64_t now_us = rtc::TimeMicros();
FrameAdaptationParams frame_adaptation_params =
ComputeAdaptationParams(orig_width, orig_height, now_us);
if (frame_adaptation_params.should_drop_frame)
return;
const int64_t translated_camera_time_us =
timestamp_aligner_.TranslateTimestamp(frame->timestamp().InMicroseconds(),
now_us);
// Return |frame| directly if it is texture not backed up by GPU memory,
// because there is no cropping support for texture yet. See
// http://crbug/503653.
if (frame->HasTextures() &&
frame->storage_type() != media::VideoFrame::STORAGE_GPU_MEMORY_BUFFER) {
// The webrtc::VideoFrame::UpdateRect expected by WebRTC must
// be relative to the |visible_rect()|. We need to translate.
base::Optional<gfx::Rect> cropped_rect;
if (accumulated_update_rect_) {
cropped_rect =
CropRectangle(*accumulated_update_rect_, frame->visible_rect());
}
DeliverFrame(std::move(frame), OptionalOrNullptr(cropped_rect),
translated_camera_time_us);
return;
}
// Translate the |crop_*| values output by AdaptFrame() from natural size to
// visible size. This is needed to apply the new cropping on top of any
// existing soft-applied cropping and scaling when using
// media::VideoFrame::WrapVideoFrame().
gfx::Rect cropped_visible_rect(
frame->visible_rect().x() + frame_adaptation_params.crop_x *
frame->visible_rect().width() /
orig_width,
frame->visible_rect().y() + frame_adaptation_params.crop_y *
frame->visible_rect().height() /
orig_height,
frame_adaptation_params.crop_width * frame->visible_rect().width() /
orig_width,
frame_adaptation_params.crop_height * frame->visible_rect().height() /
orig_height);
DVLOG(3) << "cropped_visible_rect = "
<< "[" << cropped_visible_rect.x() << ", "
<< cropped_visible_rect.y() << ", " << cropped_visible_rect.width()
<< ", " << cropped_visible_rect.height() << "]";
const gfx::Size adapted_size(frame_adaptation_params.scale_to_width,
frame_adaptation_params.scale_to_height);
// Soft-apply the new (combined) cropping and scaling.
scoped_refptr<media::VideoFrame> video_frame =
media::VideoFrame::WrapVideoFrame(frame, frame->format(),
cropped_visible_rect, adapted_size);
if (!video_frame)
return;
// The webrtc::VideoFrame::UpdateRect expected by WebRTC must be
// relative to the |visible_rect()|. We need to translate.
if (accumulated_update_rect_) {
accumulated_update_rect_ =
CropRectangle(*accumulated_update_rect_, frame->visible_rect());
}
// If no scaling is needed, return a wrapped version of |frame| directly.
// The soft-applied cropping will be taken into account by the remainder
// of the pipeline.
if (video_frame->natural_size() == video_frame->visible_rect().size()) {
DeliverFrame(std::move(video_frame),
OptionalOrNullptr(accumulated_update_rect_),
translated_camera_time_us);
return;
}
if (accumulated_update_rect_) {
accumulated_update_rect_ = ScaleRectangle(
*accumulated_update_rect_, video_frame->visible_rect().size(),
video_frame->natural_size());
}
DeliverFrame(std::move(video_frame),
OptionalOrNullptr(accumulated_update_rect_),
translated_camera_time_us);
}
WebRtcVideoTrackSource::FrameAdaptationParams
WebRtcVideoTrackSource::ComputeAdaptationParams(int width,
int height,
int64_t time_us) {
if (custom_frame_adaptation_params_for_testing_.has_value())
return custom_frame_adaptation_params_for_testing_.value();
FrameAdaptationParams result{false, 0, 0, 0, 0, 0, 0};
result.should_drop_frame = !AdaptFrame(
width, height, time_us, &result.scale_to_width, &result.scale_to_height,
&result.crop_width, &result.crop_height, &result.crop_x, &result.crop_y);
return result;
}
void WebRtcVideoTrackSource::DeliverFrame(
scoped_refptr<media::VideoFrame> frame,
gfx::Rect* update_rect,
int64_t timestamp_us) {
if (update_rect) {
DVLOG(3) << "update_rect = "
<< "[" << update_rect->x() << ", " << update_rect->y() << ", "
<< update_rect->width() << ", " << update_rect->height() << "]";
}
// If the cropping or the size have changed since the previous
// frame, even if nothing in the incoming coded frame content has changed, we
// have to assume that every pixel in the outgoing frame has changed.
if (frame->visible_rect() != cropping_rect_of_previous_delivered_frame_ ||
frame->natural_size() != natural_size_of_previous_delivered_frame_) {
cropping_rect_of_previous_delivered_frame_ = frame->visible_rect();
natural_size_of_previous_delivered_frame_ = frame->natural_size();
update_rect = nullptr;
}
webrtc::VideoFrame::Builder frame_builder =
webrtc::VideoFrame::Builder()
.set_video_frame_buffer(
new rtc::RefCountedObject<WebRtcVideoFrameAdapter>(
frame, adapter_resources_))
.set_rotation(GetFrameRotation(frame.get()))
.set_timestamp_us(timestamp_us);
if (update_rect) {
frame_builder.set_update_rect(webrtc::VideoFrame::UpdateRect{
update_rect->x(), update_rect->y(), update_rect->width(),
update_rect->height()});
}
OnFrame(frame_builder.build());
// Clear accumulated_update_rect_.
accumulated_update_rect_ = gfx::Rect();
}
} // namespace blink