1 /*
2 * Copyright (C) 2016 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 #include <stdio.h>
17 #include <stdlib.h>
18 #include <error.h>
19 #include <errno.h>
20 #include <memory.h>
21 #include <fcntl.h>
22 #include <unistd.h>
23 #include <sys/ioctl.h>
24 #include <sys/mman.h>
25 #include <cutils/log.h>
26
27 #include "assert.h"
28
29 #include "VideoCapture.h"
30
31
32 // NOTE: This developmental code does not properly clean up resources in case of failure
33 // during the resource setup phase. Of particular note is the potential to leak
34 // the file descriptor. This must be fixed before using this code for anything but
35 // experimentation.
open(const char * deviceName)36 bool VideoCapture::open(const char* deviceName) {
37 // If we want a polling interface for getting frames, we would use O_NONBLOCK
38 // int mDeviceFd = open(deviceName, O_RDWR | O_NONBLOCK, 0);
39 mDeviceFd = ::open(deviceName, O_RDWR, 0);
40 if (mDeviceFd < 0) {
41 ALOGE("failed to open device %s (%d = %s)", deviceName, errno, strerror(errno));
42 return false;
43 }
44
45 v4l2_capability caps;
46 {
47 int result = ioctl(mDeviceFd, VIDIOC_QUERYCAP, &caps);
48 if (result < 0) {
49 ALOGE("failed to get device caps for %s (%d = %s)", deviceName, errno, strerror(errno));
50 return false;
51 }
52 }
53
54 // Report device properties
55 ALOGI("Open Device: %s (fd=%d)", deviceName, mDeviceFd);
56 ALOGI(" Driver: %s", caps.driver);
57 ALOGI(" Card: %s", caps.card);
58 ALOGI(" Version: %u.%u.%u",
59 (caps.version >> 16) & 0xFF,
60 (caps.version >> 8) & 0xFF,
61 (caps.version) & 0xFF);
62 ALOGI(" All Caps: %08X", caps.capabilities);
63 ALOGI(" Dev Caps: %08X", caps.device_caps);
64
65 // Enumerate the available capture formats (if any)
66 ALOGI("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 ALOGI(" %2d: %s 0x%08X 0x%X",
73 i,
74 formatDescriptions.description,
75 formatDescriptions.pixelformat,
76 formatDescriptions.flags
77 );
78 } else {
79 // No more formats available
80 break;
81 }
82 }
83
84 // Verify we can use this device for video capture
85 if (!(caps.capabilities & V4L2_CAP_VIDEO_CAPTURE) ||
86 !(caps.capabilities & V4L2_CAP_STREAMING)) {
87 // Can't do streaming capture.
88 ALOGE("Streaming capture not supported by %s.", deviceName);
89 return false;
90 }
91
92 // Set our desired output format
93 v4l2_format format;
94 format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
95 format.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY; // Could/should we request V4L2_PIX_FMT_NV21?
96 format.fmt.pix.width = 720; // TODO: Can we avoid hard coding dimensions?
97 format.fmt.pix.height = 240; // For now, this works with available hardware
98 format.fmt.pix.field = V4L2_FIELD_ALTERNATE; // TODO: Do we need to specify this?
99 ALOGI("Requesting format %c%c%c%c (0x%08X)",
100 ((char*)&format.fmt.pix.pixelformat)[0],
101 ((char*)&format.fmt.pix.pixelformat)[1],
102 ((char*)&format.fmt.pix.pixelformat)[2],
103 ((char*)&format.fmt.pix.pixelformat)[3],
104 format.fmt.pix.pixelformat);
105 if (ioctl(mDeviceFd, VIDIOC_S_FMT, &format) < 0) {
106 ALOGE("VIDIOC_S_FMT: %s", strerror(errno));
107 }
108
109 // Report the current output format
110 format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
111 if (ioctl(mDeviceFd, VIDIOC_G_FMT, &format) == 0) {
112
113 mFormat = format.fmt.pix.pixelformat;
114 mWidth = format.fmt.pix.width;
115 mHeight = format.fmt.pix.height;
116 mStride = format.fmt.pix.bytesperline;
117
118 ALOGI("Current output format: fmt=0x%X, %dx%d, pitch=%d",
119 format.fmt.pix.pixelformat,
120 format.fmt.pix.width,
121 format.fmt.pix.height,
122 format.fmt.pix.bytesperline
123 );
124 } else {
125 ALOGE("VIDIOC_G_FMT: %s", strerror(errno));
126 return false;
127 }
128
129 // Make sure we're initialized to the STOPPED state
130 mRunMode = STOPPED;
131 mFrameReady = false;
132
133 // Ready to go!
134 return true;
135 }
136
137
close()138 void VideoCapture::close() {
139 ALOGD("VideoCapture::close");
140 // Stream should be stopped first!
141 assert(mRunMode == STOPPED);
142
143 if (isOpen()) {
144 ALOGD("closing video device file handled %d", mDeviceFd);
145 ::close(mDeviceFd);
146 mDeviceFd = -1;
147 }
148 }
149
150
startStream(std::function<void (VideoCapture *,imageBuffer *,void *)> callback)151 bool VideoCapture::startStream(std::function<void(VideoCapture*, imageBuffer*, void*)> callback) {
152 // Set the state of our background thread
153 int prevRunMode = mRunMode.fetch_or(RUN);
154 if (prevRunMode & RUN) {
155 // The background thread is already running, so we can't start a new stream
156 ALOGE("Already in RUN state, so we can't start a new streaming thread");
157 return false;
158 }
159
160 // Tell the L4V2 driver to prepare our streaming buffers
161 v4l2_requestbuffers bufrequest;
162 bufrequest.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
163 bufrequest.memory = V4L2_MEMORY_MMAP;
164 bufrequest.count = 1;
165 if (ioctl(mDeviceFd, VIDIOC_REQBUFS, &bufrequest) < 0) {
166 ALOGE("VIDIOC_REQBUFS: %s", strerror(errno));
167 return false;
168 }
169
170 // Get the information on the buffer that was created for us
171 memset(&mBufferInfo, 0, sizeof(mBufferInfo));
172 mBufferInfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
173 mBufferInfo.memory = V4L2_MEMORY_MMAP;
174 mBufferInfo.index = 0;
175 if (ioctl(mDeviceFd, VIDIOC_QUERYBUF, &mBufferInfo) < 0) {
176 ALOGE("VIDIOC_QUERYBUF: %s", strerror(errno));
177 return false;
178 }
179
180 ALOGI("Buffer description:");
181 ALOGI(" offset: %d", mBufferInfo.m.offset);
182 ALOGI(" length: %d", mBufferInfo.length);
183
184 // Get a pointer to the buffer contents by mapping into our address space
185 mPixelBuffer = mmap(
186 NULL,
187 mBufferInfo.length,
188 PROT_READ | PROT_WRITE,
189 MAP_SHARED,
190 mDeviceFd,
191 mBufferInfo.m.offset
192 );
193 if( mPixelBuffer == MAP_FAILED) {
194 ALOGE("mmap: %s", strerror(errno));
195 return false;
196 }
197 memset(mPixelBuffer, 0, mBufferInfo.length);
198 ALOGI("Buffer mapped at %p", mPixelBuffer);
199
200 // Queue the first capture buffer
201 if (ioctl(mDeviceFd, VIDIOC_QBUF, &mBufferInfo) < 0) {
202 ALOGE("VIDIOC_QBUF: %s", strerror(errno));
203 return false;
204 }
205
206 // Start the video stream
207 int type = mBufferInfo.type;
208 if (ioctl(mDeviceFd, VIDIOC_STREAMON, &type) < 0) {
209 ALOGE("VIDIOC_STREAMON: %s", strerror(errno));
210 return false;
211 }
212
213 // Remember who to tell about new frames as they arrive
214 mCallback = callback;
215
216 // Fire up a thread to receive and dispatch the video frames
217 mCaptureThread = std::thread([this](){ collectFrames(); });
218
219 ALOGD("Stream started.");
220 return true;
221 }
222
223
stopStream()224 void VideoCapture::stopStream() {
225 // Tell the background thread to stop
226 int prevRunMode = mRunMode.fetch_or(STOPPING);
227 if (prevRunMode == STOPPED) {
228 // The background thread wasn't running, so set the flag back to STOPPED
229 mRunMode = STOPPED;
230 } else if (prevRunMode & STOPPING) {
231 ALOGE("stopStream called while stream is already stopping. Reentrancy is not supported!");
232 return;
233 } else {
234 // Block until the background thread is stopped
235 if (mCaptureThread.joinable()) {
236 mCaptureThread.join();
237 }
238
239 // Stop the underlying video stream (automatically empties the buffer queue)
240 int type = mBufferInfo.type;
241 if (ioctl(mDeviceFd, VIDIOC_STREAMOFF, &type) < 0) {
242 ALOGE("VIDIOC_STREAMOFF: %s", strerror(errno));
243 }
244
245 ALOGD("Capture thread stopped.");
246 }
247
248 // Unmap the buffers we allocated
249 munmap(mPixelBuffer, mBufferInfo.length);
250
251 // Tell the L4V2 driver to release our streaming buffers
252 v4l2_requestbuffers bufrequest;
253 bufrequest.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
254 bufrequest.memory = V4L2_MEMORY_MMAP;
255 bufrequest.count = 0;
256 ioctl(mDeviceFd, VIDIOC_REQBUFS, &bufrequest);
257
258 // Drop our reference to the frame delivery callback interface
259 mCallback = nullptr;
260 }
261
262
markFrameReady()263 void VideoCapture::markFrameReady() {
264 mFrameReady = true;
265 };
266
267
returnFrame()268 bool VideoCapture::returnFrame() {
269 // We're giving the frame back to the system, so clear the "ready" flag
270 mFrameReady = false;
271
272 // Requeue the buffer to capture the next available frame
273 if (ioctl(mDeviceFd, VIDIOC_QBUF, &mBufferInfo) < 0) {
274 ALOGE("VIDIOC_QBUF: %s", strerror(errno));
275 return false;
276 }
277
278 return true;
279 }
280
281
282 // This runs on a background thread to receive and dispatch video frames
collectFrames()283 void VideoCapture::collectFrames() {
284 // Run until our atomic signal is cleared
285 while (mRunMode == RUN) {
286 // Wait for a buffer to be ready
287 if (ioctl(mDeviceFd, VIDIOC_DQBUF, &mBufferInfo) < 0) {
288 ALOGE("VIDIOC_DQBUF: %s", strerror(errno));
289 break;
290 }
291
292 markFrameReady();
293
294 // If a callback was requested per frame, do that now
295 if (mCallback) {
296 mCallback(this, &mBufferInfo, mPixelBuffer);
297 }
298 }
299
300 // Mark ourselves stopped
301 ALOGD("VideoCapture thread ending");
302 mRunMode = STOPPED;
303 }
304