/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <stdio.h> #include <stdlib.h> #include <error.h> #include <errno.h> #include <memory.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <cutils/log.h> #include "assert.h" #include "VideoCapture.h" // NOTE: This developmental code does not properly clean up resources in case of failure // during the resource setup phase. Of particular note is the potential to leak // the file descriptor. This must be fixed before using this code for anything but // experimentation. bool VideoCapture::open(const char* deviceName) { // If we want a polling interface for getting frames, we would use O_NONBLOCK // int mDeviceFd = open(deviceName, O_RDWR | O_NONBLOCK, 0); mDeviceFd = ::open(deviceName, O_RDWR, 0); if (mDeviceFd < 0) { ALOGE("failed to open device %s (%d = %s)", deviceName, errno, strerror(errno)); return false; } v4l2_capability caps; { int result = ioctl(mDeviceFd, VIDIOC_QUERYCAP, &caps); if (result < 0) { ALOGE("failed to get device caps for %s (%d = %s)", deviceName, errno, strerror(errno)); return false; } } // Report device properties ALOGI("Open Device: %s (fd=%d)", deviceName, mDeviceFd); ALOGI(" Driver: %s", caps.driver); ALOGI(" Card: %s", caps.card); ALOGI(" Version: %u.%u.%u", (caps.version >> 16) & 0xFF, (caps.version >> 8) & 0xFF, (caps.version) & 0xFF); ALOGI(" All Caps: %08X", caps.capabilities); ALOGI(" Dev Caps: %08X", caps.device_caps); // Enumerate the available capture formats (if any) ALOGI("Supported capture formats:"); v4l2_fmtdesc formatDescriptions; formatDescriptions.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; for (int i=0; true; i++) { formatDescriptions.index = i; if (ioctl(mDeviceFd, VIDIOC_ENUM_FMT, &formatDescriptions) == 0) { ALOGI(" %2d: %s 0x%08X 0x%X", i, formatDescriptions.description, formatDescriptions.pixelformat, formatDescriptions.flags ); } else { // No more formats available break; } } // Verify we can use this device for video capture if (!(caps.capabilities & V4L2_CAP_VIDEO_CAPTURE) || !(caps.capabilities & V4L2_CAP_STREAMING)) { // Can't do streaming capture. ALOGE("Streaming capture not supported by %s.", deviceName); return false; } // Set our desired output format v4l2_format format; format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; format.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY; format.fmt.pix.width = 720; format.fmt.pix.height = 240; ALOGI("Requesting format %c%c%c%c (0x%08X)", ((char*)&format.fmt.pix.pixelformat)[0], ((char*)&format.fmt.pix.pixelformat)[1], ((char*)&format.fmt.pix.pixelformat)[2], ((char*)&format.fmt.pix.pixelformat)[3], format.fmt.pix.pixelformat); if (ioctl(mDeviceFd, VIDIOC_S_FMT, &format) < 0) { ALOGE("VIDIOC_S_FMT: %s", strerror(errno)); } // Report the current output format format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(mDeviceFd, VIDIOC_G_FMT, &format) == 0) { mFormat = format.fmt.pix.pixelformat; mWidth = format.fmt.pix.width; mHeight = format.fmt.pix.height; mStride = format.fmt.pix.bytesperline; ALOGI("Current output format: fmt=0x%X, %dx%d, pitch=%d", format.fmt.pix.pixelformat, format.fmt.pix.width, format.fmt.pix.height, format.fmt.pix.bytesperline ); } else { ALOGE("VIDIOC_G_FMT: %s", strerror(errno)); return false; } // Make sure we're initialized to the STOPPED state mRunMode = STOPPED; mFrameReady = false; // Ready to go! return true; } void VideoCapture::close() { ALOGD("VideoCapture::close"); // Stream should be stopped first! assert(mRunMode == STOPPED); if (isOpen()) { ALOGD("closing video device file handled %d", mDeviceFd); ::close(mDeviceFd); mDeviceFd = -1; } } bool VideoCapture::startStream(std::function<void(VideoCapture*, imageBuffer*, void*)> callback) { // Set the state of our background thread int prevRunMode = mRunMode.fetch_or(RUN); if (prevRunMode & RUN) { // The background thread is already running, so we can't start a new stream ALOGE("Already in RUN state, so we can't start a new streaming thread"); return false; } // Tell the L4V2 driver to prepare our streaming buffers v4l2_requestbuffers bufrequest; bufrequest.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; bufrequest.memory = V4L2_MEMORY_MMAP; bufrequest.count = 1; if (ioctl(mDeviceFd, VIDIOC_REQBUFS, &bufrequest) < 0) { ALOGE("VIDIOC_REQBUFS: %s", strerror(errno)); return false; } // Get the information on the buffer that was created for us memset(&mBufferInfo, 0, sizeof(mBufferInfo)); mBufferInfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; mBufferInfo.memory = V4L2_MEMORY_MMAP; mBufferInfo.index = 0; if (ioctl(mDeviceFd, VIDIOC_QUERYBUF, &mBufferInfo) < 0) { ALOGE("VIDIOC_QUERYBUF: %s", strerror(errno)); return false; } ALOGI("Buffer description:"); ALOGI(" offset: %d", mBufferInfo.m.offset); ALOGI(" length: %d", mBufferInfo.length); // Get a pointer to the buffer contents by mapping into our address space mPixelBuffer = mmap( NULL, mBufferInfo.length, PROT_READ | PROT_WRITE, MAP_SHARED, mDeviceFd, mBufferInfo.m.offset ); if( mPixelBuffer == MAP_FAILED) { ALOGE("mmap: %s", strerror(errno)); return false; } memset(mPixelBuffer, 0, mBufferInfo.length); ALOGI("Buffer mapped at %p", mPixelBuffer); // Queue the first capture buffer if (ioctl(mDeviceFd, VIDIOC_QBUF, &mBufferInfo) < 0) { ALOGE("VIDIOC_QBUF: %s", strerror(errno)); return false; } // Start the video stream int type = mBufferInfo.type; if (ioctl(mDeviceFd, VIDIOC_STREAMON, &type) < 0) { ALOGE("VIDIOC_STREAMON: %s", strerror(errno)); return false; } // Remember who to tell about new frames as they arrive mCallback = callback; // Fire up a thread to receive and dispatch the video frames mCaptureThread = std::thread([this](){ collectFrames(); }); ALOGD("Stream started."); return true; } void VideoCapture::stopStream() { // Tell the background thread to stop int prevRunMode = mRunMode.fetch_or(STOPPING); if (prevRunMode == STOPPED) { // The background thread wasn't running, so set the flag back to STOPPED mRunMode = STOPPED; } else if (prevRunMode & STOPPING) { ALOGE("stopStream called while stream is already stopping. Reentrancy is not supported!"); return; } else { // Block until the background thread is stopped if (mCaptureThread.joinable()) { mCaptureThread.join(); } // Stop the underlying video stream (automatically empties the buffer queue) int type = mBufferInfo.type; if (ioctl(mDeviceFd, VIDIOC_STREAMOFF, &type) < 0) { ALOGE("VIDIOC_STREAMOFF: %s", strerror(errno)); } ALOGD("Capture thread stopped."); } // Unmap the buffers we allocated munmap(mPixelBuffer, mBufferInfo.length); // Tell the L4V2 driver to release our streaming buffers v4l2_requestbuffers bufrequest; bufrequest.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; bufrequest.memory = V4L2_MEMORY_MMAP; bufrequest.count = 0; ioctl(mDeviceFd, VIDIOC_REQBUFS, &bufrequest); // Drop our reference to the frame delivery callback interface mCallback = nullptr; } void VideoCapture::markFrameReady() { mFrameReady = true; }; bool VideoCapture::returnFrame() { // We're giving the frame back to the system, so clear the "ready" flag mFrameReady = false; // Requeue the buffer to capture the next available frame if (ioctl(mDeviceFd, VIDIOC_QBUF, &mBufferInfo) < 0) { ALOGE("VIDIOC_QBUF: %s", strerror(errno)); return false; } return true; } // This runs on a background thread to receive and dispatch video frames void VideoCapture::collectFrames() { // Run until our atomic signal is cleared while (mRunMode == RUN) { // Wait for a buffer to be ready if (ioctl(mDeviceFd, VIDIOC_DQBUF, &mBufferInfo) < 0) { ALOGE("VIDIOC_DQBUF: %s", strerror(errno)); break; } markFrameReady(); // If a callback was requested per frame, do that now if (mCallback) { mCallback(this, &mBufferInfo, mPixelBuffer); } } // Mark ourselves stopped ALOGD("VideoCapture thread ending"); mRunMode = STOPPED; }