| 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 "media/capture/video/android/video_capture_device_tango_android.h" |
| 6 |
| 7 #include <stdint.h> |
| 8 #include <utility> |
| 9 |
| 10 #include "base/android/jni_android.h" |
| 11 #include "base/android/jni_array.h" |
| 12 #include "base/android/jni_string.h" |
| 13 #include "base/numerics/safe_conversions.h" |
| 14 #include "base/strings/string_number_conversions.h" |
| 15 #include "base/strings/stringprintf.h" |
| 16 #include "base/sys_info.h" |
| 17 #include "base/threading/thread_task_runner_handle.h" |
| 18 #include "jni/VideoCaptureTango_jni.h" |
| 19 |
| 20 using base::android::AttachCurrentThread; |
| 21 using Intrinsics = media::TangoApi::TangoCameraIntrinsics; |
| 22 |
| 23 namespace media { |
| 24 |
| 25 namespace { |
| 26 |
| 27 // Depth frame size is selected as 214 x 120. This has the same aspect ratio as |
| 28 // color camera frame - since point cloud is in color camera 3D space |
| 29 // coordinates, we want to project point cloud to down-scaled color (1920x1080) |
| 30 // that fits the depth resolution but keeping the camera frame aspect ratio, |
| 31 // that is 214 x 120. For 3d point cloud produced by depth camera of e.g. |
| 32 // 224x172 (Lenovo Phab2 Pro) or larger size, we produce continuous depth frame |
| 33 // of size (214 x 120) synchronized to color camera frame with easy mapping |
| 34 // color to depth pixel - color frame coordinates divided by scale gives the |
| 35 // position in the depth buffer. |
| 36 const gfx::Size DepthSize(214, 120); |
| 37 |
| 38 void OnPointCloudCallback(void* context, |
| 39 const TangoApi::TangoPointCloud* point_cloud) { |
| 40 VideoCaptureDeviceTangoAndroid* p_this = |
| 41 reinterpret_cast<VideoCaptureDeviceTangoAndroid*>(context); |
| 42 p_this->OnPointCloudAvailable(point_cloud->num_points, |
| 43 reinterpret_cast<float*>(point_cloud->points), |
| 44 point_cloud->timestamp); |
| 45 } |
| 46 |
| 47 } // namespace |
| 48 |
| 49 VideoCaptureDeviceTangoAndroid::VideoCaptureDeviceTangoAndroid( |
| 50 const VideoCaptureDeviceDescriptor& device_descriptor) |
| 51 : VideoCaptureDeviceAndroid(device_descriptor), weak_ptr_factory_(this) { |
| 52 j_capture_.Reset(Java_VideoCaptureTango_create( |
| 53 AttachCurrentThread(), reinterpret_cast<intptr_t>(this))); |
| 54 } |
| 55 |
| 56 VideoCaptureDeviceTangoAndroid::~VideoCaptureDeviceTangoAndroid() { |
| 57 StopAndDeAllocate(); |
| 58 } |
| 59 |
| 60 // static |
| 61 void VideoCaptureDeviceTangoAndroid::EnumerateDevices( |
| 62 VideoCaptureDeviceDescriptors* device_descriptors) { |
| 63 // Tango devices have additional depth, color and fisheye cameras. Those |
| 64 // devices are identified by model. |
| 65 static const char* tango_models[] = {"Project Tango Tablet Development Kit", |
| 66 "Lenovo PB2-690M", "ASUS_A002", |
| 67 "ASUS_A002A"}; |
| 68 const std::string model = base::SysInfo::HardwareModelName(); |
| 69 const char** result = |
| 70 std::find(tango_models, tango_models + arraysize(tango_models), model); |
| 71 if (result == tango_models + arraysize(tango_models)) |
| 72 return; |
| 73 device_descriptors->emplace_back( |
| 74 "Tango depth", base::StringPrintf("%d", device_descriptors->size()), |
| 75 VideoCaptureApi::ANDROID_TANGO); |
| 76 } |
| 77 |
| 78 // static |
| 79 void VideoCaptureDeviceTangoAndroid::EnumerateDeviceCapabilities( |
| 80 const VideoCaptureDeviceDescriptor& descriptor, |
| 81 VideoCaptureFormats* supported_formats) { |
| 82 DCHECK(descriptor.capture_api == VideoCaptureApi::ANDROID_TANGO); |
| 83 supported_formats->emplace_back(DepthSize, 5, |
| 84 VideoPixelFormat::PIXEL_FORMAT_Y16); |
| 85 } |
| 86 |
| 87 // static |
| 88 bool VideoCaptureDeviceTangoAndroid::RegisterVideoCaptureDevice(JNIEnv* env) { |
| 89 return RegisterNativesImpl(env); |
| 90 } |
| 91 |
| 92 void VideoCaptureDeviceTangoAndroid::AllocateAndStart( |
| 93 const VideoCaptureParams& params, |
| 94 std::unique_ptr<Client> client) { |
| 95 { |
| 96 base::AutoLock lock(lock_); |
| 97 if (state_ != kIdle) |
| 98 return; |
| 99 client_ = std::move(client); |
| 100 got_first_frame_ = false; |
| 101 } |
| 102 |
| 103 JNIEnv* env = AttachCurrentThread(); |
| 104 |
| 105 capture_format_.frame_size = DepthSize; |
| 106 capture_format_.frame_rate = 5; |
| 107 capture_format_.pixel_format = VideoPixelFormat::PIXEL_FORMAT_Y16; |
| 108 |
| 109 if (capture_format_.frame_rate > 0) { |
| 110 frame_interval_ = base::TimeDelta::FromMicroseconds( |
| 111 (base::Time::kMicrosecondsPerSecond + capture_format_.frame_rate - 1) / |
| 112 capture_format_.frame_rate); |
| 113 } |
| 114 |
| 115 if (!Java_VideoCaptureTango_startCapture(env, j_capture_)) { |
| 116 SetErrorState(FROM_HERE, "failed to start capture"); |
| 117 return; |
| 118 } |
| 119 |
| 120 { |
| 121 base::AutoLock lock(lock_); |
| 122 state_ = kConfigured; |
| 123 } |
| 124 } |
| 125 |
| 126 void VideoCaptureDeviceTangoAndroid::StopAndDeAllocate() { |
| 127 { |
| 128 base::AutoLock lock(lock_); |
| 129 if (state_ != kConfigured && state_ != kError) |
| 130 return; |
| 131 } |
| 132 |
| 133 Java_VideoCaptureTango_stopCapture(AttachCurrentThread(), j_capture_); |
| 134 |
| 135 { |
| 136 base::AutoLock lock(lock_); |
| 137 state_ = kIdle; |
| 138 client_.reset(); |
| 139 } |
| 140 } |
| 141 |
| 142 void VideoCaptureDeviceTangoAndroid::OnPointCloudAvailable(uint32_t num_points, |
| 143 float* const src, |
| 144 double timestamp) { |
| 145 if (!IsClientConfiguredForIncomingData()) |
| 146 return; |
| 147 const base::TimeTicks current_time = base::TimeTicks::Now(); |
| 148 ProcessFirstFrameAvailable(current_time); |
| 149 |
| 150 CHECK(src); |
| 151 const int width = capture_format_.frame_size.width(); |
| 152 const int height = capture_format_.frame_size.height(); |
| 153 |
| 154 const int buffer_length = width * height * 2; |
| 155 std::unique_ptr<uint8_t> buffer(new uint8_t[buffer_length]); |
| 156 |
| 157 uint16_t* out = reinterpret_cast<uint16_t*>(buffer.get()); |
| 158 memset(out, 0, buffer_length); |
| 159 |
| 160 const float fx = intrinsics_->fx; |
| 161 const float fy = intrinsics_->fy; |
| 162 // Add 0.5 to cx and cy to avoid the need for round() in operations bellow. |
| 163 float cx = intrinsics_->cx + 0.5; |
| 164 float cy = intrinsics_->cy + 0.5; |
| 165 |
| 166 int current_x = INT_MIN; |
| 167 int current_y = INT_MIN; |
| 168 |
| 169 // Optimization: process 4 points at once as they are likely adjacent. |
| 170 // TODO(aleksandar.stojiljkovic): Try optimizing using NEON. |
| 171 // https://crbug.com/674440 |
| 172 float* const src_end = src + num_points / 4 * 16; |
| 173 |
| 174 for (const float* item = src; item < src_end; item += 16) { |
| 175 #define x0 item[0] |
| 176 #define y0 item[1] |
| 177 #define z0 item[2] |
| 178 #define x1 item[4] |
| 179 #define y1 item[5] |
| 180 #define z1 item[6] |
| 181 #define x2 item[8] |
| 182 #define y2 item[9] |
| 183 #define z2 item[10] |
| 184 #define x3 item[12] |
| 185 #define y3 item[13] |
| 186 #define z3 item[14] |
| 187 |
| 188 const int row3 = static_cast<int>(fy * y3 / z3 + cy); |
| 189 |
| 190 if (row3 < 0 || row3 >= height) { |
| 191 // heuristics: due to radial distortion, early leave as the rest of data |
| 192 // is outside color image. Constant 5 is large enough to compensate the |
| 193 // radial distortion. |
| 194 if (row3 >= height + 5) |
| 195 break; |
| 196 continue; |
| 197 } |
| 198 |
| 199 const int column3 = static_cast<int>(fx * x3 / z3 + cx); |
| 200 |
| 201 // We know that the cameras are configured to support range < 7 meters but |
| 202 // allow values up to 16m here. Multiplying depth by 4096 gives values with |
| 203 // good exposure in 16-bit unsigned range. |
| 204 uint16_t depth[] = { |
| 205 static_cast<uint16_t>(z0 * 4096), static_cast<uint16_t>(z1 * 4096), |
| 206 static_cast<uint16_t>(z2 * 4096), static_cast<uint16_t>(z3 * 4096)}; |
| 207 DCHECK(z0 * 4096 <= 65535 && z1 * 4096 <= 65535 && z2 * 4096 <= 65535 && |
| 208 z3 * 4096 <= 65535); |
| 209 |
| 210 unsigned out_offset3 = row3 * width + column3; |
| 211 if (column3 == current_x + 4 && row3 == current_y) { |
| 212 // All values are packed. We only need to copy depth while there is no |
| 213 // need to calculate x and y. |
| 214 if (column3 - 3 < width) |
| 215 out[out_offset3 - 3] = depth[0]; |
| 216 if (column3 - 2 < width) |
| 217 out[out_offset3 - 2] = depth[1]; |
| 218 if (column3 - 1 < width) |
| 219 out[out_offset3 - 1] = depth[2]; |
| 220 if (column3 < width) |
| 221 out[out_offset3] = depth[3]; |
| 222 current_x += 4; |
| 223 continue; |
| 224 } |
| 225 |
| 226 int row1 = static_cast<int>(fy * y1 / z1 + cy); |
| 227 int column1 = static_cast<int>(fx * x1 / z1 + cx); |
| 228 |
| 229 unsigned out_offset1 = row1 * width + column1; |
| 230 if (column1 == current_x + 2 && row1 == current_y && column1 < width) { |
| 231 if (row1 >= 0 && row1 < height) { |
| 232 out[out_offset1 - 1] = depth[0]; |
| 233 out[out_offset1] = depth[1]; |
| 234 } |
| 235 } else if (column1 == current_x + 3 && row1 == current_y && |
| 236 column1 < width) { |
| 237 // This compensates for the radial distortion side effects (vertical lines |
| 238 // with missing values) near the vertical edges. |
| 239 if (row1 >= 0 && row1 < height) { |
| 240 out[out_offset1 - 2] = depth[0]; |
| 241 out[out_offset1 - 1] = depth[0]; |
| 242 out[out_offset1] = depth[1]; |
| 243 } |
| 244 } else { |
| 245 const int row0 = static_cast<int>(fy * y0 / z0 + cy); |
| 246 const int column0 = static_cast<int>(fx * x0 / z0 + cx); |
| 247 if (row0 >= 0 && row0 < height && column0 >= 0 && column0 < width) |
| 248 out[row0 * width + column0] = depth[0]; |
| 249 if (row1 >= 0 && row1 < height && column1 >= 0 && column1 < width) |
| 250 out[out_offset1] = depth[1]; |
| 251 } |
| 252 |
| 253 if (column3 == column1 + 2 && row3 == row1) { |
| 254 if (column3 - 1 >= 0 && column3 - 1 < width) |
| 255 out[out_offset3 - 1] = depth[2]; |
| 256 if (column3 >= 0 && column3 < width) |
| 257 out[out_offset3] = depth[3]; |
| 258 } else if (column3 == column1 + 3 && row3 == row1) { |
| 259 // This compensates for the radial distortion side effects (vertical lines |
| 260 // with missing values) near the vertical edges. |
| 261 if (column3 - 2 >= 0 && column3 - 2 < width) |
| 262 out[out_offset3 - 2] = depth[2]; |
| 263 if (column3 - 1 >= 0 && column3 - 1 < width) |
| 264 out[out_offset3 - 1] = depth[2]; |
| 265 if (column3 >= 0 && column3 < width) |
| 266 out[out_offset3] = depth[3]; |
| 267 } else { |
| 268 const int row2 = static_cast<int>(fy * y2 / z2 + cy); |
| 269 const int column2 = static_cast<int>(fx * x2 / z2 + cx); |
| 270 if (row2 >= 0 && row2 < height && column2 >= 0 && column2 < width) |
| 271 out[row2 * width + column2] = depth[2]; |
| 272 if (row3 >= 0 && column3 >= 0 && column3 < width) |
| 273 out[out_offset3] = depth[3]; |
| 274 } |
| 275 current_x = (column3 >= 0) ? column3 : INT_MIN; |
| 276 current_y = row3; |
| 277 } |
| 278 |
| 279 if (AdvanceToNextFrameTime(current_time)) { |
| 280 const base::TimeDelta capture_time = |
| 281 base::TimeDelta::FromSecondsD(timestamp); |
| 282 SendIncomingDataToClient(buffer.get(), buffer_length, 0, current_time, |
| 283 capture_time); |
| 284 } |
| 285 } |
| 286 |
| 287 void VideoCaptureDeviceTangoAndroid::OnTangoServiceConnected(JNIEnv* env, |
| 288 jobject obj, |
| 289 jobject binder) { |
| 290 if (!api_) { |
| 291 api_ = TangoApi::loadAndCreate(); |
| 292 if (!api_) |
| 293 return; |
| 294 } |
| 295 |
| 296 if (api_->TangoService_setBinder(env, binder) != TangoApi::TANGO_SUCCESS) { |
| 297 LOG(ERROR) << __func__ << " - TangoService_setBinder error."; |
| 298 return; |
| 299 } |
| 300 Connect(""); |
| 301 } |
| 302 |
| 303 void VideoCaptureDeviceTangoAndroid::Connect(const std::string& uuid) { |
| 304 TangoApi::TangoConfig config = |
| 305 api_->TangoService_getConfig(TangoApi::TANGO_CONFIG_DEFAULT); |
| 306 if (config == nullptr) { |
| 307 LOG(ERROR) << "TangoService_getConfig error."; |
| 308 return; |
| 309 } |
| 310 |
| 311 TangoApi::TangoErrorType result = |
| 312 api_->TangoConfig_setBool(config, "config_enable_depth", true); |
| 313 if (result != TangoApi::TANGO_SUCCESS) { |
| 314 LOG(ERROR) << "config_enable_depth activation error:" << result; |
| 315 return; |
| 316 } |
| 317 |
| 318 // Make sure TANGO_POINTCLOUD_XYZC (the default one) is set. |
| 319 if (api_->TangoConfig_setInt32(config, "config_depth_mode", 0) != |
| 320 TangoApi::TANGO_SUCCESS) { |
| 321 LOG(ERROR) << "TangoConfig_setInt32 config_depth_mode error."; |
| 322 return; |
| 323 } |
| 324 |
| 325 if ((result = api_->TangoService_connectOnPointCloudAvailable( |
| 326 OnPointCloudCallback, this)) != TangoApi::TANGO_SUCCESS) { |
| 327 LOG(ERROR) << "Failed to connect to point cloud callback with error code:" |
| 328 << result; |
| 329 return; |
| 330 } |
| 331 |
| 332 if ((result = api_->TangoService_connect(this, config)) != |
| 333 TangoApi::TANGO_SUCCESS) { |
| 334 LOG(ERROR) << "TangoService_connect error:" << result; |
| 335 return; |
| 336 } |
| 337 |
| 338 Intrinsics color_camera_intrinsics; |
| 339 if ((result = api_->TangoService_getCameraIntrinsics( |
| 340 TangoApi::TANGO_CAMERA_COLOR, &color_camera_intrinsics)) != |
| 341 TangoApi::TANGO_SUCCESS) { |
| 342 LOG(ERROR) << "Failed to get the intrinsics for the color camera:" |
| 343 << result; |
| 344 return; |
| 345 } |
| 346 |
| 347 // Point cloud is in color camera space. We are scaling required intrinsics |
| 348 // to depth camera resolution (214x120) for projecting 3D point cloud to 2D |
| 349 // depth frame. |
| 350 const float scale = ceil(static_cast<float>(color_camera_intrinsics.width) / |
| 351 DepthSize.width()); |
| 352 intrinsics_ = std::unique_ptr<Intrinsics>(new Intrinsics); |
| 353 intrinsics_->cx = color_camera_intrinsics.cx / scale; |
| 354 intrinsics_->cy = color_camera_intrinsics.cy / scale; |
| 355 intrinsics_->fx = color_camera_intrinsics.fx / scale; |
| 356 intrinsics_->fy = color_camera_intrinsics.fy / scale; |
| 357 |
| 358 { |
| 359 base::AutoLock lock(lock_); |
| 360 if (client_) |
| 361 client_->OnStarted(); |
| 362 } |
| 363 } |
| 364 |
| 365 void VideoCaptureDeviceTangoAndroid::TangoDisconnect(JNIEnv* env, jobject obj) { |
| 366 if (api_) |
| 367 api_->TangoService_disconnect(); |
| 368 } |
| 369 |
| 370 } // namespace media |
| OLD | NEW |