blob: 3a5196361d11205760794113a82a6c234f7b467b [file] [log] [blame]
/*
* Copyright (c) 2019 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 "test/frame_change_extractor.h"
#include <algorithm>
#include "api/video/i420_buffer.h"
#include "rtc_base/checks.h"
namespace webrtc {
FrameChangeExtractor::FrameChangeExtractor() = default;
FrameChangeExtractor::~FrameChangeExtractor() = default;
void FrameChangeExtractor::SetSource(
rtc::VideoSourceInterface<VideoFrame>* video_source) {
rtc::CritScope lock(&source_crit_);
rtc::VideoSourceInterface<VideoFrame>* old_source = nullptr;
old_source = source_;
source_ = video_source;
if (old_source != video_source && old_source != nullptr) {
old_source->RemoveSink(this);
}
if (video_source) {
source_->AddOrUpdateSink(this, rtc::VideoSinkWants());
}
}
void FrameChangeExtractor::OnFrame(const VideoFrame& frame) {
// Currently only supports I420 buffers.
RTC_CHECK(frame.video_frame_buffer()->type() ==
VideoFrameBuffer::Type::kI420);
rtc::CritScope lock(&sink_crit_);
if (!sink_)
return;
webrtc::I420BufferInterface* current_buffer =
frame.video_frame_buffer()->ToI420();
int min_row, min_col, max_row, max_col;
if (!last_frame_buffer_ ||
current_buffer->width() != last_frame_buffer_->width() ||
current_buffer->height() != last_frame_buffer_->height()) {
// New resolution - fully new picture.
min_row = min_col = 0;
max_row = current_buffer->height() - 1;
max_col = current_buffer->width() - 1;
} else {
min_row = frame.width();
min_col = frame.height();
max_row = 0;
max_col = 0;
// Detect changed rect.
for (int row = 0; row < frame.height(); ++row) {
for (int col = 0; col < frame.width(); ++col) {
int uv_row = row / 2;
int uv_col = col / 2;
if (current_buffer
->DataU()[uv_row * current_buffer->StrideU() + uv_col] !=
last_frame_buffer_
->DataU()[uv_row * last_frame_buffer_->StrideU() +
uv_col] ||
current_buffer
->DataV()[uv_row * current_buffer->StrideV() + uv_col] !=
last_frame_buffer_
->DataV()[uv_row * last_frame_buffer_->StrideV() +
uv_col] ||
current_buffer->DataY()[row * current_buffer->StrideY() + col] !=
last_frame_buffer_
->DataY()[row * last_frame_buffer_->StrideY() + col]) {
min_row = std::min(min_row, row);
max_row = std::max(max_row, row);
min_col = std::min(min_col, col);
max_col = std::max(max_col, col);
}
}
}
}
if (max_row < min_row || max_col < min_col) {
min_col = min_row = 0;
max_col = max_row = -1;
}
// Expand changed rect to accommodate subsampled UV plane.
if (min_row % 2)
--min_row;
if (min_col % 2)
--min_row;
if (max_row < frame.width() && max_row % 2 == 0)
++max_row;
if (max_col < frame.height() && max_col % 2 == 0)
++max_col;
VideoFrame::PartialFrameDescription part_desc;
part_desc.offset_x = min_col;
part_desc.offset_y = min_row;
int changed_width = max_col - min_col + 1;
int changed_height = max_row - min_row + 1;
last_frame_buffer_ = current_buffer;
VideoFrame copy = frame;
if (changed_width > 0 && changed_height > 0) {
rtc::scoped_refptr<I420Buffer> changed_buffer =
I420Buffer::Create(changed_width, changed_height);
changed_buffer->CropAndScaleFrom(*current_buffer, part_desc.offset_x,
part_desc.offset_y, changed_width,
changed_height);
copy.set_video_frame_buffer(changed_buffer);
} else {
copy.set_video_frame_buffer(nullptr);
}
if (changed_width < frame.width() || changed_height < frame.height()) {
copy.set_partial_frame_description(part_desc);
}
copy.set_cache_buffer_for_partial_updates(true);
sink_->OnFrame(copy);
}
void FrameChangeExtractor::AddOrUpdateSink(
rtc::VideoSinkInterface<VideoFrame>* sink,
const rtc::VideoSinkWants& wants) {
RTC_CHECK(wants.partial_frames);
{
rtc::CritScope lock(&source_crit_);
if (source_) {
source_->AddOrUpdateSink(this, wants);
}
}
{
rtc::CritScope lock(&sink_crit_);
// Several sinks are unsupported.
RTC_CHECK(sink_ == nullptr || sink_ == sink);
sink_ = sink;
}
}
void FrameChangeExtractor::RemoveSink(
rtc::VideoSinkInterface<VideoFrame>* sink) {
{
rtc::CritScope lock(&source_crit_);
if (source_) {
source_->RemoveSink(this);
}
}
{
rtc::CritScope lock(&sink_crit_);
sink_ = nullptr;
}
}
} // namespace webrtc