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