blob: e80f2333d9beb1f176bc9e75bba8528a2f5a6f63 [file] [log] [blame]
// Copyright 2014 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 "media/capture/content/android/thread_safe_capture_oracle.h"
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/bits.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "media/base/video_frame.h"
#include "media/base/video_frame_metadata.h"
#include "media/base/video_util.h"
#include "media/capture/video_capture_types.h"
#include "ui/gfx/geometry/rect.h"
namespace media {
namespace {
// The target maximum amount of the buffer pool to utilize. Actual buffer pool
// utilization is attenuated by this amount before being reported to the
// VideoCaptureOracle. This value takes into account the maximum number of
// buffer pool buffers and a desired safety margin.
const int kTargetMaxPoolUtilizationPercent = 60;
} // namespace
struct ThreadSafeCaptureOracle::InFlightFrameCapture {
int frame_number;
VideoCaptureDevice::Client::Buffer buffer;
std::unique_ptr<VideoCaptureBufferHandle> buffer_access;
base::TimeTicks begin_time;
base::TimeDelta frame_duration;
};
ThreadSafeCaptureOracle::ThreadSafeCaptureOracle(
std::unique_ptr<VideoCaptureDevice::Client> client,
const VideoCaptureParams& params)
: client_(std::move(client)), oracle_(false), params_(params) {
DCHECK_GE(params.requested_format.frame_rate, 1e-6f);
oracle_.SetMinCapturePeriod(base::TimeDelta::FromMicroseconds(
static_cast<int64_t>(1000000.0 / params.requested_format.frame_rate +
0.5 /* to round to nearest int */)));
const auto constraints = params.SuggestConstraints();
oracle_.SetCaptureSizeConstraints(constraints.min_frame_size,
constraints.max_frame_size,
constraints.fixed_aspect_ratio);
}
ThreadSafeCaptureOracle::~ThreadSafeCaptureOracle() = default;
bool ThreadSafeCaptureOracle::ObserveEventAndDecideCapture(
VideoCaptureOracle::Event event,
const gfx::Rect& damage_rect,
base::TimeTicks event_time,
scoped_refptr<VideoFrame>* storage,
CaptureFrameCallback* callback) {
// Grab the current time before waiting to acquire the |lock_|.
const base::TimeTicks capture_begin_time = base::TimeTicks::Now();
gfx::Size visible_size;
gfx::Size coded_size;
media::VideoCaptureDevice::Client::Buffer output_buffer;
double attenuated_utilization;
int frame_number;
base::TimeDelta estimated_frame_duration;
{
base::AutoLock guard(lock_);
if (!client_)
return false; // Capture is stopped.
if (!oracle_.ObserveEventAndDecideCapture(event, damage_rect, event_time)) {
// This is a normal and acceptable way to drop a frame. We've hit our
// capture rate limit: for example, the content is animating at 60fps but
// we're capturing at 30fps.
TRACE_EVENT_INSTANT1("gpu.capture", "FpsRateLimited",
TRACE_EVENT_SCOPE_THREAD, "trigger",
VideoCaptureOracle::EventAsString(event));
return false;
}
frame_number = oracle_.next_frame_number();
visible_size = oracle_.capture_size();
// TODO(miu): Clients should request exact padding, instead of this
// memory-wasting hack to make frames that are compatible with all HW
// encoders. http://crbug.com/555911
coded_size.SetSize(base::bits::Align(visible_size.width(), 16),
base::bits::Align(visible_size.height(), 16));
const auto result_code = client_->ReserveOutputBuffer(
coded_size, params_.requested_format.pixel_format, frame_number,
&output_buffer);
// Get the current buffer pool utilization and attenuate it: The utilization
// reported to the oracle is in terms of a maximum sustainable amount (not
// the absolute maximum).
attenuated_utilization = client_->GetBufferPoolUtilization() *
(100.0 / kTargetMaxPoolUtilizationPercent);
if (result_code != VideoCaptureDevice::Client::ReserveResult::kSucceeded) {
TRACE_EVENT_INSTANT2(
"gpu.capture", "PipelineLimited", TRACE_EVENT_SCOPE_THREAD, "trigger",
VideoCaptureOracle::EventAsString(event), "atten_util_percent",
base::saturated_cast<int>(attenuated_utilization * 100.0 + 0.5));
oracle_.RecordWillNotCapture(attenuated_utilization);
return false;
}
oracle_.RecordCapture(attenuated_utilization);
estimated_frame_duration = oracle_.estimated_frame_duration();
} // End of critical section.
if (attenuated_utilization >= 1.0) {
TRACE_EVENT_INSTANT2(
"gpu.capture", "NearlyPipelineLimited", TRACE_EVENT_SCOPE_THREAD,
"trigger", VideoCaptureOracle::EventAsString(event),
"atten_util_percent",
base::saturated_cast<int>(attenuated_utilization * 100.0 + 0.5));
}
TRACE_EVENT_ASYNC_BEGIN2("gpu.capture", "Capture", output_buffer.id,
"frame_number", frame_number, "trigger",
VideoCaptureOracle::EventAsString(event));
std::unique_ptr<VideoCaptureBufferHandle> output_buffer_access =
output_buffer.handle_provider->GetHandleForInProcessAccess();
*storage = VideoFrame::WrapExternalSharedMemory(
params_.requested_format.pixel_format, coded_size,
gfx::Rect(visible_size), visible_size, output_buffer_access->data(),
output_buffer_access->mapped_size(), base::SharedMemoryHandle(), 0u,
base::TimeDelta());
// Note: Passing the |output_buffer_access| in the callback is a bit of a
// hack. Really, the access should be owned by the VideoFrame so that access
// is released (unpinning the shared memory) when the VideoFrame goes
// out-of-scope. However, there is an an issue where, at browser shutdown, the
// callback below may never be run, and instead it self-deletes: If this
// happens, the VideoFrame will release access *after* the Buffer goes
// out-of-scope, which is an invalid sequence of steps. This could be fixed in
// upstream implementation, but it's not worth spending time tracking it down
// because all of this code (and upstream code) is about to be replaced.
// http://crbug.com/754872
//
// To be clear, this solution allows |output_buffer_access| to be deleted
// before |output_buffer| if the callback self-deletes rather than ever being
// run. The InFlightFrameCapture destructor ensures this.
std::unique_ptr<InFlightFrameCapture> capture(new InFlightFrameCapture{
frame_number, std::move(output_buffer), std::move(output_buffer_access),
capture_begin_time, estimated_frame_duration});
// If creating the VideoFrame wrapper failed, call DidCaptureFrame() with
// !success to execute the required post-capture steps (tracing, notification
// of failure to VideoCaptureOracle, etc.).
if (!(*storage)) {
DidCaptureFrame(std::move(capture), *storage, event_time, false);
return false;
}
*callback = base::BindOnce(&ThreadSafeCaptureOracle::DidCaptureFrame, this,
base::Passed(&capture));
return true;
}
gfx::Size ThreadSafeCaptureOracle::GetCaptureSize() const {
base::AutoLock guard(lock_);
return oracle_.capture_size();
}
void ThreadSafeCaptureOracle::UpdateCaptureSize(const gfx::Size& source_size) {
base::AutoLock guard(lock_);
VLOG(1) << "Source size changed to " << source_size.ToString();
oracle_.SetSourceSize(source_size);
}
void ThreadSafeCaptureOracle::Stop() {
base::AutoLock guard(lock_);
client_.reset();
}
void ThreadSafeCaptureOracle::ReportError(media::VideoCaptureError error,
const base::Location& from_here,
const std::string& reason) {
base::AutoLock guard(lock_);
if (client_)
client_->OnError(error, from_here, reason);
}
void ThreadSafeCaptureOracle::ReportStarted() {
base::AutoLock guard(lock_);
if (client_)
client_->OnStarted();
}
void ThreadSafeCaptureOracle::DidCaptureFrame(
std::unique_ptr<InFlightFrameCapture> capture,
scoped_refptr<VideoFrame> frame,
base::TimeTicks reference_time,
bool success) {
// Release |buffer_access| now that nothing is accessing the memory via the
// VideoFrame data pointers anymore.
capture->buffer_access.reset();
base::AutoLock guard(lock_);
const bool should_deliver_frame =
oracle_.CompleteCapture(capture->frame_number, success, &reference_time);
TRACE_EVENT_ASYNC_END2("gpu.capture", "Capture", capture->buffer.id,
"success", should_deliver_frame, "timestamp",
(reference_time - base::TimeTicks()).InMicroseconds());
if (!should_deliver_frame || !client_)
return;
frame->metadata()->SetDouble(VideoFrameMetadata::FRAME_RATE,
params_.requested_format.frame_rate);
frame->metadata()->SetTimeTicks(VideoFrameMetadata::CAPTURE_BEGIN_TIME,
capture->begin_time);
frame->metadata()->SetTimeTicks(VideoFrameMetadata::CAPTURE_END_TIME,
base::TimeTicks::Now());
frame->metadata()->SetTimeDelta(VideoFrameMetadata::FRAME_DURATION,
capture->frame_duration);
frame->metadata()->SetTimeTicks(VideoFrameMetadata::REFERENCE_TIME,
reference_time);
media::VideoCaptureFormat format(frame->coded_size(),
params_.requested_format.frame_rate,
frame->format());
client_->OnIncomingCapturedBufferExt(
std::move(capture->buffer), format, reference_time, frame->timestamp(),
frame->visible_rect(), *frame->metadata());
}
void ThreadSafeCaptureOracle::OnConsumerReportingUtilization(
int frame_number,
double utilization) {
base::AutoLock guard(lock_);
oracle_.RecordConsumerFeedback(frame_number, utilization);
}
} // namespace media