| // 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 "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/functional/bind.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/bind_post_task.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "cc/base/math_util.h" |
| #include "components/viz/common/gpu/raster_context_provider.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/video_capture_service.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "gpu/GLES2/gl2extchromium.h" |
| #include "gpu/command_buffer/client/client_shared_image.h" |
| #include "gpu/command_buffer/client/raster_interface.h" |
| #include "gpu/command_buffer/client/shared_image_interface.h" |
| #include "gpu/command_buffer/common/shared_image_usage.h" |
| #include "media/base/media_switches.h" |
| #include "media/base/video_frame.h" |
| #include "media/base/video_frame_metadata.h" |
| #include "media/capture/mojom/video_capture_buffer.mojom.h" |
| #include "media/capture/mojom/video_capture_types.mojom.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "services/video_capture/public/cpp/mock_video_frame_handler.h" |
| #include "services/video_capture/public/mojom/constants.mojom.h" |
| #include "services/video_capture/public/mojom/producer.mojom.h" |
| #include "services/video_capture/public/mojom/video_capture_service.mojom.h" |
| #include "services/video_capture/public/mojom/video_source_provider.mojom.h" |
| #include "services/video_capture/public/mojom/virtual_device.mojom.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/compositor/compositor.h" |
| |
| // ImageTransportFactory::GetInstance is not available on all build configs. |
| #if defined(USE_AURA) || BUILDFLAG(IS_MAC) |
| #define CAN_USE_IMAGE_TRANSPORT_FACTORY 1 |
| #endif |
| |
| #if defined(CAN_USE_IMAGE_TRANSPORT_FACTORY) |
| #include "content/browser/compositor/image_transport_factory.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| static const char kVideoCaptureHtmlFile[] = "/media/video_capture_test.html"; |
| static const char kStartVideoCaptureAndVerify[] = |
| "startVideoCaptureFromVirtualDeviceAndVerifyUniformColorVideoWithSize(%d, " |
| "%d)"; |
| |
| static const char kVirtualDeviceId[] = "/virtual/device"; |
| static const char kVirtualDeviceName[] = "Virtual Device"; |
| |
| static const gfx::Size kDummyFrameCodedSize(320, 200); |
| static const gfx::Rect kDummyFrameVisibleRect(94, 36, 178, 150); |
| static const int kDummyFrameRate = 5; |
| |
| } // namespace |
| |
| // Abstraction for logic that is different between exercising |
| // DeviceFactory.AddTextureVirtualDevice() and |
| // DeviceFactory.AddSharedMemoryVirtualDevice(). |
| class VirtualDeviceExerciser { |
| public: |
| virtual ~VirtualDeviceExerciser() {} |
| virtual void Initialize() = 0; |
| virtual void RegisterVirtualDeviceAtVideoSourceProvider( |
| mojo::Remote<video_capture::mojom::VideoSourceProvider>* video_source, |
| const media::VideoCaptureDeviceInfo& info) = 0; |
| virtual gfx::Size GetVideoSize() = 0; |
| virtual void PushNextFrame(base::TimeDelta timestamp) = 0; |
| virtual void ShutDown() = 0; |
| }; |
| |
| // A VirtualDeviceExerciser for exercising |
| // DeviceFactory.AddTextureVirtualDevice(). It alternates between two texture |
| // RGB dummy frames, one dark one and one light one. |
| class TextureDeviceExerciser : public VirtualDeviceExerciser { |
| public: |
| TextureDeviceExerciser() { DETACH_FROM_SEQUENCE(sequence_checker_); } |
| |
| void Initialize() override { |
| ImageTransportFactory* factory = ImageTransportFactory::GetInstance(); |
| CHECK(factory); |
| context_provider_ = |
| factory->GetContextFactory()->SharedMainThreadRasterContextProvider(); |
| CHECK(context_provider_); |
| gpu::raster::RasterInterface* ri = context_provider_->RasterInterface(); |
| CHECK(ri); |
| |
| gpu::SharedImageInterface* sii = context_provider_->SharedImageInterface(); |
| CHECK(sii); |
| |
| const SkColor4f kDarkFrameColor = SkColors::kBlack; |
| const SkColor4f kLightFrameColor = SkColors::kGray; |
| dummy_frame_0_shared_image_ = CreateDummyRgbFrame( |
| ri, sii, kDarkFrameColor, dummy_frame_0_sync_token_); |
| dummy_frame_1_shared_image_ = CreateDummyRgbFrame( |
| ri, sii, kLightFrameColor, dummy_frame_1_sync_token_); |
| } |
| |
| void RegisterVirtualDeviceAtVideoSourceProvider( |
| mojo::Remote<video_capture::mojom::VideoSourceProvider>* video_source, |
| const media::VideoCaptureDeviceInfo& info) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| (*video_source) |
| ->AddTextureVirtualDevice(info, |
| virtual_device_.BindNewPipeAndPassReceiver()); |
| |
| gpu::ExportedSharedImage dummy_frame_0_exported_shared_image = |
| dummy_frame_0_shared_image_->Export(); |
| gpu::ExportedSharedImage dummy_frame_1_exported_shared_image = |
| dummy_frame_1_shared_image_->Export(); |
| |
| virtual_device_->OnNewSharedImageBufferHandle( |
| 0, media::mojom::SharedImageBufferHandleSet::New( |
| std::move(dummy_frame_0_exported_shared_image), |
| dummy_frame_0_sync_token_)); |
| virtual_device_->OnNewSharedImageBufferHandle( |
| 1, media::mojom::SharedImageBufferHandleSet::New( |
| std::move(dummy_frame_1_exported_shared_image), |
| dummy_frame_1_sync_token_)); |
| frame_being_consumed_[0] = false; |
| frame_being_consumed_[1] = false; |
| } |
| |
| gfx::Size GetVideoSize() override { return kDummyFrameCodedSize; } |
| |
| void PushNextFrame(base::TimeDelta timestamp) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (frame_being_consumed_[dummy_frame_index_]) { |
| LOG(INFO) << "Frame " << dummy_frame_index_ << " is still being consumed"; |
| return; |
| } |
| |
| if (!virtual_device_has_frame_access_handler_) { |
| mojo::PendingRemote<video_capture::mojom::VideoFrameAccessHandler> |
| pending_frame_access_handler; |
| mojo::MakeSelfOwnedReceiver< |
| video_capture::mojom::VideoFrameAccessHandler>( |
| std::make_unique<video_capture::FakeVideoFrameAccessHandler>( |
| base::BindRepeating( |
| &TextureDeviceExerciser::OnFrameConsumptionFinished, |
| weak_factory_.GetWeakPtr())), |
| pending_frame_access_handler.InitWithNewPipeAndPassReceiver()); |
| virtual_device_->OnFrameAccessHandlerReady( |
| std::move(pending_frame_access_handler)); |
| virtual_device_has_frame_access_handler_ = true; |
| } |
| |
| media::VideoFrameMetadata metadata; |
| metadata.frame_rate = kDummyFrameRate; |
| metadata.reference_time = base::TimeTicks::Now(); |
| |
| media::mojom::VideoFrameInfoPtr info = media::mojom::VideoFrameInfo::New(); |
| info->timestamp = timestamp; |
| info->pixel_format = media::PIXEL_FORMAT_ARGB; |
| info->coded_size = kDummyFrameCodedSize; |
| info->visible_rect = gfx::Rect(kDummyFrameCodedSize); |
| info->metadata = metadata; |
| |
| frame_being_consumed_[dummy_frame_index_] = true; |
| virtual_device_->OnFrameReadyInBuffer(dummy_frame_index_, std::move(info)); |
| |
| dummy_frame_index_ = (dummy_frame_index_ + 1) % 2; |
| } |
| |
| void ShutDown() override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| virtual_device_.reset(); |
| weak_factory_.InvalidateWeakPtrs(); |
| } |
| |
| private: |
| scoped_refptr<gpu::ClientSharedImage> CreateDummyRgbFrame( |
| gpu::raster::RasterInterface* ri, |
| gpu::SharedImageInterface* sii, |
| SkColor4f frame_color, |
| gpu::SyncToken& ri_token) { |
| SkBitmap frame_bitmap; |
| frame_bitmap.allocPixels(SkImageInfo::Make( |
| kDummyFrameCodedSize.width(), kDummyFrameCodedSize.height(), |
| kRGBA_8888_SkColorType, kOpaque_SkAlphaType)); |
| frame_bitmap.eraseColor(frame_color); |
| |
| // This SharedImage is populated via the raster interface below and may |
| // be read via the raster interface in normal VideoFrame usage exercised |
| // by the tests. |
| auto shared_image = sii->CreateSharedImage( |
| {viz::SinglePlaneFormat::kRGBA_8888, kDummyFrameCodedSize, |
| gfx::ColorSpace::CreateSRGB(), kTopLeft_GrSurfaceOrigin, |
| kOpaque_SkAlphaType, |
| gpu::SHARED_IMAGE_USAGE_RASTER_READ | |
| gpu::SHARED_IMAGE_USAGE_RASTER_WRITE | |
| gpu::SHARED_IMAGE_USAGE_OOP_RASTERIZATION, |
| "TestLabel"}, |
| gpu::kNullSurfaceHandle); |
| |
| auto ri_access = shared_image->BeginRasterAccess( |
| ri, shared_image->creation_sync_token(), /*readonly=*/false); |
| ri->WritePixels(shared_image->mailbox(), 0, 0, GL_TEXTURE_2D, |
| frame_bitmap.pixmap()); |
| |
| ri_token = gpu::RasterScopedAccess::EndAccess(std::move(ri_access)); |
| int8_t* ri_token_data = ri_token.GetData(); |
| ri->VerifySyncTokensCHROMIUM(&ri_token_data, 1); |
| ri->ShallowFlushCHROMIUM(); |
| CHECK_EQ(ri->GetError(), static_cast<GLenum>(GL_NO_ERROR)); |
| |
| return shared_image; |
| } |
| |
| void OnFrameConsumptionFinished(int32_t frame_index) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| frame_being_consumed_[frame_index] = false; |
| } |
| |
| SEQUENCE_CHECKER(sequence_checker_); |
| scoped_refptr<viz::RasterContextProvider> context_provider_; |
| mojo::Remote<video_capture::mojom::TextureVirtualDevice> virtual_device_; |
| bool virtual_device_has_frame_access_handler_ = false; |
| int dummy_frame_index_ = 0; |
| scoped_refptr<gpu::ClientSharedImage> dummy_frame_0_shared_image_; |
| scoped_refptr<gpu::ClientSharedImage> dummy_frame_1_shared_image_; |
| gpu::SyncToken dummy_frame_0_sync_token_; |
| gpu::SyncToken dummy_frame_1_sync_token_; |
| std::array<bool, 2> frame_being_consumed_; |
| base::WeakPtrFactory<TextureDeviceExerciser> weak_factory_{this}; |
| }; |
| |
| // A VirtualDeviceExerciser for exercising |
| // DeviceFactory.AddSharedMemoryVirtualDevice(). |
| // It generates (dummy) I420 frame data by setting all bytes equal to the |
| // current frame count. Padding bytes are set to 0. |
| class SharedMemoryDeviceExerciser : public VirtualDeviceExerciser, |
| public video_capture::mojom::Producer { |
| public: |
| explicit SharedMemoryDeviceExerciser( |
| media::mojom::PlaneStridesPtr strides = nullptr) |
| : strides_(std::move(strides)) {} |
| |
| // VirtualDeviceExerciser implementation. |
| void Initialize() override {} |
| void RegisterVirtualDeviceAtVideoSourceProvider( |
| mojo::Remote<video_capture::mojom::VideoSourceProvider>* video_source, |
| const media::VideoCaptureDeviceInfo& info) override { |
| mojo::PendingRemote<video_capture::mojom::Producer> producer; |
| producer_receiver_.Bind(producer.InitWithNewPipeAndPassReceiver()); |
| (*video_source) |
| ->AddSharedMemoryVirtualDevice( |
| info, std::move(producer), |
| virtual_device_.BindNewPipeAndPassReceiver()); |
| } |
| gfx::Size GetVideoSize() override { |
| return gfx::Size(kDummyFrameVisibleRect.width(), |
| kDummyFrameVisibleRect.height()); |
| } |
| void PushNextFrame(base::TimeDelta timestamp) override { |
| virtual_device_->RequestFrameBuffer( |
| kDummyFrameCodedSize, media::VideoPixelFormat::PIXEL_FORMAT_I420, |
| strides_.Clone(), |
| base::BindOnce(&SharedMemoryDeviceExerciser::OnFrameBufferReceived, |
| weak_factory_.GetWeakPtr(), timestamp)); |
| } |
| void ShutDown() override { |
| virtual_device_.reset(); |
| producer_receiver_.reset(); |
| weak_factory_.InvalidateWeakPtrs(); |
| } |
| |
| // video_capture::mojom::Producer implementation. |
| void OnNewBuffer(int32_t buffer_id, |
| media::mojom::VideoBufferHandlePtr buffer_handle, |
| OnNewBufferCallback callback) override { |
| CHECK(buffer_handle->is_unsafe_shmem_region()); |
| base::UnsafeSharedMemoryRegion region = |
| std::move(buffer_handle->get_unsafe_shmem_region()); |
| CHECK(region.IsValid()); |
| base::WritableSharedMemoryMapping mapping = region.Map(); |
| CHECK(mapping.IsValid()); |
| outgoing_buffer_id_to_buffer_map_.insert( |
| std::make_pair(buffer_id, std::move(mapping))); |
| std::move(callback).Run(); |
| } |
| void OnBufferRetired(int32_t buffer_id) override { |
| outgoing_buffer_id_to_buffer_map_.erase(buffer_id); |
| } |
| |
| private: |
| void OnFrameBufferReceived(base::TimeDelta timestamp, int32_t buffer_id) { |
| if (buffer_id == video_capture::mojom::kInvalidBufferId) |
| return; |
| |
| media::VideoFrameMetadata metadata; |
| metadata.frame_rate = kDummyFrameRate; |
| metadata.reference_time = base::TimeTicks::Now(); |
| |
| media::mojom::VideoFrameInfoPtr info = media::mojom::VideoFrameInfo::New(); |
| info->timestamp = timestamp; |
| info->pixel_format = media::PIXEL_FORMAT_I420; |
| info->coded_size = kDummyFrameCodedSize; |
| info->visible_rect = kDummyFrameVisibleRect; |
| info->metadata = metadata; |
| info->strides = strides_.Clone(); |
| |
| base::WritableSharedMemoryMapping& outgoing_buffer = |
| outgoing_buffer_id_to_buffer_map_.at(buffer_id); |
| |
| static int frame_count = 0; |
| frame_count++; |
| const uint8_t dummy_value = frame_count % 256; |
| |
| // Reset the whole buffer to 0 |
| UNSAFE_TODO(memset(outgoing_buffer.memory(), 0, outgoing_buffer.size())); |
| |
| // Set all bytes affecting |info->visible_rect| to |dummy_value|. |
| const int kYStride = info->strides ? info->strides->stride_by_plane[0] |
| : info->coded_size.width(); |
| const int kYColsToSkipAtStart = info->visible_rect.x(); |
| const int kYVisibleColCount = info->visible_rect.width(); |
| const int kYCodedRowCount = info->coded_size.height(); |
| const int kYRowsToSkipAtStart = info->visible_rect.y(); |
| const int kYVisibleRowCount = info->visible_rect.height(); |
| |
| const int kUStride = info->strides ? info->strides->stride_by_plane[1] |
| : info->coded_size.width() / 2; |
| const int kUColsToSkipAtStart = |
| cc::MathUtil::UncheckedRoundDown(info->visible_rect.x(), 2) / 2; |
| const int kUVisibleColCount = |
| (cc::MathUtil::UncheckedRoundUp(info->visible_rect.right(), 2) / 2) - |
| kUColsToSkipAtStart; |
| const int kUCodedRowCount = info->coded_size.height() / 2; |
| const int kURowsToSkipAtStart = |
| cc::MathUtil::UncheckedRoundDown(info->visible_rect.y(), 2) / 2; |
| const int kUVisibleRowCount = |
| (cc::MathUtil::UncheckedRoundUp(info->visible_rect.bottom(), 2) / 2) - |
| kURowsToSkipAtStart; |
| |
| const int kVStride = info->strides ? info->strides->stride_by_plane[2] |
| : info->coded_size.width() / 2; |
| |
| uint8_t* write_ptr = outgoing_buffer.GetMemoryAsSpan<uint8_t>().data(); |
| FillVisiblePortionOfPlane(&write_ptr, dummy_value, kYCodedRowCount, |
| kYRowsToSkipAtStart, kYVisibleRowCount, kYStride, |
| kYColsToSkipAtStart, kYVisibleColCount); |
| FillVisiblePortionOfPlane(&write_ptr, dummy_value, kUCodedRowCount, |
| kURowsToSkipAtStart, kUVisibleRowCount, kUStride, |
| kUColsToSkipAtStart, kUVisibleColCount); |
| FillVisiblePortionOfPlane(&write_ptr, dummy_value, kUCodedRowCount, |
| kURowsToSkipAtStart, kUVisibleRowCount, kVStride, |
| kUColsToSkipAtStart, kUVisibleColCount); |
| |
| virtual_device_->OnFrameReadyInBuffer(buffer_id, std::move(info)); |
| } |
| |
| void FillVisiblePortionOfPlane(uint8_t** write_ptr, |
| uint8_t fill_value, |
| int row_count, |
| int rows_to_skip_at_start, |
| int visible_row_count, |
| int col_count, |
| int cols_to_skip_at_start, |
| int visible_col_count) { |
| const int kColsToSkipAtEnd = |
| col_count - visible_col_count - cols_to_skip_at_start; |
| const int kRowsToSkipAtEnd = |
| row_count - visible_row_count - rows_to_skip_at_start; |
| |
| // Skip rows at start |
| UNSAFE_TODO((*write_ptr) += col_count * rows_to_skip_at_start); |
| // Fill rows |
| for (int i = 0; i < visible_row_count; i++) { |
| // Skip cols at start |
| UNSAFE_TODO((*write_ptr) += cols_to_skip_at_start); |
| // Fill visible bytes |
| UNSAFE_TODO(memset(*write_ptr, fill_value, visible_col_count)); |
| UNSAFE_TODO((*write_ptr) += visible_col_count); |
| // Skip cols at end |
| UNSAFE_TODO((*write_ptr) += kColsToSkipAtEnd); |
| } |
| // Skip rows at end |
| UNSAFE_TODO((*write_ptr) += col_count * kRowsToSkipAtEnd); |
| } |
| |
| media::mojom::PlaneStridesPtr strides_; |
| mojo::Receiver<video_capture::mojom::Producer> producer_receiver_{this}; |
| mojo::Remote<video_capture::mojom::SharedMemoryVirtualDevice> virtual_device_; |
| std::map<int32_t /*buffer_id*/, base::WritableSharedMemoryMapping> |
| outgoing_buffer_id_to_buffer_map_; |
| base::WeakPtrFactory<SharedMemoryDeviceExerciser> weak_factory_{this}; |
| }; |
| |
| // Integration test that obtains a connection to the video capture service. It |
| // It then registers a virtual device at the service and feeds frames to it. It |
| // opens the virtual device in a <video> element on a test page and verifies |
| // that the element plays in the expected dimensions and the pixel content on |
| // the element changes. |
| class WebRtcVideoCaptureServiceBrowserTest : public ContentBrowserTest { |
| public: |
| WebRtcVideoCaptureServiceBrowserTest() |
| : virtual_device_thread_("Virtual Device Thread") { |
| virtual_device_thread_.Start(); |
| } |
| |
| WebRtcVideoCaptureServiceBrowserTest( |
| const WebRtcVideoCaptureServiceBrowserTest&) = delete; |
| WebRtcVideoCaptureServiceBrowserTest& operator=( |
| const WebRtcVideoCaptureServiceBrowserTest&) = delete; |
| |
| ~WebRtcVideoCaptureServiceBrowserTest() override {} |
| |
| void AddVirtualDeviceAndStartCapture(VirtualDeviceExerciser* device_exerciser, |
| base::OnceClosure finish_test_cb) { |
| DCHECK(virtual_device_thread_.task_runner()->RunsTasksInCurrentSequence()); |
| |
| main_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](mojo::PendingReceiver<video_capture::mojom::VideoSourceProvider> |
| receiver) { |
| GetVideoCaptureService().ConnectToVideoSourceProvider( |
| std::move(receiver)); |
| }, |
| video_source_provider_.BindNewPipeAndPassReceiver())); |
| |
| media::VideoCaptureDeviceInfo info; |
| info.descriptor.device_id = kVirtualDeviceId; |
| info.descriptor.set_display_name(kVirtualDeviceName); |
| info.descriptor.capture_api = media::VideoCaptureApi::VIRTUAL_DEVICE; |
| |
| video_size_ = device_exerciser->GetVideoSize(); |
| device_exerciser->RegisterVirtualDeviceAtVideoSourceProvider( |
| &video_source_provider_, info); |
| |
| main_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&WebRtcVideoCaptureServiceBrowserTest:: |
| OpenVirtualDeviceInRendererAndWaitForPlaying, |
| base::Unretained(this), |
| base::BindPostTaskToCurrentDefault(base::BindOnce( |
| &WebRtcVideoCaptureServiceBrowserTest:: |
| ShutDownVirtualDeviceAndContinue, |
| base::Unretained(this), device_exerciser, |
| std::move(finish_test_cb))))); |
| |
| PushDummyFrameAndScheduleNextPush(device_exerciser); |
| } |
| |
| void PushDummyFrameAndScheduleNextPush( |
| VirtualDeviceExerciser* device_exerciser) { |
| DCHECK(virtual_device_thread_.task_runner()->RunsTasksInCurrentSequence()); |
| device_exerciser->PushNextFrame(CalculateTimeSinceFirstInvocation()); |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&WebRtcVideoCaptureServiceBrowserTest:: |
| PushDummyFrameAndScheduleNextPush, |
| weak_factory_.GetWeakPtr(), device_exerciser), |
| base::Milliseconds(1000 / kDummyFrameRate)); |
| } |
| |
| void ShutDownVirtualDeviceAndContinue( |
| VirtualDeviceExerciser* device_exerciser, |
| base::OnceClosure continuation) { |
| DCHECK(virtual_device_thread_.task_runner()->RunsTasksInCurrentSequence()); |
| LOG(INFO) << "Shutting down virtual device"; |
| device_exerciser->ShutDown(); |
| video_source_provider_.reset(); |
| weak_factory_.InvalidateWeakPtrs(); |
| std::move(continuation).Run(); |
| } |
| |
| void OpenVirtualDeviceInRendererAndWaitForPlaying( |
| base::OnceClosure finish_test_cb) { |
| DCHECK(main_task_runner_->RunsTasksInCurrentSequence()); |
| embedded_test_server()->StartAcceptingConnections(); |
| GURL url(embedded_test_server()->GetURL(kVideoCaptureHtmlFile)); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| std::string javascript_to_execute = base::StringPrintf( |
| kStartVideoCaptureAndVerify, video_size_.width(), video_size_.height()); |
| // Start video capture and wait until it started rendering |
| ASSERT_TRUE(ExecJs(shell(), javascript_to_execute)); |
| |
| std::move(finish_test_cb).Run(); |
| } |
| |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Note: We are not planning to actually use the fake device, but we want |
| // to avoid enumerating or otherwise calling into real capture devices. |
| command_line->AppendSwitch(switches::kUseFakeUIForMediaStream); |
| } |
| |
| void SetUp() override { |
| ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); |
| EnablePixelOutput(); |
| ContentBrowserTest::SetUp(); |
| } |
| |
| void Initialize() { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| main_task_runner_ = base::SingleThreadTaskRunner::GetCurrentDefault(); |
| } |
| |
| base::Thread virtual_device_thread_; |
| scoped_refptr<base::SequencedTaskRunner> main_task_runner_; |
| |
| private: |
| base::TimeDelta CalculateTimeSinceFirstInvocation() { |
| if (first_frame_time_.is_null()) |
| first_frame_time_ = base::TimeTicks::Now(); |
| return base::TimeTicks::Now() - first_frame_time_; |
| } |
| |
| mojo::Remote<video_capture::mojom::VideoSourceProvider> |
| video_source_provider_; |
| gfx::Size video_size_; |
| base::TimeTicks first_frame_time_; |
| base::WeakPtrFactory<WebRtcVideoCaptureServiceBrowserTest> weak_factory_{ |
| this}; |
| }; |
| |
| // TODO(crbug.com/40835247): Fix and enable on Fuchsia. |
| // TODO(crbug.com/40781953): This test is flakey on macOS. |
| // TODO(crbug.com/41484083): This test is flakey on ChromeOS. |
| #if BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS) |
| #define MAYBE_FramesSentThroughTextureVirtualDeviceGetDisplayedOnPage \ |
| DISABLED_FramesSentThroughTextureVirtualDeviceGetDisplayedOnPage |
| #else |
| #define MAYBE_FramesSentThroughTextureVirtualDeviceGetDisplayedOnPage \ |
| FramesSentThroughTextureVirtualDeviceGetDisplayedOnPage |
| #endif |
| IN_PROC_BROWSER_TEST_F( |
| WebRtcVideoCaptureServiceBrowserTest, |
| MAYBE_FramesSentThroughTextureVirtualDeviceGetDisplayedOnPage) { |
| Initialize(); |
| auto device_exerciser = std::make_unique<TextureDeviceExerciser>(); |
| device_exerciser->Initialize(); |
| |
| base::RunLoop run_loop; |
| virtual_device_thread_.task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&WebRtcVideoCaptureServiceBrowserTest:: |
| AddVirtualDeviceAndStartCapture, |
| base::Unretained(this), device_exerciser.get(), |
| base::BindPostTaskToCurrentDefault( |
| run_loop.QuitClosure()))); |
| run_loop.Run(); |
| } |
| |
| #if BUILDFLAG(IS_MAC) |
| // TODO(crbug.com/40781953): This test is flakey on macOS. |
| #define MAYBE_FramesSentThroughSharedMemoryVirtualDeviceGetDisplayedOnPage \ |
| DISABLED_FramesSentThroughSharedMemoryVirtualDeviceGetDisplayedOnPage |
| #else |
| #define MAYBE_FramesSentThroughSharedMemoryVirtualDeviceGetDisplayedOnPage \ |
| FramesSentThroughSharedMemoryVirtualDeviceGetDisplayedOnPage |
| #endif |
| IN_PROC_BROWSER_TEST_F( |
| WebRtcVideoCaptureServiceBrowserTest, |
| MAYBE_FramesSentThroughSharedMemoryVirtualDeviceGetDisplayedOnPage) { |
| Initialize(); |
| auto device_exerciser = std::make_unique<SharedMemoryDeviceExerciser>(); |
| device_exerciser->Initialize(); |
| |
| base::RunLoop run_loop; |
| virtual_device_thread_.task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&WebRtcVideoCaptureServiceBrowserTest:: |
| AddVirtualDeviceAndStartCapture, |
| base::Unretained(this), device_exerciser.get(), |
| base::BindPostTaskToCurrentDefault( |
| run_loop.QuitClosure()))); |
| run_loop.Run(); |
| } |
| |
| #if BUILDFLAG(IS_MAC) |
| // TODO(crbug.com/40781953): This test is flakey on macOS. |
| #define MAYBE_PaddedI420FramesSentThroughSharedMemoryVirtualDeviceGetDisplayedOnPage \ |
| DISABLED_PaddedI420FramesSentThroughSharedMemoryVirtualDeviceGetDisplayedOnPage |
| #else |
| #define MAYBE_PaddedI420FramesSentThroughSharedMemoryVirtualDeviceGetDisplayedOnPage \ |
| PaddedI420FramesSentThroughSharedMemoryVirtualDeviceGetDisplayedOnPage |
| #endif |
| IN_PROC_BROWSER_TEST_F( |
| WebRtcVideoCaptureServiceBrowserTest, |
| MAYBE_PaddedI420FramesSentThroughSharedMemoryVirtualDeviceGetDisplayedOnPage) { |
| Initialize(); |
| auto device_exerciser = std::make_unique<SharedMemoryDeviceExerciser>( |
| media::mojom::PlaneStrides::New( |
| std::vector<uint32_t>({1024u, 512u, 1024u, 0u}))); |
| device_exerciser->Initialize(); |
| |
| base::RunLoop run_loop; |
| virtual_device_thread_.task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&WebRtcVideoCaptureServiceBrowserTest:: |
| AddVirtualDeviceAndStartCapture, |
| base::Unretained(this), device_exerciser.get(), |
| base::BindPostTaskToCurrentDefault( |
| run_loop.QuitClosure()))); |
| run_loop.Run(); |
| } |
| |
| } // namespace content |
| |
| #endif // defined(CAN_USE_IMAGE_TRANSPORT_FACTORY) |