1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "VideoCapture.h"
18
19 #include <android-base/logging.h>
20
21 #include <assert.h>
22 #include <errno.h>
23 #include <error.h>
24 #include <fcntl.h>
25 #include <memory.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <sys/ioctl.h>
29 #include <sys/mman.h>
30 #include <unistd.h>
31
32 #include <cassert>
33 #include <iomanip>
34
35 namespace {
36
isCaptureSupported(const v4l2_capability & caps)37 inline bool isCaptureSupported(const v4l2_capability& caps) {
38 return (caps.capabilities & (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_CAPTURE_MPLANE)) != 0;
39 }
40
isMultiplanarCaptureSupported(const v4l2_capability & caps)41 inline bool isMultiplanarCaptureSupported(const v4l2_capability& caps) {
42 return (caps.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) != 0;
43 }
44
isStreamingSupported(const v4l2_capability & caps)45 inline bool isStreamingSupported(const v4l2_capability& caps) {
46 return (caps.capabilities & V4L2_CAP_STREAMING) != 0;
47 }
48
49 } // namespace
50
51 // NOTE: This developmental code does not properly clean up resources in case of failure
52 // during the resource setup phase. Of particular note is the potential to leak
53 // the file descriptor. This must be fixed before using this code for anything but
54 // experimentation.
open(const char * deviceName,const int32_t width,const int32_t height)55 bool VideoCapture::open(const char* deviceName, const int32_t width, const int32_t height) {
56 // If we want a polling interface for getting frames, we would use O_NONBLOCK
57 mDeviceFd = ::open(deviceName, O_RDWR, 0);
58 if (mDeviceFd < 0) {
59 PLOG(ERROR) << "failed to open device " << deviceName;
60 return false;
61 }
62
63 v4l2_capability caps;
64 {
65 int result = ioctl(mDeviceFd, VIDIOC_QUERYCAP, &caps);
66 if (result < 0) {
67 PLOG(ERROR) << "failed to get device caps for " << deviceName;
68 return false;
69 }
70 }
71
72 // Report device properties
73 LOG(INFO) << "Open Device: " << deviceName << " (fd = " << mDeviceFd << ")";
74 LOG(INFO) << " Driver: " << caps.driver;
75 LOG(INFO) << " Card: " << caps.card;
76 LOG(INFO) << " Version: " << ((caps.version >> 16) & 0xFF) << "."
77 << ((caps.version >> 8) & 0xFF) << "." << (caps.version & 0xFF);
78 LOG(INFO) << " All Caps: " << std::hex << std::setw(8) << caps.capabilities;
79 LOG(INFO) << " Dev Caps: " << std::hex << caps.device_caps;
80
81 // Verify we can use this device for video capture
82 if (!isCaptureSupported(caps) || !isStreamingSupported(caps)) {
83 // Can't do streaming capture.
84 LOG(ERROR) << "Streaming capture not supported by " << deviceName;
85 return false;
86 }
87
88 mIsMultiplanar = isMultiplanarCaptureSupported(caps);
89
90 // Enumerate the available capture formats (if any)
91 LOG(INFO) << "Supported capture formats:";
92 v4l2_fmtdesc formatDescriptions;
93 formatDescriptions.type =
94 mIsMultiplanar ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : V4L2_BUF_TYPE_VIDEO_CAPTURE;
95 for (int i = 0; true; i++) {
96 formatDescriptions.index = i;
97 if (ioctl(mDeviceFd, VIDIOC_ENUM_FMT, &formatDescriptions) == 0) {
98 LOG(INFO) << " " << std::setw(2) << i << ": " << formatDescriptions.description << " "
99 << std::hex << std::setw(8) << formatDescriptions.pixelformat << " "
100 << std::hex << formatDescriptions.flags;
101 } else {
102 // No more formats available
103 break;
104 }
105 }
106
107 // Set our desired output format; single-plane and YUYV format.
108 v4l2_format format;
109 format.type = mIsMultiplanar ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : V4L2_BUF_TYPE_VIDEO_CAPTURE;
110 format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
111 format.fmt.pix.width = width;
112 format.fmt.pix.height = height;
113 LOG(INFO) << "Requesting format: " << std::string((char*)&format.fmt.pix.pixelformat) << "("
114 << std::hex << std::setw(8) << format.fmt.pix.pixelformat << ")";
115
116 if (ioctl(mDeviceFd, VIDIOC_S_FMT, &format) < 0) {
117 PLOG(WARNING) << "VIDIOC_S_FMT failed";
118 }
119
120 // Report the current output format
121 if (ioctl(mDeviceFd, VIDIOC_G_FMT, &format) == 0) {
122 std::string fmtString;
123 if (mIsMultiplanar) {
124 // See: google3/third_party/OpenCV/public/modules/videoio/src/cap_v4l.cpp
125 mFormat = format.fmt.pix_mp.pixelformat;
126 mWidth = format.fmt.pix_mp.width;
127 mHeight = format.fmt.pix_mp.height;
128 mStride = format.fmt.pix_mp.plane_fmt[0].bytesperline;
129 mNumPlanes = format.fmt.pix_mp.num_planes;
130 fmtString = std::string((char*)&format.fmt.pix_mp.pixelformat);
131 } else {
132 mFormat = format.fmt.pix.pixelformat;
133 mWidth = format.fmt.pix.width;
134 mHeight = format.fmt.pix.height;
135 mStride = format.fmt.pix.bytesperline;
136 fmtString = std::string((char*)&format.fmt.pix.pixelformat);
137 }
138
139 LOG(INFO) << "Current output format: " << fmtString << "(0x" << std::hex << mFormat
140 << "), " << std::dec << mWidth << " x " << mHeight << ", pitch=" << mStride
141 << ", planes=" << mNumPlanes;
142 } else {
143 PLOG(ERROR) << "VIDIOC_G_FMT failed";
144 return false;
145 }
146
147 // Make sure we're initialized to the STOPPED state
148 mRunMode = STOPPED;
149 mFrames.clear();
150
151 // Ready to go!
152 return true;
153 }
154
close()155 void VideoCapture::close() {
156 LOG(DEBUG) << __FUNCTION__;
157 // Stream should be stopped first!
158 assert(mRunMode == STOPPED);
159
160 if (isOpen()) {
161 LOG(DEBUG) << "closing video device file handle " << mDeviceFd;
162 ::close(mDeviceFd);
163 mDeviceFd = -1;
164 }
165 }
166
startStream(std::function<void (VideoCapture *,imageBuffer *,void **,size_t *,size_t numPlanes)> callback)167 bool VideoCapture::startStream(
168 std::function<void(VideoCapture*, imageBuffer*, void**, size_t*, size_t numPlanes)>
169 callback) {
170 // Set the state of our background thread
171 int prevRunMode = mRunMode.fetch_or(RUN);
172 if (prevRunMode & RUN) {
173 // The background thread is already running, so we can't start a new stream
174 LOG(ERROR) << "Already in RUN state, so we can't start a new streaming thread";
175 return false;
176 }
177
178 // Tell the V4L2 driver to prepare our streaming buffers
179 v4l2_requestbuffers bufrequest;
180 bufrequest.type =
181 mIsMultiplanar ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : V4L2_BUF_TYPE_VIDEO_CAPTURE;
182 bufrequest.memory = V4L2_MEMORY_MMAP;
183 bufrequest.count = 1;
184 if (ioctl(mDeviceFd, VIDIOC_REQBUFS, &bufrequest) < 0) {
185 PLOG(ERROR) << "VIDIOC_REQBUFS failed";
186 return false;
187 }
188
189 mNumBuffers = bufrequest.count;
190 mBufferInfos = std::make_unique<BufferDesc[]>(mNumBuffers);
191
192 for (int i = 0; i < mNumBuffers; ++i) {
193 // Get the information on the buffer that was created for us
194 memset(&mBufferInfos[i].buffer, 0, sizeof(v4l2_buffer));
195 mBufferInfos[i].buffer.memory = V4L2_MEMORY_MMAP;
196 mBufferInfos[i].buffer.index = i;
197
198 if (mIsMultiplanar) {
199 mBufferInfos[i].buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
200 mBufferInfos[i].buffer.m.planes = mBufferInfos[i].planes;
201 mBufferInfos[i].buffer.length = VIDEO_MAX_PLANES;
202 } else {
203 mBufferInfos[i].buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
204 }
205
206 if (ioctl(mDeviceFd, VIDIOC_QUERYBUF, &mBufferInfos[i].buffer) < 0) {
207 PLOG(ERROR) << "VIDIOC_QUERYBUF failed";
208 return false;
209 }
210
211 for (auto j = 0u; j < mNumPlanes; ++j) {
212 const auto length = mIsMultiplanar ? mBufferInfos[i].buffer.m.planes[j].length
213 : mBufferInfos[i].buffer.length;
214 const auto offset = mIsMultiplanar ? mBufferInfos[i].buffer.m.planes[j].m.mem_offset
215 : mBufferInfos[i].buffer.m.offset;
216
217 LOG(DEBUG) << "Buffer description:";
218 LOG(DEBUG) << " plane : " << j;
219 LOG(DEBUG) << " offset: " << offset;
220 LOG(DEBUG) << " length: " << length;
221 LOG(DEBUG) << " flags : " << std::hex << mBufferInfos[i].buffer.flags;
222
223 // Get a pointer to the buffer contents by mapping into our address space
224 mBufferInfos[i].start[j] =
225 mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, mDeviceFd, offset);
226 mBufferInfos[i].length[j] = length;
227 if (mBufferInfos[i].start[j] == MAP_FAILED) {
228 PLOG(ERROR) << "mmap() failed";
229 return false;
230 }
231
232 memset(mBufferInfos[i].start[j], 0, length);
233 LOG(INFO) << "Buffer mapped at " << mBufferInfos[i].start[j];
234 }
235
236 // Queue the first capture buffer
237 if (ioctl(mDeviceFd, VIDIOC_QBUF, &mBufferInfos[i].buffer) < 0) {
238 PLOG(ERROR) << "VIDIOC_QBUF failed";
239 return false;
240 }
241 }
242
243 // Start the video stream
244 const int type =
245 mIsMultiplanar ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : V4L2_BUF_TYPE_VIDEO_CAPTURE;
246 if (ioctl(mDeviceFd, VIDIOC_STREAMON, &type) < 0) {
247 PLOG(ERROR) << "VIDIOC_STREAMON failed";
248 return false;
249 }
250
251 // Remember who to tell about new frames as they arrive
252 mCallback = callback;
253
254 // Fire up a thread to receive and dispatch the video frames
255 mCaptureThread = std::thread([this]() { collectFrames(); });
256
257 LOG(DEBUG) << "Stream started.";
258 return true;
259 }
260
stopStream()261 void VideoCapture::stopStream() {
262 // Tell the background thread to stop
263 int prevRunMode = mRunMode.fetch_or(STOPPING);
264 if (prevRunMode == STOPPED) {
265 // The background thread wasn't running, so set the flag back to STOPPED
266 mRunMode = STOPPED;
267 } else if (prevRunMode & STOPPING) {
268 LOG(ERROR) << "stopStream called while stream is already stopping. "
269 << "Reentrancy is not supported!";
270 return;
271 } else {
272 // Block until the background thread is stopped
273 if (mCaptureThread.joinable()) {
274 mCaptureThread.join();
275 }
276
277 // Stop the underlying video stream (automatically empties the buffer queue)
278 const int type =
279 mIsMultiplanar ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : V4L2_BUF_TYPE_VIDEO_CAPTURE;
280 if (ioctl(mDeviceFd, VIDIOC_STREAMOFF, &type) < 0) {
281 PLOG(ERROR) << "VIDIOC_STREAMOFF failed";
282 }
283
284 LOG(DEBUG) << "Capture thread stopped.";
285 }
286
287 for (int i = 0; i < mNumBuffers; ++i) {
288 for (auto j = 0u; j < mNumPlanes; ++j) {
289 // Unmap the buffers we allocated
290 munmap(mBufferInfos[i].start[j], mBufferInfos[i].length[j]);
291 }
292 }
293
294 // Tell the L4V2 driver to release our streaming buffers
295 v4l2_requestbuffers bufrequest;
296 bufrequest.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
297 bufrequest.memory = V4L2_MEMORY_MMAP;
298 bufrequest.count = 0;
299 ioctl(mDeviceFd, VIDIOC_REQBUFS, &bufrequest);
300
301 // Drop our reference to the frame delivery callback interface
302 mCallback = nullptr;
303
304 // Release capture buffers
305 mNumBuffers = 0;
306 mBufferInfos = nullptr;
307 }
308
returnFrame(int id)309 bool VideoCapture::returnFrame(int id) {
310 if (mFrames.find(id) == mFrames.end()) {
311 LOG(WARNING) << "Invalid request to return a buffer " << id << " is ignored.";
312 return false;
313 }
314
315 // Requeue the buffer to capture the next available frame
316 if (ioctl(mDeviceFd, VIDIOC_QBUF, &mBufferInfos[id]) < 0) {
317 PLOG(ERROR) << "VIDIOC_QBUF failed";
318 return false;
319 }
320
321 // Remove ID of returned buffer from the set
322 mFrames.erase(id);
323
324 return true;
325 }
326
327 // This runs on a background thread to receive and dispatch video frames
collectFrames()328 void VideoCapture::collectFrames() {
329 // Run until our atomic signal is cleared
330 while (mRunMode == RUN) {
331 v4l2_buffer buf = {.memory = V4L2_MEMORY_MMAP};
332 v4l2_plane mplanes[VIDEO_MAX_PLANES];
333
334 if (!mIsMultiplanar) {
335 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
336 v4l2_buffer buf = {.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
337 .memory = V4L2_MEMORY_MMAP};
338 } else {
339 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
340 buf.m.planes = mplanes;
341 buf.length = VIDEO_MAX_PLANES;
342 }
343
344 // Wait for a buffer to be ready
345 if (ioctl(mDeviceFd, VIDIOC_DQBUF, &buf) < 0) {
346 PLOG(ERROR) << "VIDIOC_DQBUF failed";
347 break;
348 }
349
350 mFrames.insert(buf.index);
351
352 // Update a frame metadata
353 mBufferInfos[buf.index].buffer = buf;
354 if (mIsMultiplanar) {
355 // Copy v4l2_plane metadata.
356 mBufferInfos[buf.index].buffer.m.planes = mBufferInfos[buf.index].planes;
357 memcpy(mBufferInfos[buf.index].planes, buf.m.planes, sizeof(mplanes));
358
359 auto offset = 0;
360 for (auto i = 0u; i < mNumPlanes; ++i) {
361 auto bytesused = mBufferInfos[buf.index].planes[i].bytesused -
362 mBufferInfos[buf.index].planes[i].data_offset;
363 offset += bytesused;
364 }
365 mBufferInfos[buf.index].bytesused = offset;
366 } else {
367 mBufferInfos[buf.index].bytesused = mBufferInfos[buf.index].buffer.bytesused;
368 }
369
370 // If a callback was requested per frame, do that now
371 if (mCallback) {
372 mCallback(this, &mBufferInfos[buf.index].buffer, mBufferInfos[buf.index].start,
373 mBufferInfos[buf.index].length, mNumPlanes);
374 }
375 }
376
377 // Mark ourselves stopped
378 LOG(DEBUG) << "VideoCapture thread ending";
379 mRunMode = STOPPED;
380 }
381
setParameter(v4l2_control & control)382 int VideoCapture::setParameter(v4l2_control& control) {
383 int status = ioctl(mDeviceFd, VIDIOC_S_CTRL, &control);
384 if (status < 0) {
385 PLOG(ERROR) << "Failed to program a parameter value " << "id = " << std::hex << control.id;
386 }
387
388 return status;
389 }
390
getParameter(v4l2_control & control)391 int VideoCapture::getParameter(v4l2_control& control) {
392 int status = ioctl(mDeviceFd, VIDIOC_G_CTRL, &control);
393 if (status < 0) {
394 PLOG(ERROR) << "Failed to read a parameter value" << " fd = " << std::hex << mDeviceFd
395 << " id = " << control.id;
396 }
397
398 return status;
399 }
400
enumerateCameraControls()401 std::set<uint32_t> VideoCapture::enumerateCameraControls() {
402 // Retrieve available camera controls
403 struct v4l2_queryctrl ctrl = {.id = V4L2_CTRL_FLAG_NEXT_CTRL};
404
405 std::set<uint32_t> ctrlIDs;
406 while (0 == ioctl(mDeviceFd, VIDIOC_QUERYCTRL, &ctrl)) {
407 if (!(ctrl.flags & V4L2_CTRL_FLAG_DISABLED)) {
408 ctrlIDs.insert(ctrl.id);
409 }
410
411 ctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
412 }
413
414 if (errno != EINVAL) {
415 PLOG(WARNING) << "Failed to run VIDIOC_QUERYCTRL";
416 }
417
418 return ctrlIDs;
419 }
420