• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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