| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/media/capture/fake_video_capture_stack.h" |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/sequence_checker.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/thread_annotations.h" |
| #include "base/time/time.h" |
| #include "components/viz/common/resources/shared_image_format.h" |
| #include "content/browser/browser_main_loop.h" |
| #include "content/browser/media/capture/frame_test_util.h" |
| #include "content/test/gpu_browsertest_helpers.h" |
| #include "gpu/command_buffer/common/mailbox_holder.h" |
| #include "gpu/ipc/client/client_shared_image_interface.h" |
| #include "gpu/ipc/client/gpu_channel_host.h" |
| #include "media/base/video_frame.h" |
| #include "media/base/video_types.h" |
| #include "media/base/video_util.h" |
| #include "media/capture/video/video_frame_receiver.h" |
| #include "media/capture/video_capture_types.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkColorSpace.h" |
| #include "ui/gfx/geometry/rect.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| scoped_refptr<gpu::ClientSharedImageInterface> GetSharedImageInterface() { |
| auto gpu_channel = GpuBrowsertestEstablishGpuChannelSyncRunLoop(); |
| return gpu_channel->CreateClientSharedImageInterface(); |
| } |
| |
| } // namespace |
| |
| FakeVideoCaptureStack::FakeVideoCaptureStack() = default; |
| |
| FakeVideoCaptureStack::~FakeVideoCaptureStack() = default; |
| |
| void FakeVideoCaptureStack::Reset() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| frames_.clear(); |
| last_frame_timestamp_ = base::TimeDelta::Min(); |
| } |
| |
| // A minimal implementation of VideoFrameReceiver that wraps buffers into |
| // VideoFrame instances and forwards all relevant callbacks and data to the |
| // parent FakeVideoCaptureStack. The implemented `media::VideoFrameReceiver` |
| // methods will be forwarded to a separate thread for processing in order to, |
| // unblock the thread on which they arrive, and the results of this processing |
| // will then be posted to the `FakeVideoCaptureStack` owning this instance, |
| // on the task runner that constructed this instance. |
| class FakeVideoCaptureStackReceiver final : public media::VideoFrameReceiver { |
| public: |
| // Creates VideoFrameReceiver that notifies `capture_stack` when new frames |
| // arrive (among other things). The `capture_stack` will be called into from |
| // the current sequence. |
| explicit FakeVideoCaptureStackReceiver( |
| base::WeakPtr<FakeVideoCaptureStack> capture_stack) |
| : capture_stack_(std::move(capture_stack)), |
| capture_stack_task_runner_( |
| base::SequencedTaskRunner::GetCurrentDefault()) { |
| DETACH_FROM_SEQUENCE(receiver_sequence_checker_); |
| |
| // This will DCHECK if we're not currently on UI thread (due to use of |
| // `content::BrowserMainLoop::GetInstance()` in the impl.), but that is |
| // fine since the tests currently call us on UI thread: |
| sii_ = GetSharedImageInterface(); |
| receiver_thread_ = |
| std::make_unique<base::Thread>("fake-capture-stack-receiver"); |
| receiver_thread_->StartAndWaitForTesting(); |
| } |
| |
| void WaitForReceiver() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(capture_stack_sequence_checker_); |
| |
| base::RunLoop runLoop(base::RunLoop::Type::kNestableTasksAllowed); |
| |
| receiver_thread_->task_runner()->PostTask(FROM_HERE, runLoop.QuitClosure()); |
| |
| runLoop.Run(); |
| } |
| |
| base::WeakPtr<FakeVideoCaptureStackReceiver> GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| FakeVideoCaptureStackReceiver(const FakeVideoCaptureStackReceiver&) = delete; |
| FakeVideoCaptureStackReceiver& operator=( |
| const FakeVideoCaptureStackReceiver&) = delete; |
| |
| ~FakeVideoCaptureStackReceiver() override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(capture_stack_sequence_checker_); |
| |
| receiver_thread_ = nullptr; |
| } |
| |
| private: |
| using Buffer = media::VideoCaptureDevice::Client::Buffer; |
| |
| void OnCaptureConfigurationChanged() override {} |
| |
| void OnNewBuffer(int buffer_id, |
| media::mojom::VideoBufferHandlePtr buffer_handle) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(capture_stack_sequence_checker_); |
| |
| // Unretained is safe since we own the thread to which we're posting. |
| receiver_thread_->task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &FakeVideoCaptureStackReceiver::OnNewBufferOnReceiverThread, |
| base::Unretained(this), buffer_id, std::move(buffer_handle))); |
| } |
| |
| void OnNewBufferOnReceiverThread( |
| int buffer_id, |
| media::mojom::VideoBufferHandlePtr buffer_handle) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(receiver_sequence_checker_); |
| |
| buffers_[buffer_id] = std::move(buffer_handle); |
| } |
| |
| scoped_refptr<media::VideoFrame> GetVideoFrameFromSharedMemory( |
| media::ReadyFrameInBuffer frame, |
| base::ReadOnlySharedMemoryMapping mapping) { |
| CHECK(mapping.IsValid()); |
| |
| const auto& frame_format = media::VideoCaptureFormat( |
| frame.frame_info->coded_size, 0.0f, frame.frame_info->pixel_format); |
| CHECK_LE(media::VideoFrame::AllocationSize(frame_format.pixel_format, |
| frame_format.frame_size), |
| mapping.size()); |
| |
| auto video_frame = media::VideoFrame::WrapExternalData( |
| frame.frame_info->pixel_format, frame.frame_info->coded_size, |
| frame.frame_info->visible_rect, frame.frame_info->visible_rect.size(), |
| mapping, frame.frame_info->timestamp); |
| CHECK(video_frame); |
| |
| video_frame->set_metadata(frame.frame_info->metadata); |
| video_frame->set_color_space(frame.frame_info->color_space); |
| |
| // This destruction observer will unmap the shared memory when the |
| // VideoFrame goes out-of-scope. |
| video_frame->AddDestructionObserver(base::BindOnce( |
| [](base::ReadOnlySharedMemoryMapping) {}, std::move(mapping))); |
| // This destruction observer will notify the video capture device once all |
| // downstream code is done using the VideoFrame. |
| video_frame->AddDestructionObserver(base::BindOnce( |
| [](std::unique_ptr<Buffer::ScopedAccessPermission> access) {}, |
| std::move(frame.buffer_read_permission))); |
| |
| return video_frame; |
| } |
| |
| scoped_refptr<media::VideoFrame> GetVideoFrameFromGMBHandle( |
| media::ReadyFrameInBuffer frame, |
| const gfx::GpuMemoryBufferHandle& gmb_handle) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(receiver_sequence_checker_); |
| |
| CHECK(!gmb_handle.is_null()); |
| CHECK_EQ(frame.frame_info->pixel_format, |
| media::VideoPixelFormat::PIXEL_FORMAT_NV12); |
| |
| const auto si_usage = gpu::SHARED_IMAGE_USAGE_DISPLAY_READ; |
| CHECK(sii_); |
| auto shared_image = sii_->CreateSharedImage( |
| {viz::MultiPlaneFormat::kNV12, frame.frame_info->coded_size, |
| gfx::ColorSpace(), gpu::SharedImageUsageSet(si_usage), |
| "FakeVideoCaptureStack"}, |
| gpu::kNullSurfaceHandle, gfx::BufferUsage::SCANOUT_VEA_CPU_READ, |
| gmb_handle.Clone()); |
| auto video_frame = media::VideoFrame::WrapMappableSharedImage( |
| std::move(shared_image), sii_->GenVerifiedSyncToken(), |
| base::NullCallback(), frame.frame_info->visible_rect, |
| frame.frame_info->coded_size, frame.frame_info->timestamp); |
| CHECK(video_frame); |
| |
| video_frame->set_metadata(frame.frame_info->metadata); |
| video_frame->set_color_space(frame.frame_info->color_space); |
| |
| auto mapped_frame = media::ConvertToMemoryMappedFrame(video_frame); |
| CHECK(mapped_frame); |
| |
| // This destruction observer will notify the video capture device once all |
| // downstream code is done using the VideoFrame. |
| mapped_frame->AddDestructionObserver(base::BindOnce( |
| [](std::unique_ptr<Buffer::ScopedAccessPermission> access) {}, |
| std::move(frame.buffer_read_permission))); |
| |
| return mapped_frame; |
| } |
| |
| void OnFrameReadyInBuffer(media::ReadyFrameInBuffer frame) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(capture_stack_sequence_checker_); |
| |
| // Unretained is safe since we own the thread to which we're posting. |
| // This implementation does not forward scaled frames. |
| receiver_thread_->task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&FakeVideoCaptureStackReceiver:: |
| OnFrameReadyInBufferOnReceiverThread, |
| base::Unretained(this), std::move(frame))); |
| } |
| |
| void OnFrameReadyInBufferOnReceiverThread(media::ReadyFrameInBuffer frame) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(receiver_sequence_checker_); |
| |
| const auto it = buffers_.find(frame.buffer_id); |
| CHECK(it != buffers_.end()); |
| |
| CHECK(it->second->is_read_only_shmem_region() || |
| it->second->is_gpu_memory_buffer_handle()); |
| |
| scoped_refptr<media::VideoFrame> video_frame = nullptr; |
| if (it->second->is_read_only_shmem_region()) { |
| video_frame = GetVideoFrameFromSharedMemory( |
| std::move(frame), it->second->get_read_only_shmem_region().Map()); |
| } else { |
| video_frame = GetVideoFrameFromGMBHandle( |
| std::move(frame), it->second->get_gpu_memory_buffer_handle()); |
| } |
| |
| // This implementation does not forward scaled frames. |
| capture_stack_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&FakeVideoCaptureStack::OnReceivedFrame, |
| capture_stack_, std::move(video_frame))); |
| } |
| |
| void OnBufferRetired(int buffer_id) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(capture_stack_sequence_checker_); |
| |
| // Unretained is safe since we own the thread to which we're posting. |
| receiver_thread_->task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &FakeVideoCaptureStackReceiver::OnBufferRetiredOnReceiverThread, |
| base::Unretained(this), buffer_id)); |
| } |
| |
| void OnBufferRetiredOnReceiverThread(int buffer_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(receiver_sequence_checker_); |
| |
| const auto it = buffers_.find(buffer_id); |
| CHECK(it != buffers_.end()); |
| buffers_.erase(it); |
| } |
| |
| void OnError(media::VideoCaptureError) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(capture_stack_sequence_checker_); |
| |
| capture_stack_->SetErrorOccurred(); |
| } |
| |
| void OnFrameDropped(media::VideoCaptureFrameDropReason) override {} |
| |
| void OnNewSubCaptureTargetVersion( |
| uint32_t sub_capture_target_version) override {} |
| |
| void OnFrameWithEmptyRegionCapture() override {} |
| |
| void OnLog(const std::string& message) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(capture_stack_sequence_checker_); |
| |
| capture_stack_->OnLog(message); |
| } |
| |
| void OnStarted() override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(capture_stack_sequence_checker_); |
| |
| capture_stack_->SetStarted(); |
| } |
| |
| void OnStartedUsingGpuDecode() override { NOTREACHED(); } |
| |
| void OnStopped() override {} |
| |
| // WeakPtr is thread-safe to copy/move around, but the deref of |
| // `capture_stack_` needs to happen on `capture_stack_sequence_checker_`. |
| // Since there's one instance where we copy the pointer around that doesn't |
| // happen on the right sequence, we cannot use `GUARDED_BY_CONTEXT()` here. |
| const base::WeakPtr<FakeVideoCaptureStack> capture_stack_; |
| |
| base::flat_map<int, media::mojom::VideoBufferHandlePtr> buffers_ |
| GUARDED_BY_CONTEXT(receiver_sequence_checker_); |
| |
| scoped_refptr<gpu::ClientSharedImageInterface> sii_; |
| |
| // Task runner on which we should be calling into capture stack: |
| scoped_refptr<base::SequencedTaskRunner> capture_stack_task_runner_; |
| |
| // Thread that will be processing the `media::VideoFrameReceiver` methods. |
| // This is needed for Windows if using GpuMemoryBuffers, since mapping GMBs |
| // incurs a mojo call and is supposed to be a synchronous operation, which |
| // means that the mapping thread will be blocked. If it so happens that the |
| // mapping thread is also responsible for processing mojo responses (as is |
| // the case if we don't introduce `receiver_thread_`), we will cause a |
| // deadlock. |
| // As per `base::Thread` documentation, can only be used from a sequence it |
| // was started on (in our case, same as `capture_stack_task_runner_`). |
| std::unique_ptr<base::Thread> receiver_thread_ |
| GUARDED_BY_CONTEXT(capture_stack_sequence_checker_); |
| |
| // Sequence checker for parts of the class that can be accessed only on |
| // the `receiver_thread_`: |
| SEQUENCE_CHECKER(receiver_sequence_checker_); |
| |
| // Sequence checker for the `receiver_thread_` member variable, since |
| // `base::Thread` documents that it can only be accessed from a sequence that |
| // started the thread. The name reflects the fact that we're created by |
| // `FakeVideoCaptureStack` and that's where we start the thread. |
| SEQUENCE_CHECKER(capture_stack_sequence_checker_); |
| |
| base::WeakPtrFactory<FakeVideoCaptureStackReceiver> weak_ptr_factory_{this}; |
| }; |
| |
| std::unique_ptr<media::VideoFrameReceiver> |
| FakeVideoCaptureStack::CreateFrameReceiver() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!receiver_); |
| |
| auto result = std::make_unique<FakeVideoCaptureStackReceiver>( |
| weak_ptr_factory_.GetWeakPtr()); |
| |
| receiver_ = result->GetWeakPtr(); |
| |
| return result; |
| } |
| |
| SkBitmap FakeVideoCaptureStack::NextCapturedFrame() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| CHECK(!frames_.empty()); |
| media::VideoFrame& frame = *(frames_.front()); |
| SkBitmap bitmap = FrameTestUtil::ConvertToBitmap(frame); |
| frames_.pop_front(); |
| return bitmap; |
| } |
| |
| void FakeVideoCaptureStack::ClearCapturedFramesQueue() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| frames_.clear(); |
| } |
| |
| void FakeVideoCaptureStack::ExpectHasLogMessages() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| EXPECT_FALSE(log_messages_.empty()); |
| while (!log_messages_.empty()) { |
| VLOG(1) << "Next log message: " << log_messages_.front(); |
| log_messages_.pop_front(); |
| } |
| } |
| |
| void FakeVideoCaptureStack::ExpectNoLogMessages() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| while (!log_messages_.empty()) { |
| ADD_FAILURE() << "Unexpected log message: " << log_messages_.front(); |
| log_messages_.pop_front(); |
| } |
| } |
| |
| void FakeVideoCaptureStack::OnReceivedFrame( |
| scoped_refptr<media::VideoFrame> frame) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Frame timestamps should be monotionically increasing. |
| EXPECT_LT(last_frame_timestamp_, frame->timestamp()); |
| last_frame_timestamp_ = frame->timestamp(); |
| |
| EXPECT_TRUE(frame->ColorSpace().IsValid()); |
| |
| if (on_frame_received_) { |
| on_frame_received_.Run(frame.get()); |
| } |
| |
| frames_.emplace_back(std::move(frame)); |
| } |
| |
| // Returns true if the device called VideoFrameReceiver::OnStarted(). |
| bool FakeVideoCaptureStack::Started() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| WaitForReceiver(); |
| |
| return started_; |
| } |
| |
| // Returns true if the device called VideoFrameReceiver::OnError(). |
| bool FakeVideoCaptureStack::ErrorOccurred() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| WaitForReceiver(); |
| |
| return error_occurred_; |
| } |
| |
| // Accessors to capture frame queue. |
| bool FakeVideoCaptureStack::HasCapturedFrames() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| WaitForReceiver(); |
| |
| return !frames_.empty(); |
| } |
| |
| void FakeVideoCaptureStack::WaitForReceiver() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!receiver_) { |
| return; |
| } |
| |
| receiver_->WaitForReceiver(); |
| } |
| |
| } // namespace content |