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 // NOTE: This developmental code does not properly clean up resources in case of failure
36 // during the resource setup phase. Of particular note is the potential to leak
37 // the file descriptor. This must be fixed before using this code for anything but
38 // experimentation.
open(const char * deviceName,const int32_t width,const int32_t height)39 bool VideoCapture::open(const char* deviceName, const int32_t width, const int32_t height) {
40 // If we want a polling interface for getting frames, we would use O_NONBLOCK
41 mDeviceFd = ::open(deviceName, O_RDWR, 0);
42 if (mDeviceFd < 0) {
43 PLOG(ERROR) << "failed to open device " << deviceName;
44 return false;
45 }
46
47 v4l2_capability caps;
48 {
49 int result = ioctl(mDeviceFd, VIDIOC_QUERYCAP, &caps);
50 if (result < 0) {
51 PLOG(ERROR) << "failed to get device caps for " << deviceName;
52 return false;
53 }
54 }
55
56 // Report device properties
57 LOG(INFO) << "Open Device: " << deviceName << " (fd = " << mDeviceFd << ")";
58 LOG(DEBUG) << " Driver: " << caps.driver;
59 LOG(DEBUG) << " Card: " << caps.card;
60 LOG(DEBUG) << " Version: " << ((caps.version >> 16) & 0xFF) << "."
61 << ((caps.version >> 8) & 0xFF) << "." << (caps.version & 0xFF);
62 LOG(DEBUG) << " All Caps: " << std::hex << std::setw(8) << caps.capabilities;
63 LOG(DEBUG) << " Dev Caps: " << std::hex << caps.device_caps;
64
65 // Enumerate the available capture formats (if any)
66 LOG(DEBUG) << "Supported capture formats:";
67 v4l2_fmtdesc formatDescriptions;
68 formatDescriptions.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
69 for (int i = 0; true; i++) {
70 formatDescriptions.index = i;
71 if (ioctl(mDeviceFd, VIDIOC_ENUM_FMT, &formatDescriptions) == 0) {
72 LOG(DEBUG) << " " << std::setw(2) << i << ": " << formatDescriptions.description << " "
73 << std::hex << std::setw(8) << formatDescriptions.pixelformat << " "
74 << std::hex << formatDescriptions.flags;
75 } else {
76 // No more formats available
77 break;
78 }
79 }
80
81 // Verify we can use this device for video capture
82 if (!(caps.capabilities & V4L2_CAP_VIDEO_CAPTURE) ||
83 !(caps.capabilities & V4L2_CAP_STREAMING)) {
84 // Can't do streaming capture.
85 LOG(ERROR) << "Streaming capture not supported by " << deviceName;
86 return false;
87 }
88
89 // Set our desired output format
90 v4l2_format format;
91 format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
92 format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
93 format.fmt.pix.width = width;
94 format.fmt.pix.height = height;
95 LOG(INFO) << "Requesting format: " << ((char*)&format.fmt.pix.pixelformat)[0]
96 << ((char*)&format.fmt.pix.pixelformat)[1] << ((char*)&format.fmt.pix.pixelformat)[2]
97 << ((char*)&format.fmt.pix.pixelformat)[3] << "(" << std::hex << std::setw(8)
98 << format.fmt.pix.pixelformat << ")";
99
100 if (ioctl(mDeviceFd, VIDIOC_S_FMT, &format) < 0) {
101 PLOG(ERROR) << "VIDIOC_S_FMT failed";
102 }
103
104 // Report the current output format
105 format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
106 if (ioctl(mDeviceFd, VIDIOC_G_FMT, &format) == 0) {
107 mFormat = format.fmt.pix.pixelformat;
108 mWidth = format.fmt.pix.width;
109 mHeight = format.fmt.pix.height;
110 mStride = format.fmt.pix.bytesperline;
111
112 LOG(INFO) << "Current output format: " << "fmt=0x" << std::hex
113 << format.fmt.pix.pixelformat << ", " << std::dec << format.fmt.pix.width << " x "
114 << format.fmt.pix.height << ", pitch=" << format.fmt.pix.bytesperline;
115 } else {
116 PLOG(ERROR) << "VIDIOC_G_FMT failed";
117 return false;
118 }
119
120 // Make sure we're initialized to the STOPPED state
121 mRunMode = STOPPED;
122 mFrames.clear();
123
124 // Ready to go!
125 return true;
126 }
127
close()128 void VideoCapture::close() {
129 LOG(DEBUG) << __FUNCTION__;
130 // Stream should be stopped first!
131 assert(mRunMode == STOPPED);
132
133 if (isOpen()) {
134 LOG(DEBUG) << "closing video device file handle " << mDeviceFd;
135 ::close(mDeviceFd);
136 mDeviceFd = -1;
137 }
138 }
139
startStream(std::function<void (VideoCapture *,imageBuffer *,void *)> callback)140 bool VideoCapture::startStream(std::function<void(VideoCapture*, imageBuffer*, void*)> callback) {
141 // Set the state of our background thread
142 int prevRunMode = mRunMode.fetch_or(RUN);
143 if (prevRunMode & RUN) {
144 // The background thread is already running, so we can't start a new stream
145 LOG(ERROR) << "Already in RUN state, so we can't start a new streaming thread";
146 return false;
147 }
148
149 // Tell the L4V2 driver to prepare our streaming buffers
150 v4l2_requestbuffers bufrequest;
151 bufrequest.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
152 bufrequest.memory = V4L2_MEMORY_MMAP;
153 bufrequest.count = 1;
154 if (ioctl(mDeviceFd, VIDIOC_REQBUFS, &bufrequest) < 0) {
155 PLOG(ERROR) << "VIDIOC_REQBUFS failed";
156 return false;
157 }
158
159 mNumBuffers = bufrequest.count;
160 mBufferInfos = std::make_unique<v4l2_buffer[]>(mNumBuffers);
161 mPixelBuffers = std::make_unique<void*[]>(mNumBuffers);
162
163 for (int i = 0; i < mNumBuffers; ++i) {
164 // Get the information on the buffer that was created for us
165 memset(&mBufferInfos[i], 0, sizeof(v4l2_buffer));
166 mBufferInfos[i].type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
167 mBufferInfos[i].memory = V4L2_MEMORY_MMAP;
168 mBufferInfos[i].index = i;
169
170 if (ioctl(mDeviceFd, VIDIOC_QUERYBUF, &mBufferInfos[i]) < 0) {
171 PLOG(ERROR) << "VIDIOC_QUERYBUF failed";
172 return false;
173 }
174
175 LOG(DEBUG) << "Buffer description:";
176 LOG(DEBUG) << " offset: " << mBufferInfos[i].m.offset;
177 LOG(DEBUG) << " length: " << mBufferInfos[i].length;
178 LOG(DEBUG) << " flags : " << std::hex << mBufferInfos[i].flags;
179
180 // Get a pointer to the buffer contents by mapping into our address space
181 mPixelBuffers[i] = mmap(NULL, mBufferInfos[i].length, PROT_READ | PROT_WRITE, MAP_SHARED,
182 mDeviceFd, mBufferInfos[i].m.offset);
183
184 if (mPixelBuffers[i] == MAP_FAILED) {
185 PLOG(ERROR) << "mmap() failed";
186 return false;
187 }
188
189 memset(mPixelBuffers[i], 0, mBufferInfos[i].length);
190 LOG(INFO) << "Buffer mapped at " << mPixelBuffers[i];
191
192 // Queue the first capture buffer
193 if (ioctl(mDeviceFd, VIDIOC_QBUF, &mBufferInfos[i]) < 0) {
194 PLOG(ERROR) << "VIDIOC_QBUF failed";
195 return false;
196 }
197 }
198
199 // Start the video stream
200 const int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
201 if (ioctl(mDeviceFd, VIDIOC_STREAMON, &type) < 0) {
202 PLOG(ERROR) << "VIDIOC_STREAMON failed";
203 return false;
204 }
205
206 // Remember who to tell about new frames as they arrive
207 mCallback = callback;
208
209 // Fire up a thread to receive and dispatch the video frames
210 mCaptureThread = std::thread([this]() { collectFrames(); });
211
212 LOG(DEBUG) << "Stream started.";
213 return true;
214 }
215
stopStream()216 void VideoCapture::stopStream() {
217 // Tell the background thread to stop
218 int prevRunMode = mRunMode.fetch_or(STOPPING);
219 if (prevRunMode == STOPPED) {
220 // The background thread wasn't running, so set the flag back to STOPPED
221 mRunMode = STOPPED;
222 } else if (prevRunMode & STOPPING) {
223 LOG(ERROR) << "stopStream called while stream is already stopping. "
224 << "Reentrancy is not supported!";
225 return;
226 } else {
227 // Block until the background thread is stopped
228 if (mCaptureThread.joinable()) {
229 mCaptureThread.join();
230 }
231
232 // Stop the underlying video stream (automatically empties the buffer queue)
233 const int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
234 if (ioctl(mDeviceFd, VIDIOC_STREAMOFF, &type) < 0) {
235 PLOG(ERROR) << "VIDIOC_STREAMOFF failed";
236 }
237
238 LOG(DEBUG) << "Capture thread stopped.";
239 }
240
241 for (int i = 0; i < mNumBuffers; ++i) {
242 // Unmap the buffers we allocated
243 munmap(mPixelBuffers[i], mBufferInfos[i].length);
244 }
245
246 // Tell the L4V2 driver to release our streaming buffers
247 v4l2_requestbuffers bufrequest;
248 bufrequest.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
249 bufrequest.memory = V4L2_MEMORY_MMAP;
250 bufrequest.count = 0;
251 ioctl(mDeviceFd, VIDIOC_REQBUFS, &bufrequest);
252
253 // Drop our reference to the frame delivery callback interface
254 mCallback = nullptr;
255
256 // Release capture buffers
257 mNumBuffers = 0;
258 mBufferInfos = nullptr;
259 mPixelBuffers = nullptr;
260 }
261
returnFrame(int id)262 bool VideoCapture::returnFrame(int id) {
263 if (mFrames.find(id) == mFrames.end()) {
264 LOG(WARNING) << "Invalid request to return a buffer " << id << " is ignored.";
265 return false;
266 }
267
268 // Requeue the buffer to capture the next available frame
269 if (ioctl(mDeviceFd, VIDIOC_QBUF, &mBufferInfos[id]) < 0) {
270 PLOG(ERROR) << "VIDIOC_QBUF failed";
271 return false;
272 }
273
274 // Remove ID of returned buffer from the set
275 mFrames.erase(id);
276
277 return true;
278 }
279
280 // This runs on a background thread to receive and dispatch video frames
collectFrames()281 void VideoCapture::collectFrames() {
282 // Run until our atomic signal is cleared
283 while (mRunMode == RUN) {
284 struct v4l2_buffer buf = {.type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP};
285
286 // Wait for a buffer to be ready
287 if (ioctl(mDeviceFd, VIDIOC_DQBUF, &buf) < 0) {
288 PLOG(ERROR) << "VIDIOC_DQBUF failed";
289 break;
290 }
291
292 mFrames.insert(buf.index);
293
294 // Update a frame metadata
295 mBufferInfos[buf.index] = buf;
296
297 // If a callback was requested per frame, do that now
298 if (mCallback) {
299 mCallback(this, &mBufferInfos[buf.index], mPixelBuffers[buf.index]);
300 }
301 }
302
303 // Mark ourselves stopped
304 LOG(DEBUG) << "VideoCapture thread ending";
305 mRunMode = STOPPED;
306 }
307
setParameter(v4l2_control & control)308 int VideoCapture::setParameter(v4l2_control& control) {
309 int status = ioctl(mDeviceFd, VIDIOC_S_CTRL, &control);
310 if (status < 0) {
311 PLOG(ERROR) << "Failed to program a parameter value " << "id = " << std::hex << control.id;
312 }
313
314 return status;
315 }
316
getParameter(v4l2_control & control)317 int VideoCapture::getParameter(v4l2_control& control) {
318 int status = ioctl(mDeviceFd, VIDIOC_G_CTRL, &control);
319 if (status < 0) {
320 PLOG(ERROR) << "Failed to read a parameter value" << " fd = " << std::hex << mDeviceFd
321 << " id = " << control.id;
322 }
323
324 return status;
325 }
326
enumerateCameraControls()327 std::set<uint32_t> VideoCapture::enumerateCameraControls() {
328 // Retrieve available camera controls
329 struct v4l2_queryctrl ctrl = {.id = V4L2_CTRL_FLAG_NEXT_CTRL};
330
331 std::set<uint32_t> ctrlIDs;
332 while (0 == ioctl(mDeviceFd, VIDIOC_QUERYCTRL, &ctrl)) {
333 if (!(ctrl.flags & V4L2_CTRL_FLAG_DISABLED)) {
334 ctrlIDs.insert(ctrl.id);
335 }
336
337 ctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
338 }
339
340 if (errno != EINVAL) {
341 PLOG(WARNING) << "Failed to run VIDIOC_QUERYCTRL";
342 }
343
344 return std::move(ctrlIDs);
345 }
346