OLD | NEW |
| (Empty) |
1 // Copyright 2014 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 <errno.h> | |
6 #include <fcntl.h> | |
7 #include <linux/videodev2.h> | |
8 #include <poll.h> | |
9 #include <string.h> | |
10 #include <sys/eventfd.h> | |
11 #include <sys/ioctl.h> | |
12 #include <sys/mman.h> | |
13 | |
14 #include "base/bind.h" | |
15 #include "base/bind_helpers.h" | |
16 #include "base/callback.h" | |
17 #include "base/numerics/safe_conversions.h" | |
18 #include "content/common/gpu/media/v4l2_image_processor.h" | |
19 #include "media/base/bind_to_current_loop.h" | |
20 | |
21 #define IOCTL_OR_ERROR_RETURN_VALUE(type, arg, value, type_str) \ | |
22 do { \ | |
23 if (device_->Ioctl(type, arg) != 0) { \ | |
24 PLOG(ERROR) << __func__ << "(): ioctl() failed: " << type_str; \ | |
25 return value; \ | |
26 } \ | |
27 } while (0) | |
28 | |
29 #define IOCTL_OR_ERROR_RETURN(type, arg) \ | |
30 IOCTL_OR_ERROR_RETURN_VALUE(type, arg, ((void)0), #type) | |
31 | |
32 #define IOCTL_OR_ERROR_RETURN_FALSE(type, arg) \ | |
33 IOCTL_OR_ERROR_RETURN_VALUE(type, arg, false, #type) | |
34 | |
35 #define IOCTL_OR_LOG_ERROR(type, arg) \ | |
36 do { \ | |
37 if (device_->Ioctl(type, arg) != 0) \ | |
38 PLOG(ERROR) << __func__ << "(): ioctl() failed: " << #type; \ | |
39 } while (0) | |
40 | |
41 namespace content { | |
42 | |
43 V4L2ImageProcessor::InputRecord::InputRecord() : at_device(false) { | |
44 } | |
45 | |
46 V4L2ImageProcessor::InputRecord::~InputRecord() { | |
47 } | |
48 | |
49 V4L2ImageProcessor::OutputRecord::OutputRecord() : at_device(false) {} | |
50 | |
51 V4L2ImageProcessor::OutputRecord::~OutputRecord() { | |
52 } | |
53 | |
54 V4L2ImageProcessor::JobRecord::JobRecord() : output_buffer_index(-1) {} | |
55 | |
56 V4L2ImageProcessor::JobRecord::~JobRecord() { | |
57 } | |
58 | |
59 V4L2ImageProcessor::V4L2ImageProcessor(const scoped_refptr<V4L2Device>& device) | |
60 : input_format_(media::PIXEL_FORMAT_UNKNOWN), | |
61 output_format_(media::PIXEL_FORMAT_UNKNOWN), | |
62 input_format_fourcc_(0), | |
63 output_format_fourcc_(0), | |
64 input_planes_count_(0), | |
65 output_planes_count_(0), | |
66 child_task_runner_(base::ThreadTaskRunnerHandle::Get()), | |
67 device_(device), | |
68 device_thread_("V4L2ImageProcessorThread"), | |
69 device_poll_thread_("V4L2ImageProcessorDevicePollThread"), | |
70 input_streamon_(false), | |
71 input_buffer_queued_count_(0), | |
72 output_streamon_(false), | |
73 output_buffer_queued_count_(0), | |
74 num_buffers_(0), | |
75 weak_this_factory_(this) { | |
76 weak_this_ = weak_this_factory_.GetWeakPtr(); | |
77 } | |
78 | |
79 V4L2ImageProcessor::~V4L2ImageProcessor() { | |
80 DCHECK(child_task_runner_->BelongsToCurrentThread()); | |
81 DCHECK(!device_thread_.IsRunning()); | |
82 DCHECK(!device_poll_thread_.IsRunning()); | |
83 | |
84 DestroyInputBuffers(); | |
85 DestroyOutputBuffers(); | |
86 } | |
87 | |
88 void V4L2ImageProcessor::NotifyError() { | |
89 LOG(ERROR) << __func__; | |
90 DCHECK(!child_task_runner_->BelongsToCurrentThread()); | |
91 child_task_runner_->PostTask( | |
92 FROM_HERE, base::Bind(&V4L2ImageProcessor::NotifyErrorOnChildThread, | |
93 weak_this_, error_cb_)); | |
94 } | |
95 | |
96 void V4L2ImageProcessor::NotifyErrorOnChildThread( | |
97 const base::Closure& error_cb) { | |
98 DCHECK(child_task_runner_->BelongsToCurrentThread()); | |
99 error_cb_.Run(); | |
100 } | |
101 | |
102 bool V4L2ImageProcessor::Initialize(media::VideoPixelFormat input_format, | |
103 media::VideoPixelFormat output_format, | |
104 gfx::Size input_visible_size, | |
105 gfx::Size output_visible_size, | |
106 gfx::Size output_allocated_size, | |
107 int num_buffers, | |
108 const base::Closure& error_cb) { | |
109 DCHECK(!error_cb.is_null()); | |
110 error_cb_ = error_cb; | |
111 | |
112 // TODO(posciak): Replace Exynos-specific format/parameter hardcoding in this | |
113 // class with proper capability enumeration. | |
114 DCHECK_EQ(input_format, media::PIXEL_FORMAT_I420); | |
115 DCHECK_EQ(output_format, media::PIXEL_FORMAT_NV12); | |
116 DCHECK_GT(num_buffers, 0); | |
117 | |
118 input_format_ = input_format; | |
119 output_format_ = output_format; | |
120 input_format_fourcc_ = V4L2Device::VideoPixelFormatToV4L2PixFmt(input_format); | |
121 output_format_fourcc_ = | |
122 V4L2Device::VideoPixelFormatToV4L2PixFmt(output_format); | |
123 num_buffers_ = num_buffers; | |
124 | |
125 if (!input_format_fourcc_ || !output_format_fourcc_) { | |
126 LOG(ERROR) << "Unrecognized format(s)"; | |
127 return false; | |
128 } | |
129 | |
130 input_visible_size_ = input_visible_size; | |
131 output_visible_size_ = output_visible_size; | |
132 output_allocated_size_ = output_allocated_size; | |
133 | |
134 input_planes_count_ = media::VideoFrame::NumPlanes(input_format); | |
135 DCHECK_LE(input_planes_count_, static_cast<size_t>(VIDEO_MAX_PLANES)); | |
136 output_planes_count_ = media::VideoFrame::NumPlanes(output_format); | |
137 DCHECK_LE(output_planes_count_, static_cast<size_t>(VIDEO_MAX_PLANES)); | |
138 | |
139 // Capabilities check. | |
140 struct v4l2_capability caps; | |
141 memset(&caps, 0, sizeof(caps)); | |
142 const __u32 kCapsRequired = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; | |
143 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QUERYCAP, &caps); | |
144 if ((caps.capabilities & kCapsRequired) != kCapsRequired) { | |
145 LOG(ERROR) << "Initialize(): ioctl() failed: VIDIOC_QUERYCAP: " | |
146 "caps check failed: 0x" << std::hex << caps.capabilities; | |
147 return false; | |
148 } | |
149 | |
150 if (!CreateInputBuffers() || !CreateOutputBuffers()) | |
151 return false; | |
152 | |
153 if (!device_thread_.Start()) { | |
154 LOG(ERROR) << "Initialize(): encoder thread failed to start"; | |
155 return false; | |
156 } | |
157 | |
158 // StartDevicePoll will NotifyError on failure, so IgnoreResult is fine here. | |
159 device_thread_.message_loop()->PostTask( | |
160 FROM_HERE, | |
161 base::Bind(base::IgnoreResult(&V4L2ImageProcessor::StartDevicePoll), | |
162 base::Unretained(this))); | |
163 | |
164 DVLOG(1) << "V4L2ImageProcessor initialized for " | |
165 << " input_format:" << media::VideoPixelFormatToString(input_format) | |
166 << ", output_format:" | |
167 << media::VideoPixelFormatToString(output_format) | |
168 << ", input_visible_size: " << input_visible_size.ToString() | |
169 << ", input_allocated_size: " << input_allocated_size_.ToString() | |
170 << ", output_visible_size: " << output_visible_size.ToString() | |
171 << ", output_allocated_size: " << output_allocated_size_.ToString(); | |
172 | |
173 return true; | |
174 } | |
175 | |
176 std::vector<base::ScopedFD> V4L2ImageProcessor::GetDmabufsForOutputBuffer( | |
177 int output_buffer_index) { | |
178 DCHECK_GE(output_buffer_index, 0); | |
179 DCHECK_LT(output_buffer_index, num_buffers_); | |
180 return device_->GetDmabufsForV4L2Buffer(output_buffer_index, | |
181 output_planes_count_, | |
182 V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); | |
183 } | |
184 | |
185 void V4L2ImageProcessor::Process(const scoped_refptr<media::VideoFrame>& frame, | |
186 int output_buffer_index, | |
187 const FrameReadyCB& cb) { | |
188 DVLOG(3) << __func__ << ": ts=" << frame->timestamp().InMilliseconds(); | |
189 | |
190 std::unique_ptr<JobRecord> job_record(new JobRecord()); | |
191 job_record->frame = frame; | |
192 job_record->output_buffer_index = output_buffer_index; | |
193 job_record->ready_cb = cb; | |
194 | |
195 device_thread_.message_loop()->PostTask( | |
196 FROM_HERE, | |
197 base::Bind(&V4L2ImageProcessor::ProcessTask, | |
198 base::Unretained(this), | |
199 base::Passed(&job_record))); | |
200 } | |
201 | |
202 void V4L2ImageProcessor::ProcessTask(std::unique_ptr<JobRecord> job_record) { | |
203 int index = job_record->output_buffer_index; | |
204 DVLOG(3) << __func__ << ": Reusing output buffer, index=" << index; | |
205 DCHECK_EQ(device_thread_.message_loop(), base::MessageLoop::current()); | |
206 | |
207 EnqueueOutput(index); | |
208 input_queue_.push(make_linked_ptr(job_record.release())); | |
209 EnqueueInput(); | |
210 } | |
211 | |
212 void V4L2ImageProcessor::Destroy() { | |
213 DVLOG(3) << __func__; | |
214 DCHECK(child_task_runner_->BelongsToCurrentThread()); | |
215 | |
216 weak_this_factory_.InvalidateWeakPtrs(); | |
217 | |
218 // If the device thread is running, destroy using posted task. | |
219 if (device_thread_.IsRunning()) { | |
220 device_thread_.message_loop()->PostTask( | |
221 FROM_HERE, | |
222 base::Bind(&V4L2ImageProcessor::DestroyTask, base::Unretained(this))); | |
223 // Wait for tasks to finish/early-exit. | |
224 device_thread_.Stop(); | |
225 } else { | |
226 // Otherwise DestroyTask() is not needed. | |
227 DCHECK(!device_poll_thread_.IsRunning()); | |
228 } | |
229 | |
230 delete this; | |
231 } | |
232 | |
233 void V4L2ImageProcessor::DestroyTask() { | |
234 DCHECK_EQ(device_thread_.message_loop(), base::MessageLoop::current()); | |
235 | |
236 // Stop streaming and the device_poll_thread_. | |
237 StopDevicePoll(); | |
238 } | |
239 | |
240 bool V4L2ImageProcessor::CreateInputBuffers() { | |
241 DVLOG(3) << __func__; | |
242 DCHECK(child_task_runner_->BelongsToCurrentThread()); | |
243 DCHECK(!input_streamon_); | |
244 | |
245 struct v4l2_control control; | |
246 memset(&control, 0, sizeof(control)); | |
247 control.id = V4L2_CID_ROTATE; | |
248 control.value = 0; | |
249 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_CTRL, &control); | |
250 | |
251 memset(&control, 0, sizeof(control)); | |
252 control.id = V4L2_CID_HFLIP; | |
253 control.value = 0; | |
254 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_CTRL, &control); | |
255 | |
256 memset(&control, 0, sizeof(control)); | |
257 control.id = V4L2_CID_VFLIP; | |
258 control.value = 0; | |
259 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_CTRL, &control); | |
260 | |
261 memset(&control, 0, sizeof(control)); | |
262 control.id = V4L2_CID_ALPHA_COMPONENT; | |
263 control.value = 255; | |
264 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_CTRL, &control); | |
265 | |
266 struct v4l2_format format; | |
267 memset(&format, 0, sizeof(format)); | |
268 format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; | |
269 format.fmt.pix_mp.width = input_visible_size_.width(); | |
270 format.fmt.pix_mp.height = input_visible_size_.height(); | |
271 format.fmt.pix_mp.pixelformat = input_format_fourcc_; | |
272 format.fmt.pix_mp.num_planes = input_planes_count_; | |
273 for (size_t i = 0; i < input_planes_count_; ++i) { | |
274 format.fmt.pix_mp.plane_fmt[i].sizeimage = | |
275 media::VideoFrame::PlaneSize(input_format_, i, input_allocated_size_) | |
276 .GetArea(); | |
277 format.fmt.pix_mp.plane_fmt[i].bytesperline = | |
278 base::checked_cast<__u32>(input_allocated_size_.width()); | |
279 } | |
280 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_FMT, &format); | |
281 | |
282 input_allocated_size_ = V4L2Device::CodedSizeFromV4L2Format(format); | |
283 DCHECK(gfx::Rect(input_allocated_size_).Contains( | |
284 gfx::Rect(input_visible_size_))); | |
285 | |
286 struct v4l2_crop crop; | |
287 memset(&crop, 0, sizeof(crop)); | |
288 crop.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; | |
289 crop.c.left = 0; | |
290 crop.c.top = 0; | |
291 crop.c.width = base::checked_cast<__u32>(input_visible_size_.width()); | |
292 crop.c.height = base::checked_cast<__u32>(input_visible_size_.height()); | |
293 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_CROP, &crop); | |
294 | |
295 struct v4l2_requestbuffers reqbufs; | |
296 memset(&reqbufs, 0, sizeof(reqbufs)); | |
297 reqbufs.count = num_buffers_; | |
298 reqbufs.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; | |
299 reqbufs.memory = V4L2_MEMORY_USERPTR; | |
300 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_REQBUFS, &reqbufs); | |
301 if (reqbufs.count != num_buffers_) { | |
302 LOG(ERROR) << "Failed to allocate input buffers. reqbufs.count=" | |
303 << reqbufs.count << ", num_buffers=" << num_buffers_; | |
304 return false; | |
305 } | |
306 | |
307 DCHECK(input_buffer_map_.empty()); | |
308 input_buffer_map_.resize(reqbufs.count); | |
309 | |
310 for (size_t i = 0; i < input_buffer_map_.size(); ++i) | |
311 free_input_buffers_.push_back(i); | |
312 | |
313 return true; | |
314 } | |
315 | |
316 bool V4L2ImageProcessor::CreateOutputBuffers() { | |
317 DVLOG(3) << __func__; | |
318 DCHECK(child_task_runner_->BelongsToCurrentThread()); | |
319 DCHECK(!output_streamon_); | |
320 | |
321 struct v4l2_format format; | |
322 memset(&format, 0, sizeof(format)); | |
323 format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; | |
324 format.fmt.pix_mp.width = output_allocated_size_.width(); | |
325 format.fmt.pix_mp.height = output_allocated_size_.height(); | |
326 format.fmt.pix_mp.pixelformat = output_format_fourcc_; | |
327 format.fmt.pix_mp.num_planes = output_planes_count_; | |
328 for (size_t i = 0; i < output_planes_count_; ++i) { | |
329 format.fmt.pix_mp.plane_fmt[i].sizeimage = | |
330 media::VideoFrame::PlaneSize(output_format_, i, output_allocated_size_) | |
331 .GetArea(); | |
332 format.fmt.pix_mp.plane_fmt[i].bytesperline = | |
333 base::checked_cast<__u32>(output_allocated_size_.width()); | |
334 } | |
335 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_FMT, &format); | |
336 | |
337 gfx::Size adjusted_allocated_size = | |
338 V4L2Device::CodedSizeFromV4L2Format(format); | |
339 DCHECK(gfx::Rect(adjusted_allocated_size).Contains( | |
340 gfx::Rect(output_allocated_size_))); | |
341 output_allocated_size_ = adjusted_allocated_size; | |
342 | |
343 struct v4l2_crop crop; | |
344 memset(&crop, 0, sizeof(crop)); | |
345 crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; | |
346 crop.c.left = 0; | |
347 crop.c.top = 0; | |
348 crop.c.width = base::checked_cast<__u32>(output_visible_size_.width()); | |
349 crop.c.height = base::checked_cast<__u32>(output_visible_size_.height()); | |
350 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_CROP, &crop); | |
351 | |
352 struct v4l2_requestbuffers reqbufs; | |
353 memset(&reqbufs, 0, sizeof(reqbufs)); | |
354 reqbufs.count = num_buffers_; | |
355 reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; | |
356 reqbufs.memory = V4L2_MEMORY_MMAP; | |
357 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_REQBUFS, &reqbufs); | |
358 if (reqbufs.count != num_buffers_) { | |
359 LOG(ERROR) << "Failed to allocate output buffers. reqbufs.count=" | |
360 << reqbufs.count << ", num_buffers=" << num_buffers_; | |
361 return false; | |
362 } | |
363 | |
364 DCHECK(output_buffer_map_.empty()); | |
365 output_buffer_map_.resize(reqbufs.count); | |
366 | |
367 return true; | |
368 } | |
369 | |
370 void V4L2ImageProcessor::DestroyInputBuffers() { | |
371 DCHECK(child_task_runner_->BelongsToCurrentThread()); | |
372 DCHECK(!input_streamon_); | |
373 | |
374 struct v4l2_requestbuffers reqbufs; | |
375 memset(&reqbufs, 0, sizeof(reqbufs)); | |
376 reqbufs.count = 0; | |
377 reqbufs.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; | |
378 reqbufs.memory = V4L2_MEMORY_USERPTR; | |
379 IOCTL_OR_LOG_ERROR(VIDIOC_REQBUFS, &reqbufs); | |
380 | |
381 input_buffer_map_.clear(); | |
382 free_input_buffers_.clear(); | |
383 } | |
384 | |
385 void V4L2ImageProcessor::DestroyOutputBuffers() { | |
386 DCHECK(child_task_runner_->BelongsToCurrentThread()); | |
387 DCHECK(!output_streamon_); | |
388 | |
389 struct v4l2_requestbuffers reqbufs; | |
390 memset(&reqbufs, 0, sizeof(reqbufs)); | |
391 reqbufs.count = 0; | |
392 reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; | |
393 reqbufs.memory = V4L2_MEMORY_MMAP; | |
394 IOCTL_OR_LOG_ERROR(VIDIOC_REQBUFS, &reqbufs); | |
395 | |
396 output_buffer_map_.clear(); | |
397 } | |
398 | |
399 void V4L2ImageProcessor::DevicePollTask(bool poll_device) { | |
400 DCHECK_EQ(device_poll_thread_.message_loop(), base::MessageLoop::current()); | |
401 | |
402 bool event_pending; | |
403 if (!device_->Poll(poll_device, &event_pending)) { | |
404 NotifyError(); | |
405 return; | |
406 } | |
407 | |
408 // All processing should happen on ServiceDeviceTask(), since we shouldn't | |
409 // touch encoder state from this thread. | |
410 device_thread_.message_loop()->PostTask( | |
411 FROM_HERE, | |
412 base::Bind(&V4L2ImageProcessor::ServiceDeviceTask, | |
413 base::Unretained(this))); | |
414 } | |
415 | |
416 void V4L2ImageProcessor::ServiceDeviceTask() { | |
417 DCHECK_EQ(device_thread_.message_loop(), base::MessageLoop::current()); | |
418 // ServiceDeviceTask() should only ever be scheduled from DevicePollTask(), | |
419 // so either: | |
420 // * device_poll_thread_ is running normally | |
421 // * device_poll_thread_ scheduled us, but then a DestroyTask() shut it down, | |
422 // in which case we should early-out. | |
423 if (!device_poll_thread_.message_loop()) | |
424 return; | |
425 | |
426 Dequeue(); | |
427 EnqueueInput(); | |
428 | |
429 if (!device_->ClearDevicePollInterrupt()) | |
430 return; | |
431 | |
432 bool poll_device = | |
433 (input_buffer_queued_count_ > 0 && output_buffer_queued_count_ > 0); | |
434 | |
435 device_poll_thread_.message_loop()->PostTask( | |
436 FROM_HERE, | |
437 base::Bind(&V4L2ImageProcessor::DevicePollTask, | |
438 base::Unretained(this), | |
439 poll_device)); | |
440 | |
441 DVLOG(2) << __func__ << ": buffer counts: INPUT[" << input_queue_.size() | |
442 << "] => DEVICE[" << free_input_buffers_.size() << "+" | |
443 << input_buffer_queued_count_ << "/" << input_buffer_map_.size() | |
444 << "->" << output_buffer_map_.size() - output_buffer_queued_count_ | |
445 << "+" << output_buffer_queued_count_ << "/" | |
446 << output_buffer_map_.size() << "]"; | |
447 } | |
448 | |
449 void V4L2ImageProcessor::EnqueueInput() { | |
450 DCHECK_EQ(device_thread_.message_loop(), base::MessageLoop::current()); | |
451 | |
452 const int old_inputs_queued = input_buffer_queued_count_; | |
453 while (!input_queue_.empty() && !free_input_buffers_.empty()) { | |
454 if (!EnqueueInputRecord()) | |
455 return; | |
456 } | |
457 if (old_inputs_queued == 0 && input_buffer_queued_count_ != 0) { | |
458 // We started up a previously empty queue. | |
459 // Queue state changed; signal interrupt. | |
460 if (!device_->SetDevicePollInterrupt()) | |
461 return; | |
462 // VIDIOC_STREAMON if we haven't yet. | |
463 if (!input_streamon_) { | |
464 __u32 type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; | |
465 IOCTL_OR_ERROR_RETURN(VIDIOC_STREAMON, &type); | |
466 input_streamon_ = true; | |
467 } | |
468 } | |
469 } | |
470 | |
471 void V4L2ImageProcessor::EnqueueOutput(int index) { | |
472 DCHECK_EQ(device_thread_.message_loop(), base::MessageLoop::current()); | |
473 | |
474 const int old_outputs_queued = output_buffer_queued_count_; | |
475 if (!EnqueueOutputRecord(index)) | |
476 return; | |
477 | |
478 if (old_outputs_queued == 0 && output_buffer_queued_count_ != 0) { | |
479 // We just started up a previously empty queue. | |
480 // Queue state changed; signal interrupt. | |
481 if (!device_->SetDevicePollInterrupt()) | |
482 return; | |
483 // Start VIDIOC_STREAMON if we haven't yet. | |
484 if (!output_streamon_) { | |
485 __u32 type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; | |
486 IOCTL_OR_ERROR_RETURN(VIDIOC_STREAMON, &type); | |
487 output_streamon_ = true; | |
488 } | |
489 } | |
490 } | |
491 | |
492 void V4L2ImageProcessor::Dequeue() { | |
493 DCHECK_EQ(device_thread_.message_loop(), base::MessageLoop::current()); | |
494 | |
495 // Dequeue completed input (VIDEO_OUTPUT) buffers, | |
496 // and recycle to the free list. | |
497 struct v4l2_buffer dqbuf; | |
498 struct v4l2_plane planes[VIDEO_MAX_PLANES]; | |
499 while (input_buffer_queued_count_ > 0) { | |
500 DCHECK(input_streamon_); | |
501 memset(&dqbuf, 0, sizeof(dqbuf)); | |
502 memset(&planes, 0, sizeof(planes)); | |
503 dqbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; | |
504 dqbuf.memory = V4L2_MEMORY_USERPTR; | |
505 dqbuf.m.planes = planes; | |
506 dqbuf.length = input_planes_count_; | |
507 if (device_->Ioctl(VIDIOC_DQBUF, &dqbuf) != 0) { | |
508 if (errno == EAGAIN) { | |
509 // EAGAIN if we're just out of buffers to dequeue. | |
510 break; | |
511 } | |
512 PLOG(ERROR) << "ioctl() failed: VIDIOC_DQBUF"; | |
513 NotifyError(); | |
514 return; | |
515 } | |
516 InputRecord& input_record = input_buffer_map_[dqbuf.index]; | |
517 DCHECK(input_record.at_device); | |
518 input_record.at_device = false; | |
519 input_record.frame = NULL; | |
520 free_input_buffers_.push_back(dqbuf.index); | |
521 input_buffer_queued_count_--; | |
522 } | |
523 | |
524 // Dequeue completed output (VIDEO_CAPTURE) buffers, recycle to the free list. | |
525 // Return the finished buffer to the client via the job ready callback. | |
526 while (output_buffer_queued_count_ > 0) { | |
527 DCHECK(output_streamon_); | |
528 memset(&dqbuf, 0, sizeof(dqbuf)); | |
529 memset(&planes, 0, sizeof(planes)); | |
530 dqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; | |
531 dqbuf.memory = V4L2_MEMORY_MMAP; | |
532 dqbuf.m.planes = planes; | |
533 dqbuf.length = output_planes_count_; | |
534 if (device_->Ioctl(VIDIOC_DQBUF, &dqbuf) != 0) { | |
535 if (errno == EAGAIN) { | |
536 // EAGAIN if we're just out of buffers to dequeue. | |
537 break; | |
538 } | |
539 PLOG(ERROR) << "ioctl() failed: VIDIOC_DQBUF"; | |
540 NotifyError(); | |
541 return; | |
542 } | |
543 OutputRecord& output_record = output_buffer_map_[dqbuf.index]; | |
544 DCHECK(output_record.at_device); | |
545 output_record.at_device = false; | |
546 output_buffer_queued_count_--; | |
547 | |
548 // Jobs are always processed in FIFO order. | |
549 DCHECK(!running_jobs_.empty()); | |
550 linked_ptr<JobRecord> job_record = running_jobs_.front(); | |
551 running_jobs_.pop(); | |
552 | |
553 DVLOG(3) << "Processing finished, returning frame, index=" << dqbuf.index; | |
554 | |
555 child_task_runner_->PostTask( | |
556 FROM_HERE, base::Bind(&V4L2ImageProcessor::FrameReady, weak_this_, | |
557 job_record->ready_cb, dqbuf.index)); | |
558 } | |
559 } | |
560 | |
561 bool V4L2ImageProcessor::EnqueueInputRecord() { | |
562 DCHECK(!input_queue_.empty()); | |
563 DCHECK(!free_input_buffers_.empty()); | |
564 | |
565 // Enqueue an input (VIDEO_OUTPUT) buffer for an input video frame. | |
566 linked_ptr<JobRecord> job_record = input_queue_.front(); | |
567 input_queue_.pop(); | |
568 const int index = free_input_buffers_.back(); | |
569 InputRecord& input_record = input_buffer_map_[index]; | |
570 DCHECK(!input_record.at_device); | |
571 input_record.frame = job_record->frame; | |
572 struct v4l2_buffer qbuf; | |
573 struct v4l2_plane qbuf_planes[VIDEO_MAX_PLANES]; | |
574 memset(&qbuf, 0, sizeof(qbuf)); | |
575 memset(qbuf_planes, 0, sizeof(qbuf_planes)); | |
576 qbuf.index = index; | |
577 qbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; | |
578 qbuf.memory = V4L2_MEMORY_USERPTR; | |
579 qbuf.m.planes = qbuf_planes; | |
580 qbuf.length = input_planes_count_; | |
581 for (size_t i = 0; i < input_planes_count_; ++i) { | |
582 qbuf.m.planes[i].bytesused = media::VideoFrame::PlaneSize( | |
583 input_record.frame->format(), i, input_allocated_size_).GetArea(); | |
584 qbuf.m.planes[i].length = qbuf.m.planes[i].bytesused; | |
585 qbuf.m.planes[i].m.userptr = | |
586 reinterpret_cast<unsigned long>(input_record.frame->data(i)); | |
587 } | |
588 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QBUF, &qbuf); | |
589 input_record.at_device = true; | |
590 running_jobs_.push(job_record); | |
591 free_input_buffers_.pop_back(); | |
592 input_buffer_queued_count_++; | |
593 | |
594 DVLOG(3) << __func__ << ": enqueued frame ts=" | |
595 << job_record->frame->timestamp().InMilliseconds() << " to device."; | |
596 | |
597 return true; | |
598 } | |
599 | |
600 bool V4L2ImageProcessor::EnqueueOutputRecord(int index) { | |
601 DCHECK_GE(index, 0); | |
602 DCHECK_LT(index, output_buffer_map_.size()); | |
603 // Enqueue an output (VIDEO_CAPTURE) buffer. | |
604 OutputRecord& output_record = output_buffer_map_[index]; | |
605 DCHECK(!output_record.at_device); | |
606 struct v4l2_buffer qbuf; | |
607 struct v4l2_plane qbuf_planes[VIDEO_MAX_PLANES]; | |
608 memset(&qbuf, 0, sizeof(qbuf)); | |
609 memset(qbuf_planes, 0, sizeof(qbuf_planes)); | |
610 qbuf.index = index; | |
611 qbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; | |
612 qbuf.memory = V4L2_MEMORY_MMAP; | |
613 qbuf.m.planes = qbuf_planes; | |
614 qbuf.length = output_planes_count_; | |
615 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QBUF, &qbuf); | |
616 output_record.at_device = true; | |
617 output_buffer_queued_count_++; | |
618 return true; | |
619 } | |
620 | |
621 bool V4L2ImageProcessor::StartDevicePoll() { | |
622 DVLOG(3) << __func__ << ": starting device poll"; | |
623 DCHECK_EQ(device_thread_.message_loop(), base::MessageLoop::current()); | |
624 DCHECK(!device_poll_thread_.IsRunning()); | |
625 | |
626 // Start up the device poll thread and schedule its first DevicePollTask(). | |
627 if (!device_poll_thread_.Start()) { | |
628 LOG(ERROR) << "StartDevicePoll(): Device thread failed to start"; | |
629 NotifyError(); | |
630 return false; | |
631 } | |
632 // Enqueue a poll task with no devices to poll on - will wait only for the | |
633 // poll interrupt | |
634 device_poll_thread_.message_loop()->PostTask( | |
635 FROM_HERE, | |
636 base::Bind( | |
637 &V4L2ImageProcessor::DevicePollTask, base::Unretained(this), false)); | |
638 | |
639 return true; | |
640 } | |
641 | |
642 bool V4L2ImageProcessor::StopDevicePoll() { | |
643 DVLOG(3) << __func__ << ": stopping device poll"; | |
644 if (device_thread_.IsRunning()) | |
645 DCHECK_EQ(device_thread_.message_loop(), base::MessageLoop::current()); | |
646 | |
647 // Signal the DevicePollTask() to stop, and stop the device poll thread. | |
648 if (!device_->SetDevicePollInterrupt()) | |
649 return false; | |
650 device_poll_thread_.Stop(); | |
651 | |
652 // Clear the interrupt now, to be sure. | |
653 if (!device_->ClearDevicePollInterrupt()) | |
654 return false; | |
655 | |
656 if (input_streamon_) { | |
657 __u32 type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; | |
658 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_STREAMOFF, &type); | |
659 } | |
660 input_streamon_ = false; | |
661 | |
662 if (output_streamon_) { | |
663 __u32 type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; | |
664 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_STREAMOFF, &type); | |
665 } | |
666 output_streamon_ = false; | |
667 | |
668 // Reset all our accounting info. | |
669 while (!input_queue_.empty()) | |
670 input_queue_.pop(); | |
671 | |
672 while (!running_jobs_.empty()) | |
673 running_jobs_.pop(); | |
674 | |
675 free_input_buffers_.clear(); | |
676 for (size_t i = 0; i < input_buffer_map_.size(); ++i) { | |
677 InputRecord& input_record = input_buffer_map_[i]; | |
678 input_record.at_device = false; | |
679 input_record.frame = NULL; | |
680 free_input_buffers_.push_back(i); | |
681 } | |
682 input_buffer_queued_count_ = 0; | |
683 | |
684 output_buffer_map_.clear(); | |
685 output_buffer_map_.resize(num_buffers_); | |
686 output_buffer_queued_count_ = 0; | |
687 | |
688 return true; | |
689 } | |
690 | |
691 void V4L2ImageProcessor::FrameReady(const FrameReadyCB& cb, | |
692 int output_buffer_index) { | |
693 DCHECK(child_task_runner_->BelongsToCurrentThread()); | |
694 cb.Run(output_buffer_index); | |
695 } | |
696 | |
697 } // namespace content | |
OLD | NEW |