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