| // 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, frame->ColorSpace(), 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 |