From e4a406a55b1fb94080258f5801338ff63b8dcbed Mon Sep 17 00:00:00 2001 From: miu Date: Fri, 25 Mar 2016 17:57:21 -0700 Subject: Add frame refresh to VideoCaptureDevice, and buffer pool resurrection. This is the first in a series of changes to solve the problems related to VideoCaptureDevices that can pause (i.e., basically, screen capture devices). This introduces new optional-to-implement methods to the media::VideoCaptureDevice interface, and the supporting functionality in content::VideoCaptureBufferPool. This change should not result in any end-user-visible differences, as it is just laying the groundwork for future changes. For more details on the overall plan, please see the crbug. BUG=486274 Review URL: https://codereview.chromium.org/1826643003 Cr-Commit-Position: refs/heads/master@{#383426} --- .../desktop_capture_device_aura_unittest.cc | 11 +- .../capture/desktop_capture_device_unittest.cc | 11 +- .../web_contents_video_capture_device_unittest.cc | 21 +- .../media/video_capture_buffer_pool.cc | 427 +++++++++++---------- .../media/video_capture_buffer_pool.h | 46 ++- .../media/video_capture_buffer_pool_unittest.cc | 147 ++++++- .../media/video_capture_controller_unittest.cc | 40 +- .../media/video_capture_device_client.cc | 27 +- .../media/video_capture_device_client.h | 4 + .../video/fake_video_capture_device_unittest.cc | 7 +- media/capture/video/video_capture_device.h | 42 +- .../capture/video/video_capture_device_unittest.cc | 9 + 12 files changed, 539 insertions(+), 253 deletions(-) diff --git a/content/browser/media/capture/desktop_capture_device_aura_unittest.cc b/content/browser/media/capture/desktop_capture_device_aura_unittest.cc index 984b661..0bcb066 100644 --- a/content/browser/media/capture/desktop_capture_device_aura_unittest.cc +++ b/content/browser/media/capture/desktop_capture_device_aura_unittest.cc @@ -60,6 +60,7 @@ class MockDeviceClient : public media::VideoCaptureDevice::Client { MOCK_METHOD0(DoReserveOutputBuffer, void(void)); MOCK_METHOD0(DoOnIncomingCapturedBuffer, void(void)); MOCK_METHOD0(DoOnIncomingCapturedVideoFrame, void(void)); + MOCK_METHOD0(DoResurrectLastOutputBuffer, void(void)); MOCK_METHOD2(OnError, void(const tracked_objects::Location& from_here, const std::string& reason)); @@ -85,7 +86,15 @@ class MockDeviceClient : public media::VideoCaptureDevice::Client { const base::TimeTicks& timestamp) override { DoOnIncomingCapturedVideoFrame(); } - + scoped_ptr ResurrectLastOutputBuffer( + const gfx::Size& dimensions, + media::VideoPixelFormat format, + media::VideoPixelStorage storage) override { + EXPECT_EQ(media::PIXEL_FORMAT_I420, format); + EXPECT_EQ(media::PIXEL_STORAGE_CPU, storage); + DoResurrectLastOutputBuffer(); + return scoped_ptr(); + } double GetBufferPoolUtilization() const override { return 0.0; } }; diff --git a/content/browser/media/capture/desktop_capture_device_unittest.cc b/content/browser/media/capture/desktop_capture_device_unittest.cc index ec10e73..3dd6bbc 100644 --- a/content/browser/media/capture/desktop_capture_device_unittest.cc +++ b/content/browser/media/capture/desktop_capture_device_unittest.cc @@ -79,6 +79,7 @@ class MockDeviceClient : public media::VideoCaptureDevice::Client { MOCK_METHOD0(DoReserveOutputBuffer, void(void)); MOCK_METHOD0(DoOnIncomingCapturedBuffer, void(void)); MOCK_METHOD0(DoOnIncomingCapturedVideoFrame, void(void)); + MOCK_METHOD0(DoResurrectLastOutputBuffer, void(void)); MOCK_METHOD2(OnError, void(const tracked_objects::Location& from_here, const std::string& reason)); @@ -104,7 +105,15 @@ class MockDeviceClient : public media::VideoCaptureDevice::Client { const base::TimeTicks& timestamp) override { DoOnIncomingCapturedVideoFrame(); } - + scoped_ptr ResurrectLastOutputBuffer( + const gfx::Size& dimensions, + media::VideoPixelFormat format, + media::VideoPixelStorage storage) override { + EXPECT_TRUE(format == media::PIXEL_FORMAT_I420 && + storage == media::PIXEL_STORAGE_CPU); + DoResurrectLastOutputBuffer(); + return scoped_ptr(); + } double GetBufferPoolUtilization() const override { return 0.0; } }; diff --git a/content/browser/media/capture/web_contents_video_capture_device_unittest.cc b/content/browser/media/capture/web_contents_video_capture_device_unittest.cc index 7b6268f..dfa6548 100644 --- a/content/browser/media/capture/web_contents_video_capture_device_unittest.cc +++ b/content/browser/media/capture/web_contents_video_capture_device_unittest.cc @@ -375,7 +375,7 @@ class StubClient : public media::VideoCaptureDevice::Client { CHECK_EQ(format, media::PIXEL_FORMAT_I420); int buffer_id_to_drop = VideoCaptureBufferPool::kInvalidId; // Ignored. const int buffer_id = buffer_pool_->ReserveForProducer( - format, storage, dimensions, &buffer_id_to_drop); + dimensions, format, storage, &buffer_id_to_drop); if (buffer_id == VideoCaptureBufferPool::kInvalidId) return NULL; @@ -383,6 +383,7 @@ class StubClient : public media::VideoCaptureDevice::Client { new AutoReleaseBuffer( buffer_pool_, buffer_pool_->GetBufferHandle(buffer_id), buffer_id)); } + // Trampoline method to workaround GMOCK problems with scoped_ptr<>. void OnIncomingCapturedBuffer(scoped_ptr buffer, const media::VideoCaptureFormat& frame_format, @@ -419,6 +420,20 @@ class StubClient : public media::VideoCaptureDevice::Client { frame->visible_rect().size()); } + scoped_ptr + ResurrectLastOutputBuffer(const gfx::Size& dimensions, + media::VideoPixelFormat format, + media::VideoPixelStorage storage) override { + CHECK_EQ(format, media::PIXEL_FORMAT_I420); + const int buffer_id = + buffer_pool_->ResurrectLastForProducer(dimensions, format, storage); + if (buffer_id == VideoCaptureBufferPool::kInvalidId) + return nullptr; + return scoped_ptr( + new AutoReleaseBuffer( + buffer_pool_, buffer_pool_->GetBufferHandle(buffer_id), buffer_id)); + } + void OnError(const tracked_objects::Location& from_here, const std::string& reason) override { error_callback_.Run(); @@ -439,7 +454,9 @@ class StubClient : public media::VideoCaptureDevice::Client { DCHECK(pool_); } int id() const override { return id_; } - gfx::Size dimensions() const override { return gfx::Size(); } + gfx::Size dimensions() const override { + return buffer_handle_->dimensions(); + } size_t mapped_size() const override { return buffer_handle_->mapped_size(); } diff --git a/content/browser/renderer_host/media/video_capture_buffer_pool.cc b/content/browser/renderer_host/media/video_capture_buffer_pool.cc index cd3e0be..831c060 100644 --- a/content/browser/renderer_host/media/video_capture_buffer_pool.cc +++ b/content/browser/renderer_host/media/video_capture_buffer_pool.cc @@ -16,99 +16,31 @@ namespace content { const int VideoCaptureBufferPool::kInvalidId = -1; -// A simple holder of a memory-backed buffer and accessors to it. -class SimpleBufferHandle final : public VideoCaptureBufferPool::BufferHandle { - public: - SimpleBufferHandle(void* data, - size_t mapped_size, - base::SharedMemoryHandle handle) - : data_(data), - mapped_size_(mapped_size) -#if defined(OS_POSIX) && !defined(OS_MACOSX) - , - handle_(handle) -#endif - { - } - ~SimpleBufferHandle() override {} - - gfx::Size dimensions() const override { - NOTREACHED(); - return gfx::Size(); - } - size_t mapped_size() const override { return mapped_size_; } - void* data(int plane) override { - DCHECK_EQ(plane, 0); - return data_; - } - ClientBuffer AsClientBuffer(int plane) override { - NOTREACHED(); - return nullptr; - } -#if defined(OS_POSIX) && !defined(OS_MACOSX) - base::FileDescriptor AsPlatformFile() override { - return handle_; - } -#endif - - private: - void* const data_; - const size_t mapped_size_; -#if defined(OS_POSIX) && !defined(OS_MACOSX) - const base::SharedMemoryHandle handle_; -#endif -}; - -// A holder of a GpuMemoryBuffer-backed buffer. Holds weak references to -// GpuMemoryBuffer-backed buffers and provides accessors to their data. -class GpuMemoryBufferBufferHandle final - : public VideoCaptureBufferPool::BufferHandle { - public: - GpuMemoryBufferBufferHandle(const gfx::Size& dimensions, - std::vector< - scoped_ptr>* gmbs) - : dimensions_(dimensions), gmbs_(gmbs) { - DCHECK(gmbs); - } - ~GpuMemoryBufferBufferHandle() override {} - - gfx::Size dimensions() const override { return dimensions_; } - size_t mapped_size() const override { return dimensions_.GetArea(); } - void* data(int plane) override { - DCHECK_GE(plane, 0); - DCHECK_LT(plane, static_cast(gmbs_->size())); - DCHECK((*gmbs_)[plane]); - return (*gmbs_)[plane]->memory(0); - } - ClientBuffer AsClientBuffer(int plane) override { - DCHECK_GE(plane, 0); - DCHECK_LT(plane, static_cast(gmbs_->size())); - return (*gmbs_)[plane]->AsClientBuffer(); - } -#if defined(OS_POSIX) && !defined(OS_MACOSX) - base::FileDescriptor AsPlatformFile() override { - NOTREACHED(); - return base::FileDescriptor(); - } -#endif - - private: - const gfx::Size dimensions_; - std::vector>* const gmbs_; -}; - // Tracker specifics for SharedMemory. class VideoCaptureBufferPool::SharedMemTracker final : public Tracker { public: - SharedMemTracker(); - bool Init(media::VideoPixelFormat format, + SharedMemTracker() : Tracker() {} + + bool Init(const gfx::Size& dimensions, + media::VideoPixelFormat format, media::VideoPixelStorage storage_type, - const gfx::Size& dimensions, - base::Lock* lock) override; + base::Lock* lock) override { + DVLOG(2) << "allocating ShMem of " << dimensions.ToString(); + set_dimensions(dimensions); + // |dimensions| can be 0x0 for trackers that do not require memory backing. + set_max_pixel_count(dimensions.GetArea()); + set_pixel_format(format); + set_storage_type(storage_type); + mapped_size_ = + media::VideoCaptureFormat(dimensions, 0.0f, format, storage_type) + .ImageAllocationSize(); + if (!mapped_size_) + return true; + return shared_memory_.CreateAndMapAnonymous(mapped_size_); + } scoped_ptr GetBufferHandle() override { - return make_scoped_ptr(new SimpleBufferHandle( - shared_memory_.memory(), mapped_size_, shared_memory_.handle())); + return make_scoped_ptr(new SharedMemBufferHandle(this)); } bool ShareToProcess(base::ProcessHandle process_handle, base::SharedMemoryHandle* new_handle) override { @@ -122,6 +54,37 @@ class VideoCaptureBufferPool::SharedMemTracker final : public Tracker { } private: + // A simple proxy that implements the BufferHandle interface, providing + // accessors to SharedMemTracker's memory and properties. + class SharedMemBufferHandle : public VideoCaptureBufferPool::BufferHandle { + public: + // |tracker| must outlive SimpleBufferHandle. This is ensured since a + // tracker is pinned until ownership of this SimpleBufferHandle is returned + // to VideoCaptureBufferPool. + explicit SharedMemBufferHandle(SharedMemTracker* tracker) + : tracker_(tracker) {} + ~SharedMemBufferHandle() override {} + + gfx::Size dimensions() const override { return tracker_->dimensions(); } + size_t mapped_size() const override { return tracker_->mapped_size_; } + void* data(int plane) override { + DCHECK_EQ(plane, 0); + return tracker_->shared_memory_.memory(); + } + ClientBuffer AsClientBuffer(int plane) override { + NOTREACHED(); + return nullptr; + } +#if defined(OS_POSIX) && !defined(OS_MACOSX) + base::FileDescriptor AsPlatformFile() override { + return tracker_->shared_memory_.handle(); + } +#endif + + private: + SharedMemTracker* const tracker_; + }; + // The memory created to be shared with renderer processes. base::SharedMemory shared_memory_; size_t mapped_size_; @@ -131,131 +94,134 @@ class VideoCaptureBufferPool::SharedMemTracker final : public Tracker { // associated pixel dimensions. class VideoCaptureBufferPool::GpuMemoryBufferTracker final : public Tracker { public: - GpuMemoryBufferTracker(); - bool Init(media::VideoPixelFormat format, + GpuMemoryBufferTracker() : Tracker() {} + + ~GpuMemoryBufferTracker() override { + for (const auto& gmb : gpu_memory_buffers_) + gmb->Unmap(); + } + + bool Init(const gfx::Size& dimensions, + media::VideoPixelFormat format, media::VideoPixelStorage storage_type, - const gfx::Size& dimensions, - base::Lock* lock) override; - ~GpuMemoryBufferTracker() override; + base::Lock* lock) override { + DVLOG(2) << "allocating GMB for " << dimensions.ToString(); + // BrowserGpuMemoryBufferManager::current() may not be accessed on IO + // Thread. + DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK(BrowserGpuMemoryBufferManager::current()); + // This class is only expected to be called with I420 buffer requests at + // this point. + DCHECK_EQ(format, media::PIXEL_FORMAT_I420); + set_dimensions(dimensions); + set_max_pixel_count(dimensions.GetArea()); + set_pixel_format(format); + set_storage_type(storage_type); + // |dimensions| can be 0x0 for trackers that do not require memory backing. + if (dimensions.GetArea() == 0u) + return true; + + base::AutoUnlock auto_unlock(*lock); + const size_t num_planes = media::VideoFrame::NumPlanes(pixel_format()); + for (size_t i = 0; i < num_planes; ++i) { + const gfx::Size& size = + media::VideoFrame::PlaneSize(pixel_format(), i, dimensions); + gpu_memory_buffers_.push_back( + BrowserGpuMemoryBufferManager::current()->AllocateGpuMemoryBuffer( + size, gfx::BufferFormat::R_8, + gfx::BufferUsage::GPU_READ_CPU_READ_WRITE)); + + DLOG_IF(ERROR, !gpu_memory_buffers_[i]) << "Allocating GpuMemoryBuffer"; + if (!gpu_memory_buffers_[i] || !gpu_memory_buffers_[i]->Map()) + return false; + } + return true; + } scoped_ptr GetBufferHandle() override { DCHECK_EQ(gpu_memory_buffers_.size(), media::VideoFrame::NumPlanes(pixel_format())); - return make_scoped_ptr( - new GpuMemoryBufferBufferHandle(dimensions_, &gpu_memory_buffers_)); + return make_scoped_ptr(new GpuMemoryBufferBufferHandle(this)); } + bool ShareToProcess(base::ProcessHandle process_handle, base::SharedMemoryHandle* new_handle) override { NOTREACHED(); return false; } + bool ShareToProcess2(int plane, base::ProcessHandle process_handle, - gfx::GpuMemoryBufferHandle* new_handle) override; - - private: - gfx::Size dimensions_; - // Owned references to GpuMemoryBuffers. - std::vector> gpu_memory_buffers_; -}; - -VideoCaptureBufferPool::SharedMemTracker::SharedMemTracker() : Tracker() {} - -bool VideoCaptureBufferPool::SharedMemTracker::Init( - media::VideoPixelFormat format, - media::VideoPixelStorage storage_type, - const gfx::Size& dimensions, - base::Lock* lock) { - DVLOG(2) << "allocating ShMem of " << dimensions.ToString(); - set_pixel_format(format); - set_storage_type(storage_type); - // |dimensions| can be 0x0 for trackers that do not require memory backing. - set_pixel_count(dimensions.GetArea()); - mapped_size_ = - media::VideoCaptureFormat(dimensions, 0.0f, format, storage_type) - .ImageAllocationSize(); - if (!mapped_size_) - return true; - return shared_memory_.CreateAndMapAnonymous(mapped_size_); -} - -VideoCaptureBufferPool::GpuMemoryBufferTracker::GpuMemoryBufferTracker() - : Tracker() { -} - -VideoCaptureBufferPool::GpuMemoryBufferTracker::~GpuMemoryBufferTracker() { - for (const auto& gmb : gpu_memory_buffers_) - gmb->Unmap(); -} - -bool VideoCaptureBufferPool::GpuMemoryBufferTracker::Init( - media::VideoPixelFormat format, - media::VideoPixelStorage storage_type, - const gfx::Size& dimensions, - base::Lock* lock) { - DVLOG(2) << "allocating GMB for " << dimensions.ToString(); - // BrowserGpuMemoryBufferManager::current() may not be accessed on IO Thread. - DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO)); - DCHECK(BrowserGpuMemoryBufferManager::current()); - // This class is only expected to be called with I420 buffer requests at this - // point. - DCHECK_EQ(format, media::PIXEL_FORMAT_I420); - set_pixel_format(format); - set_storage_type(storage_type); - set_pixel_count(dimensions.GetArea()); - // |dimensions| can be 0x0 for trackers that do not require memory backing. - if (dimensions.GetArea() == 0u) + gfx::GpuMemoryBufferHandle* new_handle) override { + DCHECK_LE(plane, static_cast(gpu_memory_buffers_.size())); + + const auto& current_gmb_handle = gpu_memory_buffers_[plane]->GetHandle(); + switch (current_gmb_handle.type) { + case gfx::EMPTY_BUFFER: + NOTREACHED(); + return false; + case gfx::SHARED_MEMORY_BUFFER: { + DCHECK(base::SharedMemory::IsHandleValid(current_gmb_handle.handle)); + base::SharedMemory shared_memory( + base::SharedMemory::DuplicateHandle(current_gmb_handle.handle), + false); + shared_memory.ShareToProcess(process_handle, &new_handle->handle); + DCHECK(base::SharedMemory::IsHandleValid(new_handle->handle)); + new_handle->type = gfx::SHARED_MEMORY_BUFFER; + return true; + } + case gfx::IO_SURFACE_BUFFER: + case gfx::SURFACE_TEXTURE_BUFFER: + case gfx::OZONE_NATIVE_PIXMAP: + *new_handle = current_gmb_handle; + return true; + } + NOTREACHED(); return true; - dimensions_ = dimensions; - - lock->Release(); - const size_t num_planes = media::VideoFrame::NumPlanes(pixel_format()); - for (size_t i = 0; i < num_planes; ++i) { - const gfx::Size& size = - media::VideoFrame::PlaneSize(pixel_format(), i, dimensions); - gpu_memory_buffers_.push_back( - BrowserGpuMemoryBufferManager::current()->AllocateGpuMemoryBuffer( - size, gfx::BufferFormat::R_8, - gfx::BufferUsage::GPU_READ_CPU_READ_WRITE)); - - DLOG_IF(ERROR, !gpu_memory_buffers_[i]) << "Allocating GpuMemoryBuffer"; - if (!gpu_memory_buffers_[i] || !gpu_memory_buffers_[i]->Map()) - return false; } - lock->Acquire(); - return true; -} - -bool VideoCaptureBufferPool::GpuMemoryBufferTracker::ShareToProcess2( - int plane, - base::ProcessHandle process_handle, - gfx::GpuMemoryBufferHandle* new_handle) { - DCHECK_LE(plane, static_cast(gpu_memory_buffers_.size())); - const auto& current_gmb_handle = gpu_memory_buffers_[plane]->GetHandle(); - switch (current_gmb_handle.type) { - case gfx::EMPTY_BUFFER: + private: + // A simple proxy that implements the BufferHandle interface, providing + // accessors to GpuMemoryBufferTracker's memory and properties. + class GpuMemoryBufferBufferHandle + : public VideoCaptureBufferPool::BufferHandle { + public: + // |tracker| must outlive GpuMemoryBufferBufferHandle. This is ensured since + // a tracker is pinned until ownership of this GpuMemoryBufferBufferHandle + // is returned to VideoCaptureBufferPool. + explicit GpuMemoryBufferBufferHandle(GpuMemoryBufferTracker* tracker) + : tracker_(tracker) {} + ~GpuMemoryBufferBufferHandle() override {} + + gfx::Size dimensions() const override { return tracker_->dimensions(); } + size_t mapped_size() const override { + return tracker_->dimensions().GetArea(); + } + void* data(int plane) override { + DCHECK_GE(plane, 0); + DCHECK_LT(plane, static_cast(tracker_->gpu_memory_buffers_.size())); + DCHECK(tracker_->gpu_memory_buffers_[plane]); + return tracker_->gpu_memory_buffers_[plane]->memory(0); + } + ClientBuffer AsClientBuffer(int plane) override { + DCHECK_GE(plane, 0); + DCHECK_LT(plane, static_cast(tracker_->gpu_memory_buffers_.size())); + return tracker_->gpu_memory_buffers_[plane]->AsClientBuffer(); + } +#if defined(OS_POSIX) && !defined(OS_MACOSX) + base::FileDescriptor AsPlatformFile() override { NOTREACHED(); - return false; - case gfx::SHARED_MEMORY_BUFFER: { - DCHECK(base::SharedMemory::IsHandleValid(current_gmb_handle.handle)); - base::SharedMemory shared_memory( - base::SharedMemory::DuplicateHandle(current_gmb_handle.handle), - false); - shared_memory.ShareToProcess(process_handle, &new_handle->handle); - DCHECK(base::SharedMemory::IsHandleValid(new_handle->handle)); - new_handle->type = gfx::SHARED_MEMORY_BUFFER; - return true; + return base::FileDescriptor(); } - case gfx::IO_SURFACE_BUFFER: - case gfx::SURFACE_TEXTURE_BUFFER: - case gfx::OZONE_NATIVE_PIXMAP: - *new_handle = current_gmb_handle; - return true; - } - NOTREACHED(); - return true; -} +#endif + + private: + GpuMemoryBufferTracker* const tracker_; + }; + + // Owned references to GpuMemoryBuffers. + std::vector> gpu_memory_buffers_; +}; // static scoped_ptr @@ -275,7 +241,8 @@ VideoCaptureBufferPool::Tracker::~Tracker() {} VideoCaptureBufferPool::VideoCaptureBufferPool(int count) : count_(count), - next_buffer_id_(0) { + next_buffer_id_(0), + last_relinquished_buffer_id_(kInvalidId) { DCHECK_GT(count, 0); } @@ -332,13 +299,12 @@ VideoCaptureBufferPool::GetBufferHandle(int buffer_id) { return tracker->GetBufferHandle(); } -int VideoCaptureBufferPool::ReserveForProducer( - media::VideoPixelFormat format, - media::VideoPixelStorage storage, - const gfx::Size& dimensions, - int* buffer_id_to_drop) { +int VideoCaptureBufferPool::ReserveForProducer(const gfx::Size& dimensions, + media::VideoPixelFormat format, + media::VideoPixelStorage storage, + int* buffer_id_to_drop) { base::AutoLock lock(lock_); - return ReserveForProducerInternal(format, storage, dimensions, + return ReserveForProducerInternal(dimensions, format, storage, buffer_id_to_drop); } @@ -351,6 +317,7 @@ void VideoCaptureBufferPool::RelinquishProducerReservation(int buffer_id) { } DCHECK(tracker->held_by_producer()); tracker->set_held_by_producer(false); + last_relinquished_buffer_id_ = buffer_id; } void VideoCaptureBufferPool::HoldForConsumers( @@ -385,6 +352,34 @@ void VideoCaptureBufferPool::RelinquishConsumerHold(int buffer_id, num_clients); } +int VideoCaptureBufferPool::ResurrectLastForProducer( + const gfx::Size& dimensions, + media::VideoPixelFormat format, + media::VideoPixelStorage storage) { + base::AutoLock lock(lock_); + + // Return early if the last relinquished buffer has been re-used already. + if (last_relinquished_buffer_id_ == kInvalidId) + return kInvalidId; + + // If there are no consumers reading from this buffer, then it's safe to + // provide this buffer back to the producer (because the producer may + // potentially modify the content). Check that the expected dimensions, + // format, and storage match. + TrackerMap::iterator it = trackers_.find(last_relinquished_buffer_id_); + DCHECK(it != trackers_.end()); + DCHECK(!it->second->held_by_producer()); + if (it->second->consumer_hold_count() == 0 && + it->second->dimensions() == dimensions && + it->second->pixel_format() == format && + it->second->storage_type() == storage) { + it->second->set_held_by_producer(true); + return last_relinquished_buffer_id_; + } + + return kInvalidId; +} + double VideoCaptureBufferPool::GetBufferPoolUtilization() const { base::AutoLock lock(lock_); int num_buffers_held = 0; @@ -397,9 +392,9 @@ double VideoCaptureBufferPool::GetBufferPoolUtilization() const { } int VideoCaptureBufferPool::ReserveForProducerInternal( + const gfx::Size& dimensions, media::VideoPixelFormat pixel_format, media::VideoPixelStorage storage_type, - const gfx::Size& dimensions, int* buffer_id_to_drop) { lock_.AssertAcquired(); @@ -408,32 +403,50 @@ int VideoCaptureBufferPool::ReserveForProducerInternal( // largest one that's not big enough, in case we have to reallocate a tracker. *buffer_id_to_drop = kInvalidId; size_t largest_size_in_pixels = 0; + TrackerMap::iterator tracker_of_last_resort = trackers_.end(); TrackerMap::iterator tracker_to_drop = trackers_.end(); for (TrackerMap::iterator it = trackers_.begin(); it != trackers_.end(); ++it) { Tracker* const tracker = it->second; if (!tracker->consumer_hold_count() && !tracker->held_by_producer()) { - if (tracker->pixel_count() >= size_in_pixels && + if (tracker->max_pixel_count() >= size_in_pixels && (tracker->pixel_format() == pixel_format) && (tracker->storage_type() == storage_type)) { + if (it->first == last_relinquished_buffer_id_) { + // This buffer would do just fine, but avoid returning it because the + // client may want to resurrect it. It will be returned perforce if + // the pool has reached it's maximum limit (see code below). + tracker_of_last_resort = it; + continue; + } // Existing tracker is big enough and has correct format. Reuse it. + tracker->set_dimensions(dimensions); tracker->set_held_by_producer(true); return it->first; } - if (tracker->pixel_count() > largest_size_in_pixels) { - largest_size_in_pixels = tracker->pixel_count(); + if (tracker->max_pixel_count() > largest_size_in_pixels) { + largest_size_in_pixels = tracker->max_pixel_count(); tracker_to_drop = it; } } } // Preferably grow the pool by creating a new tracker. If we're at maximum - // size, then reallocate by deleting an existing one instead. + // size, then try using |tracker_of_last_resort| or reallocate by deleting an + // existing one instead. if (trackers_.size() == static_cast(count_)) { + if (tracker_of_last_resort != trackers_.end()) { + last_relinquished_buffer_id_ = kInvalidId; + tracker_of_last_resort->second->set_dimensions(dimensions); + tracker_of_last_resort->second->set_held_by_producer(true); + return tracker_of_last_resort->first; + } if (tracker_to_drop == trackers_.end()) { // We're out of space, and can't find an unused tracker to reallocate. return kInvalidId; } + if (tracker_to_drop->first == last_relinquished_buffer_id_) + last_relinquished_buffer_id_ = kInvalidId; *buffer_id_to_drop = tracker_to_drop->first; delete tracker_to_drop->second; trackers_.erase(tracker_to_drop); @@ -445,7 +458,7 @@ int VideoCaptureBufferPool::ReserveForProducerInternal( scoped_ptr tracker = Tracker::CreateTracker(storage_type); // TODO(emircan): We pass the lock here to solve GMB allocation issue, see // crbug.com/545238. - if (!tracker->Init(pixel_format, storage_type, dimensions, &lock_)) { + if (!tracker->Init(dimensions, pixel_format, storage_type, &lock_)) { DLOG(ERROR) << "Error initializing Tracker"; return kInvalidId; } diff --git a/content/browser/renderer_host/media/video_capture_buffer_pool.h b/content/browser/renderer_host/media/video_capture_buffer_pool.h index e5e4e2f..bc86447 100644 --- a/content/browser/renderer_host/media/video_capture_buffer_pool.h +++ b/content/browser/renderer_host/media/video_capture_buffer_pool.h @@ -91,9 +91,9 @@ class CONTENT_EXPORT VideoCaptureBufferPool // On occasion, this call will decide to free an old buffer to make room for a // new allocation at a larger size. If so, the ID of the destroyed buffer is // returned via |buffer_id_to_drop|. - int ReserveForProducer(media::VideoPixelFormat format, + int ReserveForProducer(const gfx::Size& dimensions, + media::VideoPixelFormat format, media::VideoPixelStorage storage, - const gfx::Size& dimensions, int* buffer_id_to_drop); // Indicate that a buffer held for the producer should be returned back to the @@ -111,6 +111,18 @@ class CONTENT_EXPORT VideoCaptureBufferPool // done, a buffer is returned to the pool for reuse. void RelinquishConsumerHold(int buffer_id, int num_clients); + // Attempt to reserve the same buffer that was relinquished in the last call + // to RelinquishProducerReservation(). If the buffer is not still being + // consumed, and has not yet been re-used since being consumed, and the + // specified |dimensions|, |format|, and |storage| agree with its last + // reservation, this will succeed. Otherwise, |kInvalidId| will be returned. + // + // A producer may assume the content of the buffer has been preserved and may + // also make modifications. + int ResurrectLastForProducer(const gfx::Size& dimensions, + media::VideoPixelFormat format, + media::VideoPixelStorage storage); + // Returns a snapshot of the current number of buffers in-use divided by the // maximum |count_|. double GetBufferPoolUtilization() const; @@ -125,15 +137,19 @@ class CONTENT_EXPORT VideoCaptureBufferPool static scoped_ptr CreateTracker(media::VideoPixelStorage storage); Tracker() - : pixel_count_(0), held_by_producer_(false), consumer_hold_count_(0) {} - virtual bool Init(media::VideoPixelFormat format, + : max_pixel_count_(0), + held_by_producer_(false), + consumer_hold_count_(0) {} + virtual bool Init(const gfx::Size& dimensions, + media::VideoPixelFormat format, media::VideoPixelStorage storage_type, - const gfx::Size& dimensions, base::Lock* lock) = 0; virtual ~Tracker(); - size_t pixel_count() const { return pixel_count_; } - void set_pixel_count(size_t count) { pixel_count_ = count; } + const gfx::Size& dimensions() const { return dimensions_; } + void set_dimensions(const gfx::Size& dim) { dimensions_ = dim; } + size_t max_pixel_count() const { return max_pixel_count_; } + void set_max_pixel_count(size_t count) { max_pixel_count_ = count; } media::VideoPixelFormat pixel_format() const { return pixel_format_; } @@ -160,11 +176,17 @@ class CONTENT_EXPORT VideoCaptureBufferPool gfx::GpuMemoryBufferHandle* new_handle) = 0; private: - size_t pixel_count_; + // |dimensions_| may change as a Tracker is re-used, but |max_pixel_count_|, + // |pixel_format_|, and |storage_type_| are set once for the lifetime of a + // Tracker. + gfx::Size dimensions_; + size_t max_pixel_count_; media::VideoPixelFormat pixel_format_; media::VideoPixelStorage storage_type_; + // Indicates whether this Tracker is currently referenced by the producer. bool held_by_producer_; + // Number of consumer processes which hold this Tracker. int consumer_hold_count_; }; @@ -172,9 +194,9 @@ class CONTENT_EXPORT VideoCaptureBufferPool friend class base::RefCountedThreadSafe; virtual ~VideoCaptureBufferPool(); - int ReserveForProducerInternal(media::VideoPixelFormat format, + int ReserveForProducerInternal(const gfx::Size& dimensions, + media::VideoPixelFormat format, media::VideoPixelStorage storage, - const gfx::Size& dimensions, int* tracker_id_to_drop); Tracker* GetTracker(int buffer_id); @@ -188,6 +210,10 @@ class CONTENT_EXPORT VideoCaptureBufferPool // The ID of the next buffer. int next_buffer_id_; + // The ID of the buffer last relinquished by the producer (a candidate for + // resurrection). + int last_relinquished_buffer_id_; + // The buffers, indexed by the first parameter, a buffer id. using TrackerMap = std::map; TrackerMap trackers_; diff --git a/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc b/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc index 21c6132..f06837e 100644 --- a/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc +++ b/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc @@ -9,7 +9,9 @@ #include #include #include + #include +#include #include "base/bind.h" #include "base/macros.h" @@ -175,8 +177,8 @@ class VideoCaptureBufferPoolTest << media::VideoPixelFormatToString(format_and_storage.pixel_format) << " " << dimensions.ToString(); const int buffer_id = pool_->ReserveForProducer( - format_and_storage.pixel_format, format_and_storage.pixel_storage, - dimensions, &buffer_id_to_drop); + dimensions, format_and_storage.pixel_format, + format_and_storage.pixel_storage, &buffer_id_to_drop); if (buffer_id == VideoCaptureBufferPool::kInvalidId) return scoped_ptr(); EXPECT_EQ(expected_dropped_id_, buffer_id_to_drop); @@ -187,6 +189,18 @@ class VideoCaptureBufferPoolTest new Buffer(pool_, std::move(buffer_handle), buffer_id)); } + scoped_ptr ResurrectLastBuffer( + const gfx::Size& dimensions, + PixelFormatAndStorage format_and_storage) { + const int buffer_id = pool_->ResurrectLastForProducer( + dimensions, format_and_storage.pixel_format, + format_and_storage.pixel_storage); + if (buffer_id == VideoCaptureBufferPool::kInvalidId) + return scoped_ptr(); + return scoped_ptr( + new Buffer(pool_, pool_->GetBufferHandle(buffer_id), buffer_id)); + } + base::MessageLoop loop_; int expected_dropped_id_; scoped_refptr pool_; @@ -371,6 +385,135 @@ TEST_P(VideoCaptureBufferPoolTest, BufferPool) { buffer4.reset(); } +// Tests that a previously-released buffer can be immediately resurrected under +// normal conditions. +TEST_P(VideoCaptureBufferPoolTest, ResurrectsLastBuffer) { + ExpectDroppedId(VideoCaptureBufferPool::kInvalidId); + + // At the start, there should be nothing to resurrect. + scoped_ptr resurrected = + ResurrectLastBuffer(gfx::Size(10, 10), GetParam()); + ASSERT_EQ(nullptr, resurrected.get()); + + // Reserve a 10x10 buffer and fill it with 0xab values. + scoped_ptr original = ReserveBuffer(gfx::Size(10, 10), GetParam()); + ASSERT_NE(nullptr, original.get()); + const size_t original_mapped_size = original->mapped_size(); + memset(original->data(), 0xab, original_mapped_size); + + // Try to resurrect a buffer BEFORE releasing |original|. This should fail. + resurrected = ResurrectLastBuffer(gfx::Size(10, 10), GetParam()); + ASSERT_EQ(nullptr, resurrected.get()); + + // Release |original| and then try to resurrect it. Confirm the content of + // the resurrected buffer is a fill of 0xab values. + original.reset(); + resurrected = ResurrectLastBuffer(gfx::Size(10, 10), GetParam()); + ASSERT_NE(nullptr, resurrected.get()); + ASSERT_EQ(original_mapped_size, resurrected->mapped_size()); + uint8_t* resurrected_memory = reinterpret_cast(resurrected->data()); + for (size_t i = 0; i < original_mapped_size; ++i) + EXPECT_EQ(0xab, resurrected_memory[i]) << "Mismatch at byte offset: " << i; + + // Now, fill the resurrected buffer with 0xbc values and release it. + memset(resurrected_memory, 0xbc, original_mapped_size); + resurrected.reset(); + + // Finally, resurrect the buffer again, and confirm it contains a fill of 0xbc + // values. + resurrected = ResurrectLastBuffer(gfx::Size(10, 10), GetParam()); + ASSERT_NE(nullptr, resurrected.get()); + ASSERT_EQ(original_mapped_size, resurrected->mapped_size()); + resurrected_memory = reinterpret_cast(resurrected->data()); + for (size_t i = 0; i < original_mapped_size; ++i) + EXPECT_EQ(0xbc, resurrected_memory[i]) << "Mismatch at byte offset: " << i; +} + +// Tests that a buffer cannot be resurrected if its properties do not match. +TEST_P(VideoCaptureBufferPoolTest, DoesNotResurrectIfPropertiesNotMatched) { + ExpectDroppedId(VideoCaptureBufferPool::kInvalidId); + + // Reserve a 10x10 buffer, fill it with 0xcd values, and release it. + scoped_ptr original = ReserveBuffer(gfx::Size(10, 10), GetParam()); + ASSERT_NE(nullptr, original.get()); + const size_t original_mapped_size = original->mapped_size(); + memset(original->data(), 0xcd, original_mapped_size); + original.reset(); + + // Expect that the buffer cannot be resurrected if the dimensions do not + // match. + scoped_ptr resurrected = + ResurrectLastBuffer(gfx::Size(8, 8), GetParam()); + ASSERT_EQ(nullptr, resurrected.get()); + + // Expect that the buffer cannot be resurrected if the pixel format does not + // match. + PixelFormatAndStorage altered_format_or_storage = GetParam(); + altered_format_or_storage.pixel_format = + (altered_format_or_storage.pixel_format == media::PIXEL_FORMAT_I420 + ? media::PIXEL_FORMAT_ARGB + : media::PIXEL_FORMAT_I420); + resurrected = + ResurrectLastBuffer(gfx::Size(10, 10), altered_format_or_storage); + ASSERT_EQ(nullptr, resurrected.get()); + + // Expect that the buffer cannot be resurrected if the pixel storage does not + // match. + altered_format_or_storage = GetParam(); + altered_format_or_storage.pixel_storage = + (altered_format_or_storage.pixel_storage == media::PIXEL_STORAGE_CPU) + ? media::PIXEL_STORAGE_GPUMEMORYBUFFER + : media::PIXEL_STORAGE_CPU; + resurrected = + ResurrectLastBuffer(gfx::Size(10, 10), altered_format_or_storage); + ASSERT_EQ(nullptr, resurrected.get()); + + // Finally, check that the buffer CAN be resurrected if all properties match. + resurrected = ResurrectLastBuffer(gfx::Size(10, 10), GetParam()); + ASSERT_NE(nullptr, resurrected.get()); + ASSERT_EQ(original_mapped_size, resurrected->mapped_size()); + uint8_t* resurrected_memory = reinterpret_cast(resurrected->data()); + for (size_t i = 0; i < original_mapped_size; ++i) + EXPECT_EQ(0xcd, resurrected_memory[i]) << "Mismatch at byte offset: " << i; +} + +// Tests that the buffers are managed by the pool such that the last-released +// buffer is kept around as long as possible (for successful resurrection). +TEST_P(VideoCaptureBufferPoolTest, AvoidsClobberingForResurrectingLastBuffer) { + ExpectDroppedId(VideoCaptureBufferPool::kInvalidId); + + // Reserve a 10x10 buffer, fill it with 0xde values, and release it. + scoped_ptr original = ReserveBuffer(gfx::Size(10, 10), GetParam()); + ASSERT_NE(nullptr, original.get()); + const size_t original_mapped_size = original->mapped_size(); + memset(original->data(), 0xde, original_mapped_size); + original.reset(); + + // Reserve all but one of the pool's buffers. + std::vector> held_buffers; + for (int i = 0; i < kTestBufferPoolSize - 1; ++i) { + held_buffers.push_back(ReserveBuffer(gfx::Size(10, 10), GetParam())); + ASSERT_NE(nullptr, held_buffers.back().get()); + } + + // Now, attempt to resurrect the original buffer. This should succeed. + scoped_ptr resurrected = + ResurrectLastBuffer(gfx::Size(10, 10), GetParam()); + ASSERT_NE(nullptr, resurrected.get()); + ASSERT_EQ(original_mapped_size, resurrected->mapped_size()); + uint8_t* resurrected_memory = reinterpret_cast(resurrected->data()); + for (size_t i = 0; i < original_mapped_size; ++i) + EXPECT_EQ(0xde, resurrected_memory[i]) << "Mismatch at byte offset: " << i; + resurrected.reset(); + + // Reserve the final buffer in the pool, and then confirm resurrection does + // not succeed. + held_buffers.push_back(ReserveBuffer(gfx::Size(10, 10), GetParam())); + ASSERT_NE(nullptr, held_buffers.back().get()); + resurrected = ResurrectLastBuffer(gfx::Size(10, 10), GetParam()); + ASSERT_EQ(nullptr, resurrected.get()); +} + INSTANTIATE_TEST_CASE_P(, VideoCaptureBufferPoolTest, testing::ValuesIn(kCapturePixelFormatAndStorages)); diff --git a/content/browser/renderer_host/media/video_capture_controller_unittest.cc b/content/browser/renderer_host/media/video_capture_controller_unittest.cc index 99e563f..cdd8c16 100644 --- a/content/browser/renderer_host/media/video_capture_controller_unittest.cc +++ b/content/browser/renderer_host/media/video_capture_controller_unittest.cc @@ -331,6 +331,7 @@ TEST_F(VideoCaptureControllerTest, NormalCaptureMultipleClients) { base::RunLoop().RunUntilIdle(); Mock::VerifyAndClearExpectations(client_a_.get()); Mock::VerifyAndClearExpectations(client_b_.get()); + // Expect VideoCaptureController set the metadata in |video_frame| to hold a // resource utilization of 0.5 (the largest of all reported values). double resource_utilization_in_metadata = -1.0; @@ -359,15 +360,27 @@ TEST_F(VideoCaptureControllerTest, NormalCaptureMultipleClients) { base::TimeTicks()); // The buffer should be delivered to the clients in any order. - EXPECT_CALL(*client_a_, - DoI420BufferReady(client_a_route_1, capture_resolution)) - .Times(1); - EXPECT_CALL(*client_b_, - DoI420BufferReady(client_b_route_1, capture_resolution)) - .Times(1); - EXPECT_CALL(*client_a_, - DoI420BufferReady(client_a_route_2, capture_resolution)) - .Times(1); + { + InSequence s; + EXPECT_CALL(*client_a_, DoBufferCreated(client_a_route_1)).Times(1); + EXPECT_CALL(*client_a_, + DoI420BufferReady(client_a_route_1, capture_resolution)) + .Times(1); + } + { + InSequence s; + EXPECT_CALL(*client_b_, DoBufferCreated(client_b_route_1)).Times(1); + EXPECT_CALL(*client_b_, + DoI420BufferReady(client_b_route_1, capture_resolution)) + .Times(1); + } + { + InSequence s; + EXPECT_CALL(*client_a_, DoBufferCreated(client_a_route_2)).Times(1); + EXPECT_CALL(*client_a_, + DoI420BufferReady(client_a_route_2, capture_resolution)) + .Times(1); + } base::RunLoop().RunUntilIdle(); Mock::VerifyAndClearExpectations(client_a_.get()); Mock::VerifyAndClearExpectations(client_b_.get()); @@ -407,23 +420,24 @@ TEST_F(VideoCaptureControllerTest, NormalCaptureMultipleClients) { media::PIXEL_FORMAT_I420, media::PIXEL_STORAGE_CPU).get()); - // The new client needs to be told of 3 buffers; the old clients only 2. + // The new client needs to be notified of the creation of |kPoolSize| buffers; + // the old clients only |kPoolSize - 2|. EXPECT_CALL(*client_b_, DoBufferCreated(client_b_route_2)).Times(kPoolSize); EXPECT_CALL(*client_b_, DoI420BufferReady(client_b_route_2, capture_resolution)) .Times(kPoolSize); EXPECT_CALL(*client_a_, DoBufferCreated(client_a_route_1)) - .Times(kPoolSize - 1); + .Times(kPoolSize - 2); EXPECT_CALL(*client_a_, DoI420BufferReady(client_a_route_1, capture_resolution)) .Times(kPoolSize); EXPECT_CALL(*client_a_, DoBufferCreated(client_a_route_2)) - .Times(kPoolSize - 1); + .Times(kPoolSize - 2); EXPECT_CALL(*client_a_, DoI420BufferReady(client_a_route_2, capture_resolution)) .Times(kPoolSize); EXPECT_CALL(*client_b_, DoBufferCreated(client_b_route_1)) - .Times(kPoolSize - 1); + .Times(kPoolSize - 2); EXPECT_CALL(*client_b_, DoI420BufferReady(client_b_route_1, capture_resolution)) .Times(kPoolSize); diff --git a/content/browser/renderer_host/media/video_capture_device_client.cc b/content/browser/renderer_host/media/video_capture_device_client.cc index d5cb149..4d508c4 100644 --- a/content/browser/renderer_host/media/video_capture_device_client.cc +++ b/content/browser/renderer_host/media/video_capture_device_client.cc @@ -270,21 +270,17 @@ VideoCaptureDeviceClient::ReserveOutputBuffer( // it's a ShMem GMB or a DmaBuf GMB. int buffer_id_to_drop = VideoCaptureBufferPool::kInvalidId; const int buffer_id = buffer_pool_->ReserveForProducer( - pixel_format, pixel_storage, frame_size, &buffer_id_to_drop); - if (buffer_id == VideoCaptureBufferPool::kInvalidId) - return NULL; - - scoped_ptr output_buffer( - new AutoReleaseBuffer(buffer_pool_, buffer_id)); - + frame_size, pixel_format, pixel_storage, &buffer_id_to_drop); if (buffer_id_to_drop != VideoCaptureBufferPool::kInvalidId) { BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&VideoCaptureController::DoBufferDestroyedOnIOThread, controller_, buffer_id_to_drop)); } - - return output_buffer; + if (buffer_id == VideoCaptureBufferPool::kInvalidId) + return nullptr; + return make_scoped_ptr( + new AutoReleaseBuffer(buffer_pool_, buffer_id)); } void VideoCaptureDeviceClient::OnIncomingCapturedBuffer( @@ -340,6 +336,19 @@ void VideoCaptureDeviceClient::OnIncomingCapturedVideoFrame( timestamp)); } +scoped_ptr +VideoCaptureDeviceClient::ResurrectLastOutputBuffer( + const gfx::Size& dimensions, + media::VideoPixelFormat format, + media::VideoPixelStorage storage) { + const int buffer_id = + buffer_pool_->ResurrectLastForProducer(dimensions, format, storage); + if (buffer_id == VideoCaptureBufferPool::kInvalidId) + return nullptr; + return make_scoped_ptr( + new AutoReleaseBuffer(buffer_pool_, buffer_id)); +} + void VideoCaptureDeviceClient::OnError( const tracked_objects::Location& from_here, const std::string& reason) { diff --git a/content/browser/renderer_host/media/video_capture_device_client.h b/content/browser/renderer_host/media/video_capture_device_client.h index 1b982a2..0273c32 100644 --- a/content/browser/renderer_host/media/video_capture_device_client.h +++ b/content/browser/renderer_host/media/video_capture_device_client.h @@ -60,6 +60,10 @@ class CONTENT_EXPORT VideoCaptureDeviceClient scoped_ptr buffer, const scoped_refptr& frame, const base::TimeTicks& timestamp) override; + scoped_ptr ResurrectLastOutputBuffer( + const gfx::Size& dimensions, + media::VideoPixelFormat format, + media::VideoPixelStorage storage) override; void OnError(const tracked_objects::Location& from_here, const std::string& reason) override; void OnLog(const std::string& message) override; diff --git a/media/capture/video/fake_video_capture_device_unittest.cc b/media/capture/video/fake_video_capture_device_unittest.cc index eff85fc..a33eb3f 100644 --- a/media/capture/video/fake_video_capture_device_unittest.cc +++ b/media/capture/video/fake_video_capture_device_unittest.cc @@ -101,7 +101,12 @@ class MockClient : public VideoCaptureDevice::Client { PIXEL_FORMAT_I420); frame_cb_.Run(format); } - + scoped_ptr ResurrectLastOutputBuffer( + const gfx::Size& dimensions, + media::VideoPixelFormat format, + media::VideoPixelStorage storage) { + return scoped_ptr(); + } double GetBufferPoolUtilization() const override { return 0.0; } private: diff --git a/media/capture/video/video_capture_device.h b/media/capture/video/video_capture_device.h index 7d8ac1f..71a4f3e 100644 --- a/media/capture/video/video_capture_device.h +++ b/media/capture/video/video_capture_device.h @@ -242,6 +242,16 @@ class MEDIA_EXPORT VideoCaptureDevice { const scoped_refptr& frame, const base::TimeTicks& timestamp) = 0; + // Attempts to reserve the same Buffer provided in the last call to one of + // the OnIncomingCapturedXXX() methods. This will fail if the content of the + // Buffer has not been preserved, or if the |dimensions|, |format|, or + // |storage| disagree with how it was reserved via ReserveOutputBuffer(). + // When this operation fails, nullptr will be returned. + virtual scoped_ptr ResurrectLastOutputBuffer( + const gfx::Size& dimensions, + VideoPixelFormat format, + VideoPixelStorage storage) = 0; + // An error has occurred that cannot be handled and VideoCaptureDevice must // be StopAndDeAllocate()-ed. |reason| is a text description of the error. virtual void OnError(const tracked_objects::Location& from_here, @@ -257,18 +267,36 @@ class MEDIA_EXPORT VideoCaptureDevice { virtual ~VideoCaptureDevice(); - // Prepares the camera for use. After this function has been called no other - // applications can use the camera. StopAndDeAllocate() must be called before - // the object is deleted. + // Prepares the video capturer for use. StopAndDeAllocate() must be called + // before the object is deleted. virtual void AllocateAndStart(const VideoCaptureParams& params, scoped_ptr client) = 0; - // Deallocates the camera, possibly asynchronously. + // In cases where the video capturer self-pauses (e.g., a screen capturer + // where the screen's content has not changed in a while), consumers may call + // this to request a "refresh frame" be delivered to the Client. This is used + // in a number of circumstances, such as: + // + // 1. An additional consumer of video frames is starting up and requires a + // first frame (as opposed to not receiving a frame for an indeterminate + // amount of time). + // 2. A few repeats of the same frame would allow a lossy video encoder to + // improve the video quality of unchanging content. + // + // The default implementation is a no-op. VideoCaptureDevice implementations + // are not required to honor this request, especially if they do not + // self-pause and/or if honoring the request would cause them to exceed their + // configured maximum frame rate. Any VideoCaptureDevice that does self-pause, + // however, should provide an implementation of this method that makes + // reasonable attempts to honor these requests. + virtual void RequestRefreshFrame() {} + + // Deallocates the video capturer, possibly asynchronously. // // This call requires the device to do the following things, eventually: put - // camera hardware into a state where other applications could use it, free - // the memory associated with capture, and delete the |client| pointer passed - // into AllocateAndStart. + // hardware into a state where other applications could use it, free the + // memory associated with capture, and delete the |client| pointer passed into + // AllocateAndStart. // // If deallocation is done asynchronously, then the device implementation must // ensure that a subsequent AllocateAndStart() operation targeting the same ID diff --git a/media/capture/video/video_capture_device_unittest.cc b/media/capture/video/video_capture_device_unittest.cc index 97b4835..ae34068 100644 --- a/media/capture/video/video_capture_device_unittest.cc +++ b/media/capture/video/video_capture_device_unittest.cc @@ -85,6 +85,7 @@ class MockClient : public VideoCaptureDevice::Client { MOCK_METHOD0(DoReserveOutputBuffer, void(void)); MOCK_METHOD0(DoOnIncomingCapturedBuffer, void(void)); MOCK_METHOD0(DoOnIncomingCapturedVideoFrame, void(void)); + MOCK_METHOD0(DoResurrectLastOutputBuffer, void(void)); MOCK_METHOD2(OnError, void(const tracked_objects::Location& from_here, const std::string& reason)); @@ -123,6 +124,14 @@ class MockClient : public VideoCaptureDevice::Client { const base::TimeTicks& timestamp) override { DoOnIncomingCapturedVideoFrame(); } + scoped_ptr ResurrectLastOutputBuffer( + const gfx::Size& dimensions, + media::VideoPixelFormat format, + media::VideoPixelStorage storage) { + DoResurrectLastOutputBuffer(); + NOTREACHED() << "This should never be called"; + return scoped_ptr(); + } private: scoped_refptr main_thread_; -- cgit v1.1