| // 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/thread_safe_capture_oracle.h" |
| |
| #include "base/basictypes.h" |
| #include "base/bind.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 { |
| |
| ThreadSafeCaptureOracle::ThreadSafeCaptureOracle( |
| scoped_ptr<VideoCaptureDevice::Client> client, |
| const VideoCaptureParams& params) |
| : client_(client.Pass()), |
| oracle_(base::TimeDelta::FromMicroseconds( |
| static_cast<int64>(1000000.0 / params.requested_format.frame_rate + |
| 0.5 /* to round to nearest int */))), |
| params_(params), |
| resolution_chooser_(params.requested_format.frame_size, |
| params.resolution_change_policy) {} |
| |
| ThreadSafeCaptureOracle::~ThreadSafeCaptureOracle() {} |
| |
| 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(); |
| |
| base::AutoLock guard(lock_); |
| |
| if (!client_) |
| return false; // Capture is stopped. |
| |
| const gfx::Size visible_size = resolution_chooser_.capture_size(); |
| // Always round up the coded size to multiple of 16 pixels. |
| // See http://crbug.com/402151. |
| const gfx::Size coded_size((visible_size.width() + 15) & ~15, |
| (visible_size.height() + 15) & ~15); |
| |
| scoped_ptr<VideoCaptureDevice::Client::Buffer> output_buffer( |
| client_->ReserveOutputBuffer(params_.requested_format.pixel_format, |
| coded_size)); |
| // TODO(miu): Use current buffer pool utilization to drive automatic video |
| // resolution changes. http://crbug.com/156767. |
| VLOG(2) << "Current buffer pool utilization is " |
| << (client_->GetBufferPoolUtilization() * 100.0) << '%'; |
| |
| const bool should_capture = |
| oracle_.ObserveEventAndDecideCapture(event, damage_rect, event_time); |
| |
| 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); |
| 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; |
| } |
| int frame_number = oracle_.RecordCapture(); |
| TRACE_EVENT_ASYNC_BEGIN2("gpu.capture", "Capture", output_buffer.get(), |
| "frame_number", frame_number, |
| "trigger", event_name); |
| // NATIVE_TEXTURE frames wrap a texture mailbox, which we don't have at the |
| // moment. We do not construct those frames. |
| if (params_.requested_format.pixel_format != PIXEL_FORMAT_TEXTURE) { |
| *storage = VideoFrame::WrapExternalData( |
| VideoFrame::I420, |
| coded_size, |
| gfx::Rect(visible_size), |
| visible_size, |
| static_cast<uint8*>(output_buffer->data()), |
| output_buffer->size(), |
| base::TimeDelta()); |
| DCHECK(*storage); |
| } |
| *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 resolution_chooser_.capture_size(); |
| } |
| |
| void ThreadSafeCaptureOracle::UpdateCaptureSize(const gfx::Size& source_size) { |
| base::AutoLock guard(lock_); |
| resolution_chooser_.SetSourceSize(source_size); |
| VLOG(1) << "Source size changed to " << source_size.ToString() |
| << " --> Capture size is now " |
| << resolution_chooser_.capture_size().ToString(); |
| } |
| |
| void ThreadSafeCaptureOracle::Stop() { |
| base::AutoLock guard(lock_); |
| client_.reset(); |
| } |
| |
| void ThreadSafeCaptureOracle::ReportError(const std::string& reason) { |
| base::AutoLock guard(lock_); |
| if (client_) |
| client_->OnError(reason); |
| } |
| |
| void ThreadSafeCaptureOracle::DidCaptureFrame( |
| int frame_number, |
| scoped_ptr<VideoCaptureDevice::Client::Buffer> buffer, |
| base::TimeTicks capture_begin_time, |
| base::TimeDelta estimated_frame_duration, |
| const scoped_refptr<VideoFrame>& 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(buffer.Pass(), 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) && |
| utilization >= 0.0) { |
| VLOG(2) << "Consumer resource utilization for frame " << frame_number |
| << ": " << utilization; |
| } |
| |
| // TODO(miu): Use |utilization| to drive automatic video resolution changes. |
| // http://crbug.com/156767. |
| } |
| |
| } // namespace media |