/* * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/video_capture/linux/video_capture_linux.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "api/scoped_refptr.h" #include "media/base/video_common.h" #include "modules/video_capture/video_capture.h" #include "rtc_base/logging.h" #include "rtc_base/ref_counted_object.h" namespace webrtc { namespace videocapturemodule { rtc::scoped_refptr VideoCaptureImpl::Create( const char* deviceUniqueId) { rtc::scoped_refptr implementation( new rtc::RefCountedObject()); if (implementation->Init(deviceUniqueId) != 0) return nullptr; return implementation; } VideoCaptureModuleV4L2::VideoCaptureModuleV4L2() : VideoCaptureImpl(), _deviceId(-1), _deviceFd(-1), _buffersAllocatedByDevice(-1), _currentWidth(-1), _currentHeight(-1), _currentFrameRate(-1), _captureStarted(false), _captureVideoType(VideoType::kI420), _pool(NULL) {} int32_t VideoCaptureModuleV4L2::Init(const char* deviceUniqueIdUTF8) { int len = strlen((const char*)deviceUniqueIdUTF8); _deviceUniqueId = new (std::nothrow) char[len + 1]; if (_deviceUniqueId) { memcpy(_deviceUniqueId, deviceUniqueIdUTF8, len + 1); } int fd; char device[32]; bool found = false; /* detect /dev/video [0-63] entries */ int n; for (n = 0; n < 64; n++) { sprintf(device, "/dev/video%d", n); if ((fd = open(device, O_RDONLY)) != -1) { // query device capabilities struct v4l2_capability cap; if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) { if (cap.bus_info[0] != 0) { if (strncmp((const char*)cap.bus_info, (const char*)deviceUniqueIdUTF8, strlen((const char*)deviceUniqueIdUTF8)) == 0) // match with device id { close(fd); found = true; break; // fd matches with device unique id supplied } } } close(fd); // close since this is not the matching device } } if (!found) { RTC_LOG(LS_INFO) << "no matching device found"; return -1; } _deviceId = n; // store the device id return 0; } VideoCaptureModuleV4L2::~VideoCaptureModuleV4L2() { StopCapture(); if (_deviceFd != -1) close(_deviceFd); } int32_t VideoCaptureModuleV4L2::StartCapture( const VideoCaptureCapability& capability) { if (_captureStarted) { if (capability.width == _currentWidth && capability.height == _currentHeight && _captureVideoType == capability.videoType) { return 0; } else { StopCapture(); } } MutexLock lock(&capture_lock_); // first open /dev/video device char device[20]; sprintf(device, "/dev/video%d", (int)_deviceId); if ((_deviceFd = open(device, O_RDWR | O_NONBLOCK, 0)) < 0) { RTC_LOG(LS_INFO) << "error in opening " << device << " errono = " << errno; return -1; } // Supported video formats in preferred order. // If the requested resolution is larger than VGA, we prefer MJPEG. Go for // I420 otherwise. const int nFormats = 5; unsigned int fmts[nFormats]; if (capability.width > 640 || capability.height > 480) { fmts[0] = V4L2_PIX_FMT_MJPEG; fmts[1] = V4L2_PIX_FMT_YUV420; fmts[2] = V4L2_PIX_FMT_YUYV; fmts[3] = V4L2_PIX_FMT_UYVY; fmts[4] = V4L2_PIX_FMT_JPEG; } else { fmts[0] = V4L2_PIX_FMT_YUV420; fmts[1] = V4L2_PIX_FMT_YUYV; fmts[2] = V4L2_PIX_FMT_UYVY; fmts[3] = V4L2_PIX_FMT_MJPEG; fmts[4] = V4L2_PIX_FMT_JPEG; } // Enumerate image formats. struct v4l2_fmtdesc fmt; int fmtsIdx = nFormats; memset(&fmt, 0, sizeof(fmt)); fmt.index = 0; fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; RTC_LOG(LS_INFO) << "Video Capture enumerats supported image formats:"; while (ioctl(_deviceFd, VIDIOC_ENUM_FMT, &fmt) == 0) { RTC_LOG(LS_INFO) << " { pixelformat = " << cricket::GetFourccName(fmt.pixelformat) << ", description = '" << fmt.description << "' }"; // Match the preferred order. for (int i = 0; i < nFormats; i++) { if (fmt.pixelformat == fmts[i] && i < fmtsIdx) fmtsIdx = i; } // Keep enumerating. fmt.index++; } if (fmtsIdx == nFormats) { RTC_LOG(LS_INFO) << "no supporting video formats found"; return -1; } else { RTC_LOG(LS_INFO) << "We prefer format " << cricket::GetFourccName(fmts[fmtsIdx]); } struct v4l2_format video_fmt; memset(&video_fmt, 0, sizeof(struct v4l2_format)); video_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; video_fmt.fmt.pix.sizeimage = 0; video_fmt.fmt.pix.width = capability.width; video_fmt.fmt.pix.height = capability.height; video_fmt.fmt.pix.pixelformat = fmts[fmtsIdx]; if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) _captureVideoType = VideoType::kYUY2; else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) _captureVideoType = VideoType::kI420; else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) _captureVideoType = VideoType::kUYVY; else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG || video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_JPEG) _captureVideoType = VideoType::kMJPEG; // set format and frame size now if (ioctl(_deviceFd, VIDIOC_S_FMT, &video_fmt) < 0) { RTC_LOG(LS_INFO) << "error in VIDIOC_S_FMT, errno = " << errno; return -1; } // initialize current width and height _currentWidth = video_fmt.fmt.pix.width; _currentHeight = video_fmt.fmt.pix.height; // Trying to set frame rate, before check driver capability. bool driver_framerate_support = true; struct v4l2_streamparm streamparms; memset(&streamparms, 0, sizeof(streamparms)); streamparms.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(_deviceFd, VIDIOC_G_PARM, &streamparms) < 0) { RTC_LOG(LS_INFO) << "error in VIDIOC_G_PARM errno = " << errno; driver_framerate_support = false; // continue } else { // check the capability flag is set to V4L2_CAP_TIMEPERFRAME. if (streamparms.parm.capture.capability & V4L2_CAP_TIMEPERFRAME) { // driver supports the feature. Set required framerate. memset(&streamparms, 0, sizeof(streamparms)); streamparms.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; streamparms.parm.capture.timeperframe.numerator = 1; streamparms.parm.capture.timeperframe.denominator = capability.maxFPS; if (ioctl(_deviceFd, VIDIOC_S_PARM, &streamparms) < 0) { RTC_LOG(LS_INFO) << "Failed to set the framerate. errno=" << errno; driver_framerate_support = false; } else { _currentFrameRate = capability.maxFPS; } } } // If driver doesn't support framerate control, need to hardcode. // Hardcoding the value based on the frame size. if (!driver_framerate_support) { if (_currentWidth >= 800 && _captureVideoType != VideoType::kMJPEG) { _currentFrameRate = 15; } else { _currentFrameRate = 30; } } if (!AllocateVideoBuffers()) { RTC_LOG(LS_INFO) << "failed to allocate video capture buffers"; return -1; } // start capture thread; if (!_captureThread) { quit_ = false; _captureThread.reset( new rtc::PlatformThread(VideoCaptureModuleV4L2::CaptureThread, this, "CaptureThread", rtc::kHighPriority)); _captureThread->Start(); } // Needed to start UVC camera - from the uvcview application enum v4l2_buf_type type; type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(_deviceFd, VIDIOC_STREAMON, &type) == -1) { RTC_LOG(LS_INFO) << "Failed to turn on stream"; return -1; } _captureStarted = true; return 0; } int32_t VideoCaptureModuleV4L2::StopCapture() { if (_captureThread) { { MutexLock lock(&capture_lock_); quit_ = true; } // Make sure the capture thread stop stop using the critsect. _captureThread->Stop(); _captureThread.reset(); } MutexLock lock(&capture_lock_); if (_captureStarted) { _captureStarted = false; DeAllocateVideoBuffers(); close(_deviceFd); _deviceFd = -1; } return 0; } // critical section protected by the caller bool VideoCaptureModuleV4L2::AllocateVideoBuffers() { struct v4l2_requestbuffers rbuffer; memset(&rbuffer, 0, sizeof(v4l2_requestbuffers)); rbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; rbuffer.memory = V4L2_MEMORY_MMAP; rbuffer.count = kNoOfV4L2Bufffers; if (ioctl(_deviceFd, VIDIOC_REQBUFS, &rbuffer) < 0) { RTC_LOG(LS_INFO) << "Could not get buffers from device. errno = " << errno; return false; } if (rbuffer.count > kNoOfV4L2Bufffers) rbuffer.count = kNoOfV4L2Bufffers; _buffersAllocatedByDevice = rbuffer.count; // Map the buffers _pool = new Buffer[rbuffer.count]; for (unsigned int i = 0; i < rbuffer.count; i++) { struct v4l2_buffer buffer; memset(&buffer, 0, sizeof(v4l2_buffer)); buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buffer.memory = V4L2_MEMORY_MMAP; buffer.index = i; if (ioctl(_deviceFd, VIDIOC_QUERYBUF, &buffer) < 0) { return false; } _pool[i].start = mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, _deviceFd, buffer.m.offset); if (MAP_FAILED == _pool[i].start) { for (unsigned int j = 0; j < i; j++) munmap(_pool[j].start, _pool[j].length); return false; } _pool[i].length = buffer.length; if (ioctl(_deviceFd, VIDIOC_QBUF, &buffer) < 0) { return false; } } return true; } bool VideoCaptureModuleV4L2::DeAllocateVideoBuffers() { // unmap buffers for (int i = 0; i < _buffersAllocatedByDevice; i++) munmap(_pool[i].start, _pool[i].length); delete[] _pool; // turn off stream enum v4l2_buf_type type; type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(_deviceFd, VIDIOC_STREAMOFF, &type) < 0) { RTC_LOG(LS_INFO) << "VIDIOC_STREAMOFF error. errno: " << errno; } return true; } bool VideoCaptureModuleV4L2::CaptureStarted() { return _captureStarted; } void VideoCaptureModuleV4L2::CaptureThread(void* obj) { VideoCaptureModuleV4L2* capture = static_cast(obj); while (capture->CaptureProcess()) { } } bool VideoCaptureModuleV4L2::CaptureProcess() { int retVal = 0; fd_set rSet; struct timeval timeout; FD_ZERO(&rSet); FD_SET(_deviceFd, &rSet); timeout.tv_sec = 1; timeout.tv_usec = 0; // _deviceFd written only in StartCapture, when this thread isn't running. retVal = select(_deviceFd + 1, &rSet, NULL, NULL, &timeout); if (retVal < 0 && errno != EINTR) // continue if interrupted { // select failed return false; } else if (retVal == 0) { // select timed out return true; } else if (!FD_ISSET(_deviceFd, &rSet)) { // not event on camera handle return true; } { MutexLock lock(&capture_lock_); if (quit_) { return false; } if (_captureStarted) { struct v4l2_buffer buf; memset(&buf, 0, sizeof(struct v4l2_buffer)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; // dequeue a buffer - repeat until dequeued properly! while (ioctl(_deviceFd, VIDIOC_DQBUF, &buf) < 0) { if (errno != EINTR) { RTC_LOG(LS_INFO) << "could not sync on a buffer on device " << strerror(errno); return true; } } VideoCaptureCapability frameInfo; frameInfo.width = _currentWidth; frameInfo.height = _currentHeight; frameInfo.videoType = _captureVideoType; // convert to to I420 if needed IncomingFrame((unsigned char*)_pool[buf.index].start, buf.bytesused, frameInfo); // enqueue the buffer again if (ioctl(_deviceFd, VIDIOC_QBUF, &buf) == -1) { RTC_LOG(LS_INFO) << "Failed to enqueue capture buffer"; } } } usleep(0); return true; } int32_t VideoCaptureModuleV4L2::CaptureSettings( VideoCaptureCapability& settings) { settings.width = _currentWidth; settings.height = _currentHeight; settings.maxFPS = _currentFrameRate; settings.videoType = _captureVideoType; return 0; } } // namespace videocapturemodule } // namespace webrtc