// 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/thread_safe_capture_oracle.h" #include #include #include "base/bind.h" #include "base/bits.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/synchronization/lock.h" #include "base/time/time.h" #include "base/trace_event/trace_event.h" #include "media/base/video_capture_types.h" #include "media/base/video_frame.h" #include "media/base/video_frame_metadata.h" #include "media/base/video_util.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 ThreadSafeCaptureOracle::ThreadSafeCaptureOracle( scoped_ptr client, const VideoCaptureParams& params, bool enable_auto_throttling) : client_(std::move(client)), oracle_(base::TimeDelta::FromMicroseconds(static_cast( 1000000.0 / params.requested_format.frame_rate + 0.5 /* to round to nearest int */)), params.requested_format.frame_size, params.resolution_change_policy, enable_auto_throttling), params_(params) {} ThreadSafeCaptureOracle::~ThreadSafeCaptureOracle() { } bool ThreadSafeCaptureOracle::ObserveEventAndDecideCapture( VideoCaptureOracle::Event event, const gfx::Rect& damage_rect, base::TimeTicks event_time, scoped_refptr* storage, CaptureFrameCallback* callback) { // Grab the current time before waiting to acquire the |lock_|. const base::TimeTicks capture_begin_time = base::TimeTicks::Now(); base::AutoLock guard(lock_); if (!client_) return false; // Capture is stopped. const bool should_capture = oracle_.ObserveEventAndDecideCapture(event, damage_rect, event_time); const gfx::Size 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 const gfx::Size coded_size(base::bits::Align(visible_size.width(), 16), base::bits::Align(visible_size.height(), 16)); scoped_ptr output_buffer( client_->ReserveOutputBuffer(coded_size, params_.requested_format.pixel_format, params_.requested_format.pixel_storage)); // 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). const double attenuated_utilization = client_->GetBufferPoolUtilization() * (100.0 / kTargetMaxPoolUtilizationPercent); const char* event_name = (event == VideoCaptureOracle::kTimerPoll ? "poll" : (event == VideoCaptureOracle::kCompositorUpdate ? "gpu" : "unknown")); // Consider the various reasons not to initiate a capture. if (should_capture && !output_buffer.get()) { TRACE_EVENT_INSTANT1("gpu.capture", "PipelineLimited", TRACE_EVENT_SCOPE_THREAD, "trigger", event_name); oracle_.RecordWillNotCapture(attenuated_utilization); return false; } else if (!should_capture && output_buffer.get()) { if (event == VideoCaptureOracle::kCompositorUpdate) { // 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", event_name); } return false; } else if (!should_capture && !output_buffer.get()) { // We decided not to capture, but we wouldn't have been able to if we wanted // to because no output buffer was available. TRACE_EVENT_INSTANT1("gpu.capture", "NearlyPipelineLimited", TRACE_EVENT_SCOPE_THREAD, "trigger", event_name); return false; } const int frame_number = oracle_.RecordCapture(attenuated_utilization); TRACE_EVENT_ASYNC_BEGIN2("gpu.capture", "Capture", output_buffer.get(), "frame_number", frame_number, "trigger", event_name); DCHECK_EQ(media::PIXEL_STORAGE_CPU, params_.requested_format.pixel_storage); *storage = VideoFrame::WrapExternalSharedMemory( params_.requested_format.pixel_format, coded_size, gfx::Rect(visible_size), visible_size, static_cast(output_buffer->data()), output_buffer->mapped_size(), base::SharedMemory::NULLHandle(), 0u, base::TimeDelta()); if (!(*storage)) return false; *callback = base::Bind(&ThreadSafeCaptureOracle::DidCaptureFrame, this, frame_number, base::Passed(&output_buffer), capture_begin_time, oracle_.estimated_frame_duration()); 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( const tracked_objects::Location& from_here, const std::string& reason) { base::AutoLock guard(lock_); if (client_) client_->OnError(from_here, reason); } void ThreadSafeCaptureOracle::DidCaptureFrame( int frame_number, scoped_ptr buffer, base::TimeTicks capture_begin_time, base::TimeDelta estimated_frame_duration, const scoped_refptr& frame, base::TimeTicks timestamp, bool success) { base::AutoLock guard(lock_); TRACE_EVENT_ASYNC_END2("gpu.capture", "Capture", buffer.get(), "success", success, "timestamp", timestamp.ToInternalValue()); if (oracle_.CompleteCapture(frame_number, success, ×tamp)) { TRACE_EVENT_INSTANT0("gpu.capture", "CaptureSucceeded", TRACE_EVENT_SCOPE_THREAD); if (!client_) return; // Capture is stopped. 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, estimated_frame_duration); frame->AddDestructionObserver( base::Bind(&ThreadSafeCaptureOracle::DidConsumeFrame, this, frame_number, frame->metadata())); client_->OnIncomingCapturedVideoFrame(std::move(buffer), frame, timestamp); } } void ThreadSafeCaptureOracle::DidConsumeFrame( int frame_number, const media::VideoFrameMetadata* metadata) { // Note: This function may be called on any thread by the VideoFrame // destructor. |metadata| is still valid for read-access at this point. double utilization = -1.0; if (metadata->GetDouble(media::VideoFrameMetadata::RESOURCE_UTILIZATION, &utilization)) { base::AutoLock guard(lock_); oracle_.RecordConsumerFeedback(frame_number, utilization); } } } // namespace media