/* * 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 #include #include #include #include #include #include #include #include #include #include #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, const int32_t width, const int32_t height) { // 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) { PLOG(ERROR) << "failed to open device " << deviceName; return false; } v4l2_capability caps; { int result = ioctl(mDeviceFd, VIDIOC_QUERYCAP, &caps); if (result < 0) { PLOG(ERROR) << "failed to get device caps for " << deviceName; return false; } } // Report device properties LOG(INFO) << "Open Device: " << deviceName << " (fd = " << mDeviceFd << ")"; LOG(INFO) << " Driver: " << caps.driver; LOG(INFO) << " Card: " << caps.card; LOG(INFO) << " Version: " << ((caps.version >> 16) & 0xFF) << "." << ((caps.version >> 8) & 0xFF) << "." << (caps.version & 0xFF); LOG(INFO) << " All Caps: " << std::hex << std::setw(8) << caps.capabilities; LOG(INFO) << " Dev Caps: " << std::hex << caps.device_caps; // Enumerate the available capture formats (if any) LOG(INFO) << "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) { LOG(INFO) << " " << std::setw(2) << i << ": " << formatDescriptions.description << " " << std::hex << std::setw(8) << formatDescriptions.pixelformat << " " << std::hex << 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. LOG(ERROR) << "Streaming capture not supported by " << 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 = width; format.fmt.pix.height = height; LOG(INFO) << "Requesting format: " << ((char*)&format.fmt.pix.pixelformat)[0] << ((char*)&format.fmt.pix.pixelformat)[1] << ((char*)&format.fmt.pix.pixelformat)[2] << ((char*)&format.fmt.pix.pixelformat)[3] << "(" << std::hex << std::setw(8) << format.fmt.pix.pixelformat << ")"; if (ioctl(mDeviceFd, VIDIOC_S_FMT, &format) < 0) { PLOG(ERROR) << "VIDIOC_S_FMT failed"; } // 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; LOG(INFO) << "Current output format: " << "fmt=0x" << std::hex << format.fmt.pix.pixelformat << ", " << std::dec << format.fmt.pix.width << " x " << format.fmt.pix.height << ", pitch=" << format.fmt.pix.bytesperline; } else { PLOG(ERROR) << "VIDIOC_G_FMT failed"; return false; } // Make sure we're initialized to the STOPPED state mRunMode = STOPPED; mFrameReady = false; // Ready to go! return true; } void VideoCapture::close() { LOG(DEBUG) << __FUNCTION__; // Stream should be stopped first! assert(mRunMode == STOPPED); if (isOpen()) { LOG(DEBUG) << "closing video device file handle " << mDeviceFd; ::close(mDeviceFd); mDeviceFd = -1; } } bool VideoCapture::startStream(std::function 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 LOG(ERROR) << "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) { PLOG(ERROR) << "VIDIOC_REQBUFS failed"; 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) { PLOG(ERROR) << "VIDIOC_QUERYBUF failed"; return false; } LOG(INFO) << "Buffer description:"; LOG(INFO) << " offset: " << mBufferInfo.m.offset; LOG(INFO) << " length: " << mBufferInfo.length; LOG(INFO) << " flags : " << std::hex << mBufferInfo.flags; // 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) { PLOG(ERROR) << "mmap() failed"; return false; } memset(mPixelBuffer, 0, mBufferInfo.length); LOG(INFO) << "Buffer mapped at " << mPixelBuffer; // Queue the first capture buffer if (ioctl(mDeviceFd, VIDIOC_QBUF, &mBufferInfo) < 0) { PLOG(ERROR) << "VIDIOC_QBUF failed"; return false; } // Start the video stream int type = mBufferInfo.type; if (ioctl(mDeviceFd, VIDIOC_STREAMON, &type) < 0) { PLOG(ERROR) << "VIDIOC_STREAMON failed"; 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(); }); LOG(DEBUG) << "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) { LOG(ERROR) << "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) { PLOG(ERROR) << "VIDIOC_STREAMOFF failed"; } LOG(DEBUG) << "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) { PLOG(ERROR) << "VIDIOC_QBUF failed"; 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) { PLOG(ERROR) << "VIDIOC_DQBUF failed"; break; } markFrameReady(); // If a callback was requested per frame, do that now if (mCallback) { mCallback(this, &mBufferInfo, mPixelBuffer); } } // Mark ourselves stopped LOG(DEBUG) << "VideoCapture thread ending"; mRunMode = STOPPED; } int VideoCapture::setParameter(v4l2_control& control) { int status = ioctl(mDeviceFd, VIDIOC_S_CTRL, &control); if (status < 0) { PLOG(ERROR) << "Failed to program a parameter value " << "id = " << std::hex << control.id; } return status; } int VideoCapture::getParameter(v4l2_control& control) { int status = ioctl(mDeviceFd, VIDIOC_G_CTRL, &control); if (status < 0) { PLOG(ERROR) << "Failed to read a parameter value" << " fd = " << std::hex << mDeviceFd << " id = " << control.id; } return status; } std::set VideoCapture::enumerateCameraControls() { // Retrieve available camera controls struct v4l2_queryctrl ctrl = { .id = V4L2_CTRL_FLAG_NEXT_CTRL }; std::set ctrlIDs; while (0 == ioctl(mDeviceFd, VIDIOC_QUERYCTRL, &ctrl)) { if (!(ctrl.flags & V4L2_CTRL_FLAG_DISABLED)) { ctrlIDs.emplace(ctrl.id); } ctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL; } if (errno != EINVAL) { PLOG(WARNING) << "Failed to run VIDIOC_QUERYCTRL"; } return std::move(ctrlIDs); }