Chromium Code Reviews| Index: media/capture/video/android/video_capture_device_tango_android_unittest.cc |
| diff --git a/media/capture/video/android/video_capture_device_tango_android_unittest.cc b/media/capture/video/android/video_capture_device_tango_android_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..cbb4f96becc472efd65507935c5fdd6beae5cdec |
| --- /dev/null |
| +++ b/media/capture/video/android/video_capture_device_tango_android_unittest.cc |
| @@ -0,0 +1,264 @@ |
| +// Copyright 2017 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 "video_capture_device_tango_android.h" |
| +#include "base/android/jni_android.h" |
| +#include "base/strings/string_number_conversions.h" |
| +#include "base/sys_info.h" |
| +#include "base/test/scoped_task_environment.h" |
| +#include "media/capture/video/android/video_capture_device_factory_android.h" |
| +#include "media/capture/video/android/video_capture_device_tango_android.h" |
| +#include "media/capture/video_capture_types.h" |
| +#include "testing/gmock/include/gmock/gmock.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| + |
| +namespace media { |
| + |
| +namespace { |
| + |
| +const int kWidth = 214; |
| +const int kHeight = 120; |
| +// Projected point cloud values to color frame could be are outside color frame |
| +// bounds. kMargin defines how larger is the depth frame area compared to color. |
| +const int kMargin = 20; |
| + |
| +bool IsTangoDevice() { |
| + const std::string model = base::SysInfo::HardwareModelName(); |
| + return (model == "Project Tango Tablet Development Kit" || |
| + model == "Lenovo PB2-690M"); |
|
mcasas
2017/07/15 00:12:37
Usually these name lists are moved out as static a
|
| +} |
| + |
| +void createTestTangoPointCloud(float* data, |
| + int xstart, |
| + int ystart, |
| + int xend, |
| + int yend, |
| + float cx, |
| + float cy, |
| + float fx, |
| + float fy) { |
| + float* out = data; |
| + for (int i = ystart; i < yend; i++) { |
| + for (int j = xstart; j < xend; j++) { |
| + // Skip some elements to mimic Tango behavior. |
| + if ((j % 3 && (i + j) % 15 == 0) || j % 20 == 0) |
| + continue; |
| + // Generate depth value based on row and column. Maximum value is 16m. |
| + float depth = |
| + 16.0 / (yend - ystart + xend - xstart) * (j - xstart + i - ystart); |
| + float xvalue = depth * (j - cx) / fx; |
| + float yvalue = depth * (i - cy) / fy; |
| + out[0] = xvalue; |
| + out[1] = yvalue; |
| + out[2] = depth; |
| + out[3] = 1.0; |
| + out += 4; |
| + } |
| + } |
| +} |
| + |
| +} // namespace |
| + |
| +class MockVideoCaptureClient : public VideoCaptureDevice::Client { |
| + public: |
| + MOCK_METHOD2(OnError, |
| + void(const tracked_objects::Location& from_here, |
| + const std::string& reason)); |
| + MOCK_CONST_METHOD0(GetBufferPoolUtilization, double(void)); |
| + MOCK_METHOD0(OnStarted, void(void)); |
| + |
| + explicit MockVideoCaptureClient( |
| + base::Callback<void(const uint8_t*, int, const VideoCaptureFormat&)> |
| + frame_cb) |
| + : frame_cb_(frame_cb) {} |
| + |
| + void OnIncomingCapturedData(const uint8_t* data, |
| + int length, |
| + const VideoCaptureFormat& format, |
| + int rotation, |
| + base::TimeTicks reference_time, |
| + base::TimeDelta timestamp, |
| + int frame_feedback_id) override { |
| + ASSERT_GT(length, 0); |
| + ASSERT_TRUE(data); |
| + frame_cb_.Run(data, length, format); |
| + } |
| + |
| + // Trampoline methods to workaround GMOCK problems with std::unique_ptr<>. |
| + Buffer ReserveOutputBuffer(const gfx::Size& dimensions, |
| + media::VideoPixelFormat format, |
| + media::VideoPixelStorage storage, |
| + int frame_feedback_id) override { |
| + NOTREACHED() << "This should never be called"; |
| + return Buffer(); |
| + } |
| + void OnIncomingCapturedBuffer(Buffer buffer, |
| + const VideoCaptureFormat& format, |
| + base::TimeTicks reference_time, |
| + base::TimeDelta timestamp) override { |
| + NOTREACHED() << "This should never be called"; |
| + } |
| + void OnIncomingCapturedBufferExt( |
| + Buffer buffer, |
| + const VideoCaptureFormat& format, |
| + base::TimeTicks reference_time, |
| + base::TimeDelta timestamp, |
| + gfx::Rect visible_rect, |
| + const VideoFrameMetadata& additional_metadata) override { |
| + NOTREACHED() << "This should never be called"; |
| + } |
| + Buffer ResurrectLastOutputBuffer(const gfx::Size& dimensions, |
| + media::VideoPixelFormat format, |
| + media::VideoPixelStorage storage, |
| + int frame_feedback_id) { |
| + NOTREACHED() << "This should never be called"; |
| + return Buffer(); |
| + } |
| + |
| + private: |
| + base::Callback<void(const uint8_t*, int, const VideoCaptureFormat&)> |
| + frame_cb_; |
| +}; |
| + |
| +class VideoCaptureDeviceTangoAndroidTest |
| + : public testing::TestWithParam<gfx::Size> { |
| + protected: |
| + VideoCaptureDeviceTangoAndroidTest() |
| + : video_capture_device_factory_(VideoCaptureDeviceFactory::CreateFactory( |
| + base::ThreadTaskRunnerHandle::Get())), |
| + device_descriptors_(new VideoCaptureDeviceDescriptors()), |
| + video_capture_client_(new MockVideoCaptureClient( |
| + base::Bind(&VideoCaptureDeviceTangoAndroidTest::OnFrameCaptured, |
| + base::Unretained(this)))) {} |
| + |
| + void SetUp() override { |
| + video_capture_device_factory_->GetDeviceDescriptors( |
| + device_descriptors_.get()); |
| + VideoCaptureDeviceAndroid::RegisterVideoCaptureDevice( |
| + base::android::AttachCurrentThread()); |
| + |
| + static_cast<VideoCaptureDeviceFactoryAndroid*>( |
| + video_capture_device_factory_.get()) |
| + ->ConfigureForTesting(); |
| + } |
| + |
| + std::unique_ptr<VideoCaptureDeviceDescriptor> GetTangoDepthDescriptor() { |
| + for (const auto& descriptor : *device_descriptors_) { |
| + if (descriptor.capture_api == VideoCaptureApi::ANDROID_TANGO) { |
| + return std::unique_ptr<VideoCaptureDeviceDescriptor>( |
| + new VideoCaptureDeviceDescriptor(descriptor)); |
| + } |
| + } |
| + return nullptr; |
| + } |
| + |
| + void OnFrameCaptured(const uint8_t* data, |
| + int length, |
| + const VideoCaptureFormat& format) { |
| + memcpy(last_data_, data, length); |
| + last_length_ = length; |
| + last_format_ = format; |
| + } |
| + |
| + static std::unique_ptr<VideoCaptureDeviceTangoAndroid::TangoIntrinsics> |
| + GetIntrinsics(const VideoCaptureDeviceTangoAndroid& tango_device) { |
| + if (!tango_device.intrinsics_) |
| + return nullptr; |
| + return std::unique_ptr<VideoCaptureDeviceTangoAndroid::TangoIntrinsics>( |
| + new VideoCaptureDeviceTangoAndroid::TangoIntrinsics( |
| + *(tango_device.intrinsics_))); |
| + } |
| + |
| + static jobject GetJavaCaptureObject( |
| + const VideoCaptureDeviceTangoAndroid& tango_device) { |
| + return tango_device.j_capture_.obj(); |
| + } |
| + |
| + base::test::ScopedTaskEnvironment scoped_task_environment_; |
| + const std::unique_ptr<VideoCaptureDeviceFactory> |
| + video_capture_device_factory_; |
| + std::unique_ptr<VideoCaptureDeviceDescriptors> device_descriptors_; |
| + std::unique_ptr<MockVideoCaptureClient> video_capture_client_; |
| + VideoCaptureFormat last_format_; |
| + uint16_t last_data_[kWidth * kHeight] = {0}; |
| + int last_length_ = 0; |
| +}; |
| + |
| +TEST_F(VideoCaptureDeviceTangoAndroidTest, OnPointCloudAvailable) { |
| + VideoCaptureDeviceFactoryAndroid* android_factory = |
| + static_cast<VideoCaptureDeviceFactoryAndroid*>( |
| + video_capture_device_factory_.get()); |
| + |
| + std::unique_ptr<VideoCaptureDeviceDescriptor> tango_depth_descriptor( |
| + GetTangoDepthDescriptor()); |
| + ASSERT_TRUE(IsTangoDevice() ^ tango_depth_descriptor == nullptr); |
| + if (!tango_depth_descriptor) { |
| + // Create mock Tango device. Tango depth id value is equal to number of |
| + // system cameras; see VideoCaptureFactory.java. |
| + tango_depth_descriptor = std::unique_ptr<VideoCaptureDeviceDescriptor>( |
| + new VideoCaptureDeviceDescriptor( |
| + "Tango Mock Test Depth", |
| + base::IntToString(device_descriptors_->size()), |
| + VideoCaptureApi::ANDROID_TANGO)); |
| + } |
| + std::unique_ptr<VideoCaptureDevice> device = |
| + android_factory->CreateDevice(*tango_depth_descriptor); |
| + ASSERT_TRUE(device); |
| + |
| + VideoCaptureDeviceTangoAndroid* tango_device = |
| + static_cast<VideoCaptureDeviceTangoAndroid*>(device.get()); |
| + const auto intrinsics_null = GetIntrinsics(*tango_device); |
| + ASSERT_FALSE(intrinsics_null); |
| + |
| + VideoCaptureParams capture_params; |
| + capture_params.requested_format.frame_size.SetSize(kWidth, kHeight); |
| + capture_params.requested_format.frame_rate = 5; |
| + capture_params.requested_format.pixel_format = PIXEL_FORMAT_Y16; |
| + tango_device->AllocateAndStart(capture_params, |
| + std::move(video_capture_client_)); |
| + |
| + JNIEnv* env = base::android::AttachCurrentThread(); |
| + |
| + const int length = (kWidth + 2 * kMargin) * (kHeight + 2 * kMargin); |
| + float buffer[length * 4] = {0}; |
| + jobject byte_buffer = |
| + env->NewDirectByteBuffer(static_cast<void*>(buffer), sizeof(buffer)); |
| + |
| + // Call this once before running with the real values to get tango intrinsics |
| + // as they are fetched on first OnPointCloudAvailable call. |
| + tango_device->OnPointCloudAvailable(env, GetJavaCaptureObject(*tango_device), |
| + byte_buffer, length, 0); |
| + |
| + // Mock values for intrinsics are set in VideoCaptureTango.java. |
| + const auto intrinsics = GetIntrinsics(*tango_device); |
| + ASSERT_TRUE(intrinsics); |
| + EXPECT_NEAR(213.333333, intrinsics->fx, 0.000005); |
| + EXPECT_NEAR(213.333333, intrinsics->fy, 0.000005); |
| + EXPECT_NEAR(106.666667, intrinsics->cx, 0.000005); |
| + EXPECT_EQ(60., intrinsics->cy); |
| + |
| + createTestTangoPointCloud(buffer, -kMargin, -kMargin, kWidth + kMargin, |
| + kHeight + kMargin, intrinsics->cx, intrinsics->cy, |
| + intrinsics->fx, intrinsics->fy); |
| + tango_device->OnPointCloudAvailable(env, GetJavaCaptureObject(*tango_device), |
| + byte_buffer, length, 0); |
| + |
| + // Verify the conversion of the generated point cloud to depth buffer. |
| + for (int i = 0; i < kHeight; i++) { |
| + for (int j = 0; j < kWidth; j++) { |
| + // Skip the same elements as in createTestTangoPointCloud. |
| + if ((j % 3 && (i + j) % 15 == 0) || j % 20 == 0) { |
| + continue; |
| + } |
| + // Generate depth value based on row and column. Maximum value is 16m. |
| + float depth = |
| + 16.0 / (kWidth + kHeight + 4 * kMargin) * (j + kMargin + i + kMargin); |
| + EXPECT_EQ(static_cast<uint16_t>(depth * 4096), last_data_[j + i * kWidth]) |
| + << "x:" << j << " ,y:" << i; |
| + } |
| + } |
| + tango_device->StopAndDeAllocate(); |
| +} |
| + |
| +} // namespace media |