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