blob: 1e6d5082695c75587c0afb0032d78229e5c5dd29 [file] [log] [blame]
// Copyright (c) 2012 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/video/video_capture_device.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/task_environment.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/video_frame.h"
#include "media/capture/video/create_video_capture_device_factory.h"
#include "media/capture/video/mock_video_capture_device_client.h"
#include "media/capture/video_capture_types.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_WIN)
#include <mfcaptureengine.h>
#include "base/win/scoped_com_initializer.h"
#include "base/win/windows_version.h" // For fine-grained suppression.
#include "media/capture/video/win/video_capture_device_factory_win.h"
#include "media/capture/video/win/video_capture_device_mf_win.h"
#endif
#if defined(OS_MAC)
#include "media/capture/video/mac/video_capture_device_factory_mac.h"
#endif
#if defined(OS_ANDROID)
#include "base/android/build_info.h"
#include "base/android/jni_android.h"
#include "media/capture/video/android/video_capture_device_android.h"
#include "media/capture/video/android/video_capture_device_factory_android.h"
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chromeos/dbus/power/power_manager_client.h"
#include "media/capture/video/chromeos/camera_buffer_factory.h"
#include "media/capture/video/chromeos/camera_hal_dispatcher_impl.h"
#include "media/capture/video/chromeos/public/cros_features.h"
#include "media/capture/video/chromeos/video_capture_device_chromeos_halv3.h"
#include "media/capture/video/chromeos/video_capture_device_factory_chromeos.h"
#include "media/gpu/test/local_gpu_memory_buffer_manager.h" // nogncheck
#include "mojo/public/cpp/bindings/pending_receiver.h"
#endif
#if defined(OS_MAC)
// Mac will always give you the size you ask for and this case will fail.
#define MAYBE_UsingRealWebcam_AllocateBadSize \
DISABLED_UsingRealWebcam_AllocateBadSize
// We will always get YUYV from the Mac AVFoundation implementations.
#define MAYBE_UsingRealWebcam_CaptureMjpeg DISABLED_UsingRealWebcam_CaptureMjpeg
// TODO(crbug.com/1128470): Re-enable as soon as issues with resource access
// are fixed.
#define MAYBE_UsingRealWebcam_TakePhoto DISABLED_UsingRealWebcam_TakePhoto
// TODO(crbug.com/1128470): Re-enable as soon as issues with resource access
// are fixed.
#define MAYBE_UsingRealWebcam_GetPhotoState \
DISABLED_UsingRealWebcam_GetPhotoState
// TODO(crbug.com/1128470): Re-enable as soon as issues with resource access
// are fixed.
#define MAYBE_UsingRealWebcam_CaptureWithSize \
DISABLED_UsingRealWebcam_CaptureWithSize
#define MAYBE_UsingRealWebcam_CheckPhotoCallbackRelease \
UsingRealWebcam_CheckPhotoCallbackRelease
#elif defined(OS_WIN) || defined(OS_FUCHSIA)
// Windows test bots don't have camera.
// On Fuchsia the tests run under emulator that doesn't support camera.
#define MAYBE_UsingRealWebcam_AllocateBadSize \
DISABLED_UsingRealWebcam_AllocateBadSize
#define MAYBE_UsingRealWebcam_CaptureMjpeg DISABLED_UsingRealWebcam_CaptureMjpeg
#define MAYBE_UsingRealWebcam_TakePhoto DISABLED_UsingRealWebcam_TakePhoto
#define MAYBE_UsingRealWebcam_GetPhotoState \
DISABLED_UsingRealWebcam_GetPhotoState
#define MAYBE_UsingRealWebcam_CaptureWithSize \
DISABLED_UsingRealWebcam_CaptureWithSize
#define MAYBE_UsingRealWebcam_CheckPhotoCallbackRelease \
DISABLED_UsingRealWebcam_CheckPhotoCallbackRelease
#elif defined(OS_ANDROID)
#define MAYBE_UsingRealWebcam_AllocateBadSize UsingRealWebcam_AllocateBadSize
// This format is not returned by VideoCaptureDeviceFactoryAndroid's
// GetSupportedFormats
#define MAYBE_UsingRealWebcam_CaptureMjpeg DISABLED_UsingRealWebcam_CaptureMjpeg
#define MAYBE_UsingRealWebcam_TakePhoto UsingRealWebcam_TakePhoto
#define MAYBE_UsingRealWebcam_GetPhotoState UsingRealWebcam_GetPhotoState
#define MAYBE_UsingRealWebcam_CaptureWithSize UsingRealWebcam_CaptureWithSize
#define MAYBE_UsingRealWebcam_CheckPhotoCallbackRelease \
UsingRealWebcam_CheckPhotoCallbackRelease
#elif BUILDFLAG(IS_CHROMEOS_ASH)
#define MAYBE_UsingRealWebcam_AllocateBadSize \
DISABLED_UsingRealWebcam_AllocateBadSize
#define MAYBE_UsingRealWebcam_CaptureMjpeg UsingRealWebcam_CaptureMjpeg
#define MAYBE_UsingRealWebcam_TakePhoto UsingRealWebcam_TakePhoto
#define MAYBE_UsingRealWebcam_GetPhotoState UsingRealWebcam_GetPhotoState
#define MAYBE_UsingRealWebcam_CaptureWithSize UsingRealWebcam_CaptureWithSize
#define MAYBE_UsingRealWebcam_CheckPhotoCallbackRelease \
UsingRealWebcam_CheckPhotoCallbackRelease
#elif defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
// UsingRealWebcam_AllocateBadSize will hang when a real camera is attached and
// if more than one test is trying to use the camera (even across processes). Do
// NOT renable this test without fixing the many bugs associated with it:
// http://crbug.com/94134 http://crbug.com/137260 http://crbug.com/417824
#define MAYBE_UsingRealWebcam_AllocateBadSize \
DISABLED_UsingRealWebcam_AllocateBadSize
#define MAYBE_UsingRealWebcam_CaptureMjpeg UsingRealWebcam_CaptureMjpeg
#define MAYBE_UsingRealWebcam_TakePhoto UsingRealWebcam_TakePhoto
#define MAYBE_UsingRealWebcam_GetPhotoState UsingRealWebcam_GetPhotoState
#define MAYBE_UsingRealWebcam_CaptureWithSize UsingRealWebcam_CaptureWithSize
#define MAYBE_UsingRealWebcam_CheckPhotoCallbackRelease \
UsingRealWebcam_CheckPhotoCallbackRelease
#else
#define MAYBE_UsingRealWebcam_AllocateBadSize UsingRealWebcam_AllocateBadSize
#define MAYBE_UsingRealWebcam_CaptureMjpeg UsingRealWebcam_CaptureMjpeg
#define MAYBE_UsingRealWebcam_TakePhoto DISABLED_UsingRealWebcam_TakePhoto
#define MAYBE_UsingRealWebcam_GetPhotoState \
DISABLED_UsingRealWebcam_GetPhotoState
#define MAYBE_UsingRealWebcam_CaptureWithSize UsingRealWebcam_CaptureWithSize
#define MAYBE_UsingRealWebcam_CheckPhotoCallbackRelease \
UsingRealWebcam_CheckPhotoCallbackRelease
#endif
// Wrap the TEST_P macro into another one to allow to preprocess |test_name|
// macros. Needed until https://github.com/google/googletest/issues/389 is
// fixed.
#define WRAPPED_TEST_P(test_case_name, test_name) \
TEST_P(test_case_name, test_name)
using base::test::RunClosure;
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::SaveArg;
namespace media {
namespace {
void DumpError(media::VideoCaptureError,
const base::Location& location,
const std::string& message) {
DPLOG(ERROR) << location.ToString() << " " << message;
}
enum VideoCaptureImplementationTweak {
NONE,
#if defined(OS_WIN)
WIN_MEDIA_FOUNDATION
#endif
};
#if defined(OS_WIN)
class MockMFPhotoCallback final : public IMFCaptureEngineOnSampleCallback {
public:
~MockMFPhotoCallback() {}
MOCK_METHOD2(DoQueryInterface, HRESULT(REFIID, void**));
MOCK_METHOD0(DoAddRef, ULONG(void));
MOCK_METHOD0(DoRelease, ULONG(void));
MOCK_METHOD1(DoOnSample, HRESULT(IMFSample*));
IFACEMETHODIMP QueryInterface(REFIID riid, void** object) override {
return DoQueryInterface(riid, object);
}
IFACEMETHODIMP_(ULONG) AddRef() override { return DoAddRef(); }
IFACEMETHODIMP_(ULONG) Release() override { return DoRelease(); }
IFACEMETHODIMP OnSample(IMFSample* sample) override {
return DoOnSample(sample);
}
};
#endif
class MockImageCaptureClient
: public base::RefCountedThreadSafe<MockImageCaptureClient> {
public:
// GMock doesn't support move-only arguments, so we use this forward method.
void DoOnPhotoTaken(mojom::BlobPtr blob) {
if (strcmp("image/jpeg", blob->mime_type.c_str()) == 0) {
ASSERT_GT(blob->data.size(), 4u);
// Check some bytes that univocally identify |data| as a JPEG File.
// The first two bytes must be the SOI marker.
// The next two bytes must be a marker, such as APPn, DTH etc.
// cf. Section B.2 at https://www.w3.org/Graphics/JPEG/itu-t81.pdf
EXPECT_EQ(0xFF, blob->data[0]); // First SOI byte
EXPECT_EQ(0xD8, blob->data[1]); // Second SOI byte
EXPECT_EQ(0xFF, blob->data[2]); // First byte of the next marker
OnCorrectPhotoTaken();
} else if (strcmp("image/png", blob->mime_type.c_str()) == 0) {
ASSERT_GT(blob->data.size(), 4u);
EXPECT_EQ('P', blob->data[1]);
EXPECT_EQ('N', blob->data[2]);
EXPECT_EQ('G', blob->data[3]);
OnCorrectPhotoTaken();
} else {
ADD_FAILURE() << "Photo format should be jpeg or png";
}
}
MOCK_METHOD0(OnCorrectPhotoTaken, void(void));
// GMock doesn't support move-only arguments, so we use this forward method.
void DoOnGetPhotoState(mojom::PhotoStatePtr state) {
state_ = std::move(state);
OnCorrectGetPhotoState();
}
MOCK_METHOD0(OnCorrectGetPhotoState, void(void));
const mojom::PhotoState* capabilities() { return state_.get(); }
private:
friend class base::RefCountedThreadSafe<MockImageCaptureClient>;
virtual ~MockImageCaptureClient() = default;
mojom::PhotoStatePtr state_;
};
base::test::SingleThreadTaskEnvironment::MainThreadType kMainThreadType =
#if defined(OS_MAC)
// Video capture code on MacOSX must run on a CFRunLoop enabled thread
// for interaction with AVFoundation.
base::test::SingleThreadTaskEnvironment::MainThreadType::UI;
#elif defined(OS_FUCHSIA)
// FIDL APIs on Fuchsia requires IO thread.
base::test::SingleThreadTaskEnvironment::MainThreadType::IO;
#else
base::test::SingleThreadTaskEnvironment::MainThreadType::DEFAULT;
#endif
} // namespace
class VideoCaptureDeviceTest
: public testing::TestWithParam<
std::tuple<gfx::Size, VideoCaptureImplementationTweak>> {
public:
#if defined(OS_WIN)
scoped_refptr<IMFCaptureEngineOnSampleCallback> CreateMockPhotoCallback(
MockMFPhotoCallback* mock_photo_callback,
VideoCaptureDevice::TakePhotoCallback callback,
VideoCaptureFormat format) {
return scoped_refptr<IMFCaptureEngineOnSampleCallback>(mock_photo_callback);
}
#endif
void RunOpenInvalidDeviceTestCase();
void RunCaptureWithSizeTestCase();
void RunAllocateBadSizeTestCase();
void RunReAllocateCameraTestCase();
void RunCaptureMjpegTestCase();
void RunNoCameraSupportsPixelFormatMaxTestCase();
void RunTakePhotoTestCase();
void RunGetPhotoStateTestCase();
protected:
typedef VideoCaptureDevice::Client Client;
VideoCaptureDeviceTest()
: task_environment_(kMainThreadType),
main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
video_capture_client_(CreateDeviceClient()),
image_capture_client_(new MockImageCaptureClient()) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
local_gpu_memory_buffer_manager_ =
std::make_unique<LocalGpuMemoryBufferManager>();
VideoCaptureDeviceFactoryChromeOS::SetGpuBufferManager(
local_gpu_memory_buffer_manager_.get());
if (media::ShouldUseCrosCameraService() &&
!CameraHalDispatcherImpl::GetInstance()->IsStarted()) {
CameraHalDispatcherImpl::GetInstance()->Start(base::DoNothing(),
base::DoNothing());
}
#endif
video_capture_device_factory_ =
CreateVideoCaptureDeviceFactory(base::ThreadTaskRunnerHandle::Get());
}
void SetUp() override {
#if BUILDFLAG(IS_CHROMEOS_ASH)
chromeos::PowerManagerClient::InitializeFake();
#endif
#if defined(OS_ANDROID)
static_cast<VideoCaptureDeviceFactoryAndroid*>(
video_capture_device_factory_.get())
->ConfigureForTesting();
#elif defined(OS_WIN)
static_cast<VideoCaptureDeviceFactoryWin*>(
video_capture_device_factory_.get())
->set_use_media_foundation_for_testing(UseWinMediaFoundation());
#endif
}
void TearDown() override {
#if BUILDFLAG(IS_CHROMEOS_ASH)
chromeos::PowerManagerClient::Shutdown();
#endif
}
#if defined(OS_WIN)
bool UseWinMediaFoundation() {
return std::get<1>(GetParam()) == WIN_MEDIA_FOUNDATION &&
VideoCaptureDeviceFactoryWin::PlatformSupportsMediaFoundation();
}
#endif
std::unique_ptr<MockVideoCaptureDeviceClient> CreateDeviceClient() {
auto result = std::make_unique<NiceMockVideoCaptureDeviceClient>();
ON_CALL(*result, OnError(_, _, _)).WillByDefault(Invoke(DumpError));
EXPECT_CALL(*result, ReserveOutputBuffer(_, _, _, _)).Times(0);
EXPECT_CALL(*result, DoOnIncomingCapturedBuffer(_, _, _, _)).Times(0);
EXPECT_CALL(*result, DoOnIncomingCapturedBufferExt(_, _, _, _, _, _, _))
.Times(0);
ON_CALL(*result, OnIncomingCapturedData(_, _, _, _, _, _, _, _, _))
.WillByDefault(
Invoke([this](const uint8_t* data, int length,
const media::VideoCaptureFormat& frame_format,
const gfx::ColorSpace&, int, bool, base::TimeTicks,
base::TimeDelta, int) {
ASSERT_GT(length, 0);
ASSERT_TRUE(data);
main_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&VideoCaptureDeviceTest::OnFrameCaptured,
base::Unretained(this), frame_format));
}));
ON_CALL(*result, OnIncomingCapturedGfxBuffer(_, _, _, _, _, _))
.WillByDefault(Invoke([this](
gfx::GpuMemoryBuffer* buffer,
const media::VideoCaptureFormat& frame_format,
int, base::TimeTicks, base::TimeDelta, int) {
ASSERT_TRUE(buffer);
ASSERT_GT(buffer->GetSize().width() * buffer->GetSize().height(), 0);
main_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&VideoCaptureDeviceTest::OnFrameCaptured,
base::Unretained(this), frame_format));
}));
return result;
}
void OnFrameCaptured(const VideoCaptureFormat& format) {
last_format_ = format;
if (run_loop_)
run_loop_->Quit();
}
void WaitForCapturedFrame() {
run_loop_ = std::make_unique<base::RunLoop>(
base::RunLoop::Type::kNestableTasksAllowed);
run_loop_->Run();
}
absl::optional<VideoCaptureDeviceInfo> FindUsableDevice() {
base::RunLoop run_loop;
video_capture_device_factory_->GetDevicesInfo(base::BindLambdaForTesting(
[this, &run_loop](std::vector<VideoCaptureDeviceInfo> devices_info) {
devices_info_ = std::move(devices_info);
run_loop.Quit();
}));
run_loop.Run();
if (devices_info_.empty()) {
DLOG(WARNING) << "No camera found";
return absl::nullopt;
}
#if defined(OS_ANDROID)
for (const auto& device : devices_info_) {
// Android deprecated/legacy devices capture on a single thread, which is
// occupied by the tests, so nothing gets actually delivered.
// TODO(mcasas): use those devices' test mode to deliver frames in a
// background thread, https://crbug.com/626857
if (!VideoCaptureDeviceFactoryAndroid::IsLegacyOrDeprecatedDevice(
device.descriptor.device_id)) {
DLOG(INFO) << "Using camera " << device.descriptor.GetNameAndModel();
return device;
}
}
DLOG(WARNING) << "No usable camera found";
return absl::nullopt;
#else
auto device = devices_info_.front();
DLOG(INFO) << "Using camera " << device.descriptor.GetNameAndModel();
return device;
#endif
}
const VideoCaptureFormat& last_format() const { return last_format_; }
absl::optional<VideoCaptureDeviceInfo> GetFirstDeviceSupportingPixelFormat(
const VideoPixelFormat& pixel_format) {
if (!FindUsableDevice())
return absl::nullopt;
for (const auto& device : devices_info_) {
for (const auto& format : device.supported_formats) {
if (format.pixel_format == pixel_format) {
return device;
}
}
}
DVLOG_IF(1, pixel_format != PIXEL_FORMAT_MAX)
<< VideoPixelFormatToString(pixel_format);
return absl::nullopt;
}
bool IsCaptureSizeSupported(const VideoCaptureDeviceInfo& device_info,
const gfx::Size& size) {
auto& supported_formats = device_info.supported_formats;
const auto it = std::find_if(
supported_formats.begin(), supported_formats.end(),
[&size](VideoCaptureFormat const& f) { return f.frame_size == size; });
if (it == supported_formats.end()) {
DVLOG(1) << "Size " << size.ToString() << " is not supported.";
return false;
}
return true;
}
void RunTestCase(base::OnceClosure test_case) {
#if defined(OS_MAC)
// In order to make the test case run on the actual message loop that has
// been created for this thread, we need to run it inside a RunLoop. This is
// required, because on MacOS the capture code must run on a CFRunLoop
// enabled message loop.
base::RunLoop run_loop;
main_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](base::RunLoop* run_loop, base::OnceClosure* test_case) {
std::move(*test_case).Run();
run_loop->Quit();
},
&run_loop, &test_case));
run_loop.Run();
#else
std::move(test_case).Run();
#endif
}
#if defined(OS_WIN)
base::win::ScopedCOMInitializer initialize_com_;
#endif
base::test::SingleThreadTaskEnvironment task_environment_;
std::vector<VideoCaptureDeviceInfo> devices_info_;
std::unique_ptr<base::RunLoop> run_loop_;
scoped_refptr<base::TaskRunner> main_thread_task_runner_;
std::unique_ptr<MockVideoCaptureDeviceClient> video_capture_client_;
const scoped_refptr<MockImageCaptureClient> image_capture_client_;
VideoCaptureFormat last_format_;
#if BUILDFLAG(IS_CHROMEOS_ASH)
std::unique_ptr<LocalGpuMemoryBufferManager> local_gpu_memory_buffer_manager_;
#endif
std::unique_ptr<VideoCaptureDeviceFactory> video_capture_device_factory_;
};
// Cause hangs on Windows Debug. http://crbug.com/417824
#if (defined(OS_WIN) && !defined(NDEBUG))
#define MAYBE_UsingRealWebcam_OpenInvalidDevice \
DISABLED_UsingRealWebcam_OpenInvalidDevice
#else
#define MAYBE_UsingRealWebcam_OpenInvalidDevice \
UsingRealWebcam_OpenInvalidDevice
#endif
// Tries to allocate an invalid device and verifies it doesn't work.
WRAPPED_TEST_P(VideoCaptureDeviceTest,
MAYBE_UsingRealWebcam_OpenInvalidDevice) {
RunTestCase(
base::BindOnce(&VideoCaptureDeviceTest::RunOpenInvalidDeviceTestCase,
base::Unretained(this)));
}
void VideoCaptureDeviceTest::RunOpenInvalidDeviceTestCase() {
VideoCaptureDeviceDescriptor invalid_descriptor;
invalid_descriptor.device_id = "jibberish";
invalid_descriptor.set_display_name("jibberish");
#if defined(OS_WIN)
invalid_descriptor.capture_api =
VideoCaptureDeviceFactoryWin::PlatformSupportsMediaFoundation()
? VideoCaptureApi::WIN_MEDIA_FOUNDATION
: VideoCaptureApi::WIN_DIRECT_SHOW;
#elif defined(OS_MAC)
invalid_descriptor.capture_api = VideoCaptureApi::MACOSX_AVFOUNDATION;
#endif
std::unique_ptr<VideoCaptureDevice> device =
video_capture_device_factory_->CreateDevice(invalid_descriptor);
#if !defined(OS_MAC)
EXPECT_FALSE(device);
#else
// The presence of the actual device is only checked on AllocateAndStart()
// and not on creation.
EXPECT_CALL(*video_capture_client_, OnError(_, _, _)).Times(1);
VideoCaptureParams capture_params;
capture_params.requested_format.frame_size.SetSize(640, 480);
capture_params.requested_format.frame_rate = 30;
capture_params.requested_format.pixel_format = PIXEL_FORMAT_I420;
device->AllocateAndStart(capture_params, std::move(video_capture_client_));
device->StopAndDeAllocate();
#endif
}
// See crbug.com/805411.
TEST(VideoCaptureDeviceDescriptor, RemoveTrailingWhitespaceFromDisplayName) {
VideoCaptureDeviceDescriptor descriptor;
descriptor.set_display_name("My WebCam\n");
EXPECT_EQ(descriptor.display_name(), "My WebCam");
}
// Allocates the first enumerated device, and expects a frame.
WRAPPED_TEST_P(VideoCaptureDeviceTest, MAYBE_UsingRealWebcam_CaptureWithSize) {
RunTestCase(
base::BindOnce(&VideoCaptureDeviceTest::RunCaptureWithSizeTestCase,
base::Unretained(this)));
}
void VideoCaptureDeviceTest::RunCaptureWithSizeTestCase() {
const auto device_info = FindUsableDevice();
ASSERT_TRUE(device_info);
const gfx::Size& size = std::get<0>(GetParam());
if (!IsCaptureSizeSupported(*device_info, size))
return;
const int width = size.width();
const int height = size.height();
std::unique_ptr<VideoCaptureDevice> device(
video_capture_device_factory_->CreateDevice(device_info->descriptor));
ASSERT_TRUE(device);
EXPECT_CALL(*video_capture_client_, OnError(_, _, _)).Times(0);
EXPECT_CALL(*video_capture_client_, OnStarted());
VideoCaptureParams capture_params;
capture_params.requested_format.frame_size.SetSize(width, height);
capture_params.requested_format.frame_rate = 30.0f;
capture_params.requested_format.pixel_format = PIXEL_FORMAT_I420;
device->AllocateAndStart(capture_params, std::move(video_capture_client_));
WaitForCapturedFrame();
EXPECT_EQ(last_format().frame_size.width(), width);
EXPECT_EQ(last_format().frame_size.height(), height);
if (last_format().pixel_format != PIXEL_FORMAT_MJPEG)
EXPECT_EQ(size.GetArea(), last_format().frame_size.GetArea());
EXPECT_EQ(last_format().frame_rate, 30);
device->StopAndDeAllocate();
}
const gfx::Size kCaptureSizes[] = {gfx::Size(640, 480), gfx::Size(1280, 720)};
const VideoCaptureImplementationTweak kCaptureImplementationTweaks[] = {
NONE,
#if defined(OS_WIN)
WIN_MEDIA_FOUNDATION
#endif
};
INSTANTIATE_TEST_SUITE_P(
VideoCaptureDeviceTests,
VideoCaptureDeviceTest,
testing::Combine(testing::ValuesIn(kCaptureSizes),
testing::ValuesIn(kCaptureImplementationTweaks)));
// Allocates a device with an uncommon resolution and verifies frames are
// captured in a close, much more typical one.
WRAPPED_TEST_P(VideoCaptureDeviceTest, MAYBE_UsingRealWebcam_AllocateBadSize) {
RunTestCase(
base::BindOnce(&VideoCaptureDeviceTest::RunAllocateBadSizeTestCase,
base::Unretained(this)));
}
void VideoCaptureDeviceTest::RunAllocateBadSizeTestCase() {
const auto device_info = FindUsableDevice();
ASSERT_TRUE(device_info);
std::unique_ptr<VideoCaptureDevice> device(
video_capture_device_factory_->CreateDevice(device_info->descriptor));
ASSERT_TRUE(device);
EXPECT_CALL(*video_capture_client_, OnError(_, _, _)).Times(0);
EXPECT_CALL(*video_capture_client_, OnStarted());
const gfx::Size input_size(640, 480);
VideoCaptureParams capture_params;
capture_params.requested_format.frame_size.SetSize(637, 472);
capture_params.requested_format.frame_rate = 35;
capture_params.requested_format.pixel_format = PIXEL_FORMAT_I420;
device->AllocateAndStart(capture_params, std::move(video_capture_client_));
WaitForCapturedFrame();
device->StopAndDeAllocate();
EXPECT_EQ(last_format().frame_size.width(), input_size.width());
EXPECT_EQ(last_format().frame_size.height(), input_size.height());
if (last_format().pixel_format != PIXEL_FORMAT_MJPEG)
EXPECT_EQ(input_size.GetArea(), last_format().frame_size.GetArea());
}
// Cause hangs on Windows, Linux. Fails Android. https://crbug.com/417824
WRAPPED_TEST_P(VideoCaptureDeviceTest,
DISABLED_UsingRealWebcam_ReAllocateCamera) {
RunTestCase(
base::BindOnce(&VideoCaptureDeviceTest::RunReAllocateCameraTestCase,
base::Unretained(this)));
}
void VideoCaptureDeviceTest::RunReAllocateCameraTestCase() {
const auto device_info = FindUsableDevice();
ASSERT_TRUE(device_info);
// First, do a number of very fast device start/stops.
for (int i = 0; i <= 5; i++) {
video_capture_client_ = CreateDeviceClient();
std::unique_ptr<VideoCaptureDevice> device(
video_capture_device_factory_->CreateDevice(device_info->descriptor));
gfx::Size resolution;
if (i % 2)
resolution = gfx::Size(640, 480);
else
resolution = gfx::Size(1280, 1024);
VideoCaptureParams capture_params;
capture_params.requested_format.frame_size = resolution;
capture_params.requested_format.frame_rate = 30;
capture_params.requested_format.pixel_format = PIXEL_FORMAT_I420;
device->AllocateAndStart(capture_params, std::move(video_capture_client_));
device->StopAndDeAllocate();
}
// Finally, do a device start and wait for it to finish.
VideoCaptureParams capture_params;
capture_params.requested_format.frame_size.SetSize(320, 240);
capture_params.requested_format.frame_rate = 30;
capture_params.requested_format.pixel_format = PIXEL_FORMAT_I420;
video_capture_client_ = CreateDeviceClient();
std::unique_ptr<VideoCaptureDevice> device(
video_capture_device_factory_->CreateDevice(device_info->descriptor));
device->AllocateAndStart(capture_params, std::move(video_capture_client_));
WaitForCapturedFrame();
device->StopAndDeAllocate();
device.reset();
EXPECT_EQ(last_format().frame_size.width(), 320);
EXPECT_EQ(last_format().frame_size.height(), 240);
}
// Starts the camera in 720p to try and capture MJPEG format.
WRAPPED_TEST_P(VideoCaptureDeviceTest, MAYBE_UsingRealWebcam_CaptureMjpeg) {
RunTestCase(base::BindOnce(&VideoCaptureDeviceTest::RunCaptureMjpegTestCase,
base::Unretained(this)));
}
void VideoCaptureDeviceTest::RunCaptureMjpegTestCase() {
#if BUILDFLAG(IS_CHROMEOS_ASH)
if (media::ShouldUseCrosCameraService()) {
VLOG(1)
<< "Skipped on Chrome OS device where HAL v3 camera service is used";
return;
}
#endif
auto device_info = GetFirstDeviceSupportingPixelFormat(PIXEL_FORMAT_MJPEG);
ASSERT_TRUE(device_info);
#if defined(OS_WIN)
base::win::Version version = base::win::GetVersion();
if (version >= base::win::Version::WIN10) {
VLOG(1) << "Skipped on Win10: http://crbug.com/570604, current: "
<< static_cast<int>(version);
return;
}
#endif
std::unique_ptr<VideoCaptureDevice> device(
video_capture_device_factory_->CreateDevice(device_info->descriptor));
ASSERT_TRUE(device);
EXPECT_CALL(*video_capture_client_, OnError(_, _, _)).Times(0);
EXPECT_CALL(*video_capture_client_, OnStarted());
VideoCaptureParams capture_params;
capture_params.requested_format.frame_size.SetSize(1280, 720);
capture_params.requested_format.frame_rate = 30;
capture_params.requested_format.pixel_format = PIXEL_FORMAT_MJPEG;
device->AllocateAndStart(capture_params, std::move(video_capture_client_));
WaitForCapturedFrame();
// Verify we get MJPEG from the device. Not all devices can capture 1280x720
// @ 30 fps, so we don't care about the exact resolution we get.
EXPECT_EQ(last_format().pixel_format, PIXEL_FORMAT_MJPEG);
EXPECT_GE(static_cast<size_t>(1280 * 720),
media::VideoFrame::AllocationSize(last_format().pixel_format,
last_format().frame_size));
device->StopAndDeAllocate();
}
#define MAYBE_UsingRealWebcam_NoCameraSupportsPixelFormatMax \
UsingRealWebcam_NoCameraSupportsPixelFormatMax
WRAPPED_TEST_P(VideoCaptureDeviceTest,
MAYBE_UsingRealWebcam_NoCameraSupportsPixelFormatMax) {
RunTestCase(base::BindOnce(
&VideoCaptureDeviceTest::RunNoCameraSupportsPixelFormatMaxTestCase,
base::Unretained(this)));
}
void VideoCaptureDeviceTest::RunNoCameraSupportsPixelFormatMaxTestCase() {
// Use PIXEL_FORMAT_MAX to iterate all device names for testing
// GetDeviceSupportedFormats().
auto device_descriptor =
GetFirstDeviceSupportingPixelFormat(PIXEL_FORMAT_MAX);
// Verify no camera returned for PIXEL_FORMAT_MAX. Nothing else to test here
// since we cannot forecast the hardware capabilities.
ASSERT_FALSE(device_descriptor);
}
// Starts the camera and verifies that a photo can be taken. The correctness of
// the photo is enforced by MockImageCaptureClient.
WRAPPED_TEST_P(VideoCaptureDeviceTest, MAYBE_UsingRealWebcam_TakePhoto) {
RunTestCase(base::BindOnce(&VideoCaptureDeviceTest::RunTakePhotoTestCase,
base::Unretained(this)));
}
void VideoCaptureDeviceTest::RunTakePhotoTestCase() {
const auto device_info = FindUsableDevice();
ASSERT_TRUE(device_info);
if (device_info->supported_formats.empty())
return;
const gfx::Size frame_size =
device_info->supported_formats.front().frame_size;
#if defined(OS_ANDROID)
// TODO(mcasas): fails on Lollipop devices, reconnect https://crbug.com/646840
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SDK_VERSION_MARSHMALLOW) {
return;
}
#endif
std::unique_ptr<VideoCaptureDevice> device(
video_capture_device_factory_->CreateDevice(device_info->descriptor));
ASSERT_TRUE(device);
EXPECT_CALL(*video_capture_client_, OnError(_, _, _)).Times(0);
EXPECT_CALL(*video_capture_client_, OnStarted()).Times(testing::AtLeast(1));
VideoCaptureParams capture_params;
capture_params.requested_format.frame_size = frame_size;
capture_params.requested_format.frame_rate = 30;
capture_params.requested_format.pixel_format = PIXEL_FORMAT_I420;
device->AllocateAndStart(capture_params, std::move(video_capture_client_));
VideoCaptureDevice::TakePhotoCallback scoped_callback = base::BindOnce(
&MockImageCaptureClient::DoOnPhotoTaken, image_capture_client_);
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
base::RepeatingClosure quit_closure =
BindToCurrentLoop(run_loop.QuitClosure());
EXPECT_CALL(*image_capture_client_.get(), OnCorrectPhotoTaken())
.Times(1)
.WillOnce(RunClosure(quit_closure));
device->TakePhoto(std::move(scoped_callback));
run_loop.Run();
device->StopAndDeAllocate();
}
// Starts the camera and verifies that the photo capabilities can be retrieved.
WRAPPED_TEST_P(VideoCaptureDeviceTest, MAYBE_UsingRealWebcam_GetPhotoState) {
RunTestCase(base::BindOnce(&VideoCaptureDeviceTest::RunGetPhotoStateTestCase,
base::Unretained(this)));
}
void VideoCaptureDeviceTest::RunGetPhotoStateTestCase() {
const auto device_info = FindUsableDevice();
ASSERT_TRUE(device_info);
if (device_info->supported_formats.empty())
return;
const gfx::Size frame_size =
device_info->supported_formats.front().frame_size;
#if defined(OS_ANDROID)
// TODO(mcasas): fails on Lollipop devices, reconnect https://crbug.com/646840
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SDK_VERSION_MARSHMALLOW) {
return;
}
#endif
std::unique_ptr<VideoCaptureDevice> device(
video_capture_device_factory_->CreateDevice(device_info->descriptor));
ASSERT_TRUE(device);
EXPECT_CALL(*video_capture_client_, OnError(_, _, _)).Times(0);
EXPECT_CALL(*video_capture_client_, OnStarted());
VideoCaptureParams capture_params;
capture_params.requested_format.frame_size = frame_size;
capture_params.requested_format.frame_rate = 30;
capture_params.requested_format.pixel_format = PIXEL_FORMAT_I420;
device->AllocateAndStart(capture_params, std::move(video_capture_client_));
VideoCaptureDevice::GetPhotoStateCallback scoped_get_callback =
base::BindOnce(&MockImageCaptureClient::DoOnGetPhotoState,
image_capture_client_);
// On Chrome OS AllocateAndStart() is asynchronous, so wait until we get the
// first frame.
WaitForCapturedFrame();
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
base::RepeatingClosure quit_closure =
BindToCurrentLoop(run_loop.QuitClosure());
EXPECT_CALL(*image_capture_client_.get(), OnCorrectGetPhotoState())
.Times(1)
.WillOnce(RunClosure(quit_closure));
device->GetPhotoState(std::move(scoped_get_callback));
run_loop.Run();
ASSERT_TRUE(image_capture_client_->capabilities());
device->StopAndDeAllocate();
}
#if defined(OS_WIN)
// Verifies that the photo callback is correctly released by MediaFoundation
WRAPPED_TEST_P(VideoCaptureDeviceTest,
MAYBE_UsingRealWebcam_CheckPhotoCallbackRelease) {
if (!UseWinMediaFoundation())
return;
auto device_info = GetFirstDeviceSupportingPixelFormat(PIXEL_FORMAT_MJPEG);
ASSERT_TRUE(device_info);
EXPECT_CALL(*video_capture_client_, OnError(_, _, _)).Times(0);
EXPECT_CALL(*video_capture_client_, OnStarted());
std::unique_ptr<VideoCaptureDevice> device(
video_capture_device_factory_->CreateDevice(device_info->descriptor));
ASSERT_TRUE(device);
VideoCaptureParams capture_params;
capture_params.requested_format.frame_size.SetSize(320, 240);
capture_params.requested_format.frame_rate = 30;
capture_params.requested_format.pixel_format = PIXEL_FORMAT_MJPEG;
device->AllocateAndStart(capture_params, std::move(video_capture_client_));
if (!static_cast<VideoCaptureDeviceMFWin*>(device.get())
->get_use_photo_stream_to_take_photo_for_testing()) {
DVLOG(1) << "The device is not using the MediaFoundation photo callback. "
"Exiting test.";
device->StopAndDeAllocate();
return;
}
MockMFPhotoCallback* callback = new MockMFPhotoCallback();
EXPECT_CALL(*callback, DoQueryInterface(_, _)).WillRepeatedly(Return(S_OK));
EXPECT_CALL(*callback, DoAddRef()).WillOnce(Return(1U));
EXPECT_CALL(*callback, DoRelease()).WillOnce(Return(1U));
EXPECT_CALL(*callback, DoOnSample(_)).WillOnce(Return(S_OK));
static_cast<VideoCaptureDeviceMFWin*>(device.get())
->set_create_mf_photo_callback_for_testing(base::BindRepeating(
&VideoCaptureDeviceTest::CreateMockPhotoCallback,
base::Unretained(this), base::Unretained(callback)));
VideoCaptureDevice::TakePhotoCallback scoped_callback = base::BindOnce(
&MockImageCaptureClient::DoOnPhotoTaken, image_capture_client_);
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
base::RepeatingClosure quit_closure =
BindToCurrentLoop(run_loop.QuitClosure());
EXPECT_CALL(*image_capture_client_.get(), OnCorrectPhotoTaken())
.WillOnce(RunClosure(quit_closure));
device->TakePhoto(std::move(scoped_callback));
run_loop.Run();
device->StopAndDeAllocate();
}
#endif
} // namespace media