Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(240)

Side by Side Diff: media/capture/video/android/video_capture_device_tango_android.cc

Issue 2983473002: Android Tango depth camera capture support.
Patch Set: rename tango_api files to tango_client_api_glue Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698