| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "video_capture_device_tango_android.h" |
| 6 #include "base/android/jni_android.h" |
| 7 #include "base/strings/string_number_conversions.h" |
| 8 #include "base/sys_info.h" |
| 9 #include "base/test/scoped_task_environment.h" |
| 10 #include "media/capture/video/android/tango_api.h" |
| 11 #include "media/capture/video/android/video_capture_device_factory_android.h" |
| 12 #include "media/capture/video/android/video_capture_device_tango_android.h" |
| 13 #include "media/capture/video_capture_types.h" |
| 14 #include "testing/gmock/include/gmock/gmock.h" |
| 15 #include "testing/gtest/include/gtest/gtest.h" |
| 16 |
| 17 using Intrinsics = media::TangoApi::TangoCameraIntrinsics; |
| 18 |
| 19 namespace media { |
| 20 |
| 21 namespace { |
| 22 |
| 23 const int kWidth = 214; |
| 24 const int kHeight = 120; |
| 25 // Projected point cloud values to color frame could be are outside color frame |
| 26 // bounds. kMargin defines how larger is the depth frame area compared to color. |
| 27 const int kMargin = 20; |
| 28 |
| 29 bool IsTangoDevice() { |
| 30 static const char* tango_models[] = {"Project Tango Tablet Development Kit", |
| 31 "Lenovo PB2-690M", "ASUS_A002", |
| 32 "ASUS_A002A"}; |
| 33 const std::string model = base::SysInfo::HardwareModelName(); |
| 34 const char** result = |
| 35 std::find(tango_models, tango_models + arraysize(tango_models), model); |
| 36 return (result < tango_models + arraysize(tango_models)); |
| 37 } |
| 38 |
| 39 void OnPointCloudAvailable(void* context, |
| 40 const TangoApi::TangoPointCloud* point_cloud) {} |
| 41 |
| 42 void createTestTangoPointCloud(float* data, |
| 43 int xstart, |
| 44 int ystart, |
| 45 int xend, |
| 46 int yend, |
| 47 float cx, |
| 48 float cy, |
| 49 float fx, |
| 50 float fy) { |
| 51 float* out = data; |
| 52 for (int i = ystart; i < yend; i++) { |
| 53 for (int j = xstart; j < xend; j++) { |
| 54 // Skip some elements to mimic Tango behavior. |
| 55 if ((j % 3 && (i + j) % 15 == 0) || j % 20 == 0) |
| 56 continue; |
| 57 // Generate depth value based on row and column. Maximum value is 16m. |
| 58 float depth = |
| 59 16.0 / (yend - ystart + xend - xstart) * (j - xstart + i - ystart); |
| 60 float xvalue = depth * (j - cx) / fx; |
| 61 float yvalue = depth * (i - cy) / fy; |
| 62 out[0] = xvalue; |
| 63 out[1] = yvalue; |
| 64 out[2] = depth; |
| 65 out[3] = 1.0; |
| 66 out += 4; |
| 67 } |
| 68 } |
| 69 } |
| 70 |
| 71 } // namespace |
| 72 |
| 73 class MockVideoCaptureClient : public VideoCaptureDevice::Client { |
| 74 public: |
| 75 MOCK_METHOD2(OnError, |
| 76 void(const tracked_objects::Location& from_here, |
| 77 const std::string& reason)); |
| 78 MOCK_CONST_METHOD0(GetBufferPoolUtilization, double(void)); |
| 79 MOCK_METHOD0(OnStarted, void(void)); |
| 80 |
| 81 explicit MockVideoCaptureClient( |
| 82 base::Callback<void(const uint8_t*, int, const VideoCaptureFormat&)> |
| 83 frame_cb) |
| 84 : frame_cb_(frame_cb) {} |
| 85 |
| 86 void OnIncomingCapturedData(const uint8_t* data, |
| 87 int length, |
| 88 const VideoCaptureFormat& format, |
| 89 int rotation, |
| 90 base::TimeTicks reference_time, |
| 91 base::TimeDelta timestamp, |
| 92 int frame_feedback_id) override { |
| 93 ASSERT_GT(length, 0); |
| 94 ASSERT_TRUE(data); |
| 95 frame_cb_.Run(data, length, format); |
| 96 } |
| 97 |
| 98 // Trampoline methods to workaround GMOCK problems with std::unique_ptr<>. |
| 99 Buffer ReserveOutputBuffer(const gfx::Size& dimensions, |
| 100 media::VideoPixelFormat format, |
| 101 media::VideoPixelStorage storage, |
| 102 int frame_feedback_id) override { |
| 103 NOTREACHED() << "This should never be called"; |
| 104 return Buffer(); |
| 105 } |
| 106 void OnIncomingCapturedBuffer(Buffer buffer, |
| 107 const VideoCaptureFormat& format, |
| 108 base::TimeTicks reference_time, |
| 109 base::TimeDelta timestamp) override { |
| 110 NOTREACHED() << "This should never be called"; |
| 111 } |
| 112 void OnIncomingCapturedBufferExt( |
| 113 Buffer buffer, |
| 114 const VideoCaptureFormat& format, |
| 115 base::TimeTicks reference_time, |
| 116 base::TimeDelta timestamp, |
| 117 gfx::Rect visible_rect, |
| 118 const VideoFrameMetadata& additional_metadata) override { |
| 119 NOTREACHED() << "This should never be called"; |
| 120 } |
| 121 Buffer ResurrectLastOutputBuffer(const gfx::Size& dimensions, |
| 122 media::VideoPixelFormat format, |
| 123 media::VideoPixelStorage storage, |
| 124 int frame_feedback_id) { |
| 125 NOTREACHED() << "This should never be called"; |
| 126 return Buffer(); |
| 127 } |
| 128 |
| 129 private: |
| 130 base::Callback<void(const uint8_t*, int, const VideoCaptureFormat&)> |
| 131 frame_cb_; |
| 132 }; |
| 133 |
| 134 class VideoCaptureDeviceTangoAndroidTest |
| 135 : public testing::TestWithParam<gfx::Size> { |
| 136 protected: |
| 137 VideoCaptureDeviceTangoAndroidTest() |
| 138 : video_capture_device_factory_(VideoCaptureDeviceFactory::CreateFactory( |
| 139 base::ThreadTaskRunnerHandle::Get())), |
| 140 device_descriptors_(new VideoCaptureDeviceDescriptors()), |
| 141 video_capture_client_(new MockVideoCaptureClient( |
| 142 base::Bind(&VideoCaptureDeviceTangoAndroidTest::OnFrameCaptured, |
| 143 base::Unretained(this)))) {} |
| 144 |
| 145 void SetUp() override { |
| 146 video_capture_device_factory_->GetDeviceDescriptors( |
| 147 device_descriptors_.get()); |
| 148 VideoCaptureDeviceTangoAndroid::RegisterVideoCaptureDevice( |
| 149 base::android::AttachCurrentThread()); |
| 150 |
| 151 static_cast<VideoCaptureDeviceFactoryAndroid*>( |
| 152 video_capture_device_factory_.get()) |
| 153 ->ConfigureForTesting(); |
| 154 } |
| 155 |
| 156 std::unique_ptr<VideoCaptureDeviceDescriptor> GetTangoDepthDescriptor() { |
| 157 for (const auto& descriptor : *device_descriptors_) { |
| 158 if (descriptor.capture_api == VideoCaptureApi::ANDROID_TANGO) { |
| 159 return std::unique_ptr<VideoCaptureDeviceDescriptor>( |
| 160 new VideoCaptureDeviceDescriptor(descriptor)); |
| 161 } |
| 162 } |
| 163 return nullptr; |
| 164 } |
| 165 |
| 166 void OnFrameCaptured(const uint8_t* data, |
| 167 int length, |
| 168 const VideoCaptureFormat& format) { |
| 169 memcpy(last_data_, data, length); |
| 170 last_length_ = length; |
| 171 last_format_ = format; |
| 172 } |
| 173 |
| 174 void SetIntrinsics(VideoCaptureDeviceTangoAndroid* tango_device, |
| 175 double cx, |
| 176 double cy, |
| 177 double fx, |
| 178 double fy) { |
| 179 tango_device->intrinsics_ = std::unique_ptr<Intrinsics>(new Intrinsics()); |
| 180 tango_device->intrinsics_->cx = cx; |
| 181 tango_device->intrinsics_->cy = cy; |
| 182 tango_device->intrinsics_->fx = fx; |
| 183 tango_device->intrinsics_->fy = fy; |
| 184 } |
| 185 |
| 186 // Non-Tango devices enter error state and we need to force it to configured |
| 187 // in order to deliver the frame to client. |
| 188 void SetConfiguredState(VideoCaptureDeviceTangoAndroid* tango_device) { |
| 189 tango_device->state_ = VideoCaptureDeviceTangoAndroid::kConfigured; |
| 190 } |
| 191 |
| 192 std::unique_ptr<TangoApi> CreateMockTangoApi() { |
| 193 return std::unique_ptr<TangoApi>(new TangoApi(nullptr)); |
| 194 } |
| 195 |
| 196 base::test::ScopedTaskEnvironment scoped_task_environment_; |
| 197 const std::unique_ptr<VideoCaptureDeviceFactory> |
| 198 video_capture_device_factory_; |
| 199 std::unique_ptr<VideoCaptureDeviceDescriptors> device_descriptors_; |
| 200 std::unique_ptr<MockVideoCaptureClient> video_capture_client_; |
| 201 VideoCaptureFormat last_format_; |
| 202 uint16_t last_data_[kWidth * kHeight] = {0}; |
| 203 int last_length_ = 0; |
| 204 }; |
| 205 |
| 206 TEST_F(VideoCaptureDeviceTangoAndroidTest, OnPointCloudAvailable) { |
| 207 VideoCaptureDeviceFactoryAndroid* android_factory = |
| 208 static_cast<VideoCaptureDeviceFactoryAndroid*>( |
| 209 video_capture_device_factory_.get()); |
| 210 |
| 211 std::unique_ptr<VideoCaptureDeviceDescriptor> tango_depth_descriptor( |
| 212 GetTangoDepthDescriptor()); |
| 213 ASSERT_TRUE(IsTangoDevice() ^ tango_depth_descriptor == nullptr); |
| 214 if (!tango_depth_descriptor) { |
| 215 // Create mock Tango device. Tango depth id value is equal to number of |
| 216 // system cameras; see VideoCaptureFactory.java. |
| 217 tango_depth_descriptor = std::unique_ptr<VideoCaptureDeviceDescriptor>( |
| 218 new VideoCaptureDeviceDescriptor( |
| 219 "Tango Mock Test Depth", |
| 220 base::IntToString(device_descriptors_->size()), |
| 221 VideoCaptureApi::ANDROID_TANGO)); |
| 222 } |
| 223 std::unique_ptr<VideoCaptureDevice> device = |
| 224 android_factory->CreateDevice(*tango_depth_descriptor); |
| 225 ASSERT_TRUE(device); |
| 226 |
| 227 VideoCaptureDeviceTangoAndroid* tango_device = |
| 228 static_cast<VideoCaptureDeviceTangoAndroid*>(device.get()); |
| 229 |
| 230 VideoCaptureParams capture_params; |
| 231 capture_params.requested_format.frame_size.SetSize(kWidth, kHeight); |
| 232 capture_params.requested_format.frame_rate = 5; |
| 233 capture_params.requested_format.pixel_format = PIXEL_FORMAT_Y16; |
| 234 tango_device->AllocateAndStart(capture_params, |
| 235 std::move(video_capture_client_)); |
| 236 if (!IsTangoDevice()) |
| 237 SetConfiguredState(tango_device); |
| 238 |
| 239 const int length = (kWidth + 2 * kMargin) * (kHeight + 2 * kMargin); |
| 240 float buffer[length * 4] = {0}; |
| 241 |
| 242 const double cx = 1920. / 18; |
| 243 const double cy = 1080. / 18; |
| 244 const double fxy = 1920. / 9; |
| 245 SetIntrinsics(tango_device, cx, cy, fxy, fxy); |
| 246 |
| 247 createTestTangoPointCloud(buffer, -kMargin, -kMargin, kWidth + kMargin, |
| 248 kHeight + kMargin, cx, cy, fxy, fxy); |
| 249 tango_device->OnPointCloudAvailable(length, buffer, 0); |
| 250 |
| 251 // Verify the conversion of the generated point cloud to depth buffer. |
| 252 for (int i = 0; i < kHeight; i++) { |
| 253 for (int j = 0; j < kWidth; j++) { |
| 254 // Skip the same elements as in createTestTangoPointCloud. |
| 255 if ((j % 3 && (i + j) % 15 == 0) || j % 20 == 0) { |
| 256 continue; |
| 257 } |
| 258 // Generate depth value based on row and column. Maximum value is 16m. |
| 259 float depth = |
| 260 16.0 / (kWidth + kHeight + 4 * kMargin) * (j + kMargin + i + kMargin); |
| 261 ASSERT_TRUE(static_cast<uint16_t>(depth * 4096) == |
| 262 last_data_[j + i * kWidth]); |
| 263 EXPECT_EQ(static_cast<uint16_t>(depth * 4096), last_data_[j + i * kWidth]) |
| 264 << "x:" << j << " ,y:" << i; |
| 265 } |
| 266 } |
| 267 tango_device->StopAndDeAllocate(); |
| 268 } |
| 269 |
| 270 TEST_F(VideoCaptureDeviceTangoAndroidTest, TangoApiNotFound) { |
| 271 std::unique_ptr<TangoApi> api = CreateMockTangoApi(); |
| 272 JNIEnv* env = base::android::AttachCurrentThread(); |
| 273 EXPECT_EQ(TangoApi::TANGO_METHOD_NOT_FOUND, |
| 274 api->TangoService_setBinder(env, nullptr)); |
| 275 EXPECT_EQ(TangoApi::TANGO_METHOD_NOT_FOUND, |
| 276 api->TangoService_connect(nullptr, nullptr)); |
| 277 EXPECT_EQ(TangoApi::TANGO_METHOD_NOT_FOUND, |
| 278 api->TangoConfig_setBool(nullptr, "config_enable_depth", true)); |
| 279 EXPECT_EQ(TangoApi::TANGO_METHOD_NOT_FOUND, |
| 280 api->TangoConfig_setInt32(nullptr, "config_depth_mode", 0)); |
| 281 EXPECT_EQ(TangoApi::TANGO_METHOD_NOT_FOUND, |
| 282 api->TangoService_connectOnPointCloudAvailable( |
| 283 OnPointCloudAvailable, this)); |
| 284 Intrinsics intrinsics; |
| 285 EXPECT_EQ(TangoApi::TANGO_METHOD_NOT_FOUND, |
| 286 api->TangoService_getCameraIntrinsics(TangoApi::TANGO_CAMERA_COLOR, |
| 287 &intrinsics)); |
| 288 ASSERT_FALSE(api->TangoService_getConfig(TangoApi::TANGO_CONFIG_DEFAULT)); |
| 289 api->TangoService_disconnect(); |
| 290 } |
| 291 |
| 292 TEST_F(VideoCaptureDeviceTangoAndroidTest, TangoApi) { |
| 293 std::unique_ptr<TangoApi> api = TangoApi::loadAndCreate(); |
| 294 ASSERT_TRUE(api || !IsTangoDevice()); |
| 295 if (!IsTangoDevice()) |
| 296 return; // Skip if not a Tango device. |
| 297 |
| 298 // Previous versions of Tango allow calls to some of the methods even without |
| 299 // connecting to service. We want to verify that the methods are resolved and |
| 300 // checking that return error is not TANGO_METHOD_NOT_FOUND is sufficient. |
| 301 JNIEnv* env = base::android::AttachCurrentThread(); |
| 302 EXPECT_EQ(TangoApi::TANGO_ERROR, api->TangoService_setBinder(env, nullptr)); |
| 303 EXPECT_NE(TangoApi::TANGO_METHOD_NOT_FOUND, |
| 304 api->TangoConfig_setBool(nullptr, "config_enable_depth", true)); |
| 305 EXPECT_NE(TangoApi::TANGO_METHOD_NOT_FOUND, |
| 306 api->TangoConfig_setInt32(nullptr, "config_depth_mode", 0)); |
| 307 EXPECT_NE(TangoApi::TANGO_METHOD_NOT_FOUND, |
| 308 api->TangoService_connectOnPointCloudAvailable( |
| 309 OnPointCloudAvailable, this)); |
| 310 Intrinsics intrinsics; |
| 311 EXPECT_NE(TangoApi::TANGO_METHOD_NOT_FOUND, |
| 312 api->TangoService_getCameraIntrinsics(TangoApi::TANGO_CAMERA_COLOR, |
| 313 &intrinsics)); |
| 314 api->TangoService_disconnect(); |
| 315 } |
| 316 |
| 317 } // namespace media |
| OLD | NEW |