
Also cleaning up methods with the same name in other classes that are derived from the above method. R=perkj@webrtc.org Review URL: https://webrtc-codereview.appspot.com/41759004 Cr-Commit-Position: refs/heads/master@{#8242} git-svn-id: http://webrtc.googlecode.com/svn/trunk@8242 4adac7df-926f-26a2-2b94-8c16560cd09d
504 lines
15 KiB
C++
504 lines
15 KiB
C++
/*
|
|
* 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 <errno.h>
|
|
#include <fcntl.h>
|
|
#include <linux/videodev2.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include <iostream>
|
|
#include <new>
|
|
|
|
#include "webrtc/modules/video_capture/linux/video_capture_linux.h"
|
|
#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
|
|
#include "webrtc/system_wrappers/interface/ref_count.h"
|
|
#include "webrtc/system_wrappers/interface/thread_wrapper.h"
|
|
#include "webrtc/system_wrappers/interface/trace.h"
|
|
|
|
namespace webrtc
|
|
{
|
|
namespace videocapturemodule
|
|
{
|
|
VideoCaptureModule* VideoCaptureImpl::Create(const int32_t id,
|
|
const char* deviceUniqueId)
|
|
{
|
|
RefCountImpl<videocapturemodule::VideoCaptureModuleV4L2>* implementation =
|
|
new RefCountImpl<videocapturemodule::VideoCaptureModuleV4L2>(id);
|
|
|
|
if (!implementation || implementation->Init(deviceUniqueId) != 0)
|
|
{
|
|
delete implementation;
|
|
implementation = NULL;
|
|
}
|
|
|
|
return implementation;
|
|
}
|
|
|
|
VideoCaptureModuleV4L2::VideoCaptureModuleV4L2(const int32_t id)
|
|
: VideoCaptureImpl(id),
|
|
_captureThread(NULL),
|
|
_captureCritSect(CriticalSectionWrapper::CreateCriticalSection()),
|
|
_deviceId(-1),
|
|
_deviceFd(-1),
|
|
_buffersAllocatedByDevice(-1),
|
|
_currentWidth(-1),
|
|
_currentHeight(-1),
|
|
_currentFrameRate(-1),
|
|
_captureStarted(false),
|
|
_captureVideoType(kVideoI420),
|
|
_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)
|
|
{
|
|
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id, "no matching device found");
|
|
return -1;
|
|
}
|
|
_deviceId = n; //store the device id
|
|
return 0;
|
|
}
|
|
|
|
VideoCaptureModuleV4L2::~VideoCaptureModuleV4L2()
|
|
{
|
|
StopCapture();
|
|
if (_captureCritSect)
|
|
{
|
|
delete _captureCritSect;
|
|
}
|
|
if (_deviceFd != -1)
|
|
close(_deviceFd);
|
|
}
|
|
|
|
int32_t VideoCaptureModuleV4L2::StartCapture(
|
|
const VideoCaptureCapability& capability)
|
|
{
|
|
if (_captureStarted)
|
|
{
|
|
if (capability.width == _currentWidth &&
|
|
capability.height == _currentHeight &&
|
|
_captureVideoType == capability.rawType)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
StopCapture();
|
|
}
|
|
}
|
|
|
|
CriticalSectionScoped cs(_captureCritSect);
|
|
//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)
|
|
{
|
|
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
|
|
"error in opening %s errono = %d", device, 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;
|
|
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
|
|
"Video Capture enumerats supported image formats:");
|
|
while (ioctl(_deviceFd, VIDIOC_ENUM_FMT, &fmt) == 0) {
|
|
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
|
|
" { pixelformat = %c%c%c%c, description = '%s' }",
|
|
fmt.pixelformat & 0xFF, (fmt.pixelformat>>8) & 0xFF,
|
|
(fmt.pixelformat>>16) & 0xFF, (fmt.pixelformat>>24) & 0xFF,
|
|
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)
|
|
{
|
|
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
|
|
"no supporting video formats found");
|
|
return -1;
|
|
} else {
|
|
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
|
|
"We prefer format %c%c%c%c",
|
|
fmts[fmtsIdx] & 0xFF, (fmts[fmtsIdx]>>8) & 0xFF,
|
|
(fmts[fmtsIdx]>>16) & 0xFF, (fmts[fmtsIdx]>>24) & 0xFF);
|
|
}
|
|
|
|
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 = kVideoYUY2;
|
|
else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420)
|
|
_captureVideoType = kVideoI420;
|
|
else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY)
|
|
_captureVideoType = kVideoUYVY;
|
|
else if (video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG ||
|
|
video_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_JPEG)
|
|
_captureVideoType = kVideoMJPEG;
|
|
|
|
//set format and frame size now
|
|
if (ioctl(_deviceFd, VIDIOC_S_FMT, &video_fmt) < 0)
|
|
{
|
|
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
|
|
"error in VIDIOC_S_FMT, errno = %d", errno);
|
|
return -1;
|
|
}
|
|
|
|
// initialize current width and height
|
|
_currentWidth = video_fmt.fmt.pix.width;
|
|
_currentHeight = video_fmt.fmt.pix.height;
|
|
_captureDelay = 120;
|
|
|
|
// 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) {
|
|
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
|
|
"error in VIDIOC_G_PARM errno = %d", 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) {
|
|
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
|
|
"Failed to set the framerate. errno=%d", 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 != kVideoMJPEG) {
|
|
_currentFrameRate = 15;
|
|
} else {
|
|
_currentFrameRate = 30;
|
|
}
|
|
}
|
|
|
|
if (!AllocateVideoBuffers())
|
|
{
|
|
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
|
|
"failed to allocate video capture buffers");
|
|
return -1;
|
|
}
|
|
|
|
//start capture thread;
|
|
if (!_captureThread)
|
|
{
|
|
_captureThread = ThreadWrapper::CreateThread(
|
|
VideoCaptureModuleV4L2::CaptureThread, this, kHighPriority);
|
|
unsigned int id;
|
|
_captureThread->Start(id);
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
|
|
"Failed to turn on stream");
|
|
return -1;
|
|
}
|
|
|
|
_captureStarted = true;
|
|
return 0;
|
|
}
|
|
|
|
int32_t VideoCaptureModuleV4L2::StopCapture()
|
|
{
|
|
if (_captureThread) {
|
|
// Make sure the capture thread stop stop using the critsect.
|
|
if (_captureThread->Stop()) {
|
|
delete _captureThread;
|
|
_captureThread = NULL;
|
|
} else
|
|
{
|
|
// Couldn't stop the thread, leak instead of crash.
|
|
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, -1,
|
|
"%s: could not stop capture thread", __FUNCTION__);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
CriticalSectionScoped cs(_captureCritSect);
|
|
if (_captureStarted)
|
|
{
|
|
_captureStarted = false;
|
|
_captureThread = NULL;
|
|
|
|
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)
|
|
{
|
|
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
|
|
"Could not get buffers from device. errno = %d", 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)
|
|
{
|
|
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
|
|
"VIDIOC_STREAMOFF error. errno: %d", errno);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VideoCaptureModuleV4L2::CaptureStarted()
|
|
{
|
|
return _captureStarted;
|
|
}
|
|
|
|
bool VideoCaptureModuleV4L2::CaptureThread(void* obj)
|
|
{
|
|
return static_cast<VideoCaptureModuleV4L2*> (obj)->CaptureProcess();
|
|
}
|
|
bool VideoCaptureModuleV4L2::CaptureProcess()
|
|
{
|
|
int retVal = 0;
|
|
fd_set rSet;
|
|
struct timeval timeout;
|
|
|
|
_captureCritSect->Enter();
|
|
|
|
FD_ZERO(&rSet);
|
|
FD_SET(_deviceFd, &rSet);
|
|
timeout.tv_sec = 1;
|
|
timeout.tv_usec = 0;
|
|
|
|
retVal = select(_deviceFd + 1, &rSet, NULL, NULL, &timeout);
|
|
if (retVal < 0 && errno != EINTR) // continue if interrupted
|
|
{
|
|
// select failed
|
|
_captureCritSect->Leave();
|
|
return false;
|
|
}
|
|
else if (retVal == 0)
|
|
{
|
|
// select timed out
|
|
_captureCritSect->Leave();
|
|
return true;
|
|
}
|
|
else if (!FD_ISSET(_deviceFd, &rSet))
|
|
{
|
|
// not event on camera handle
|
|
_captureCritSect->Leave();
|
|
return true;
|
|
}
|
|
|
|
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)
|
|
{
|
|
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
|
|
"could not sync on a buffer on device %s", strerror(errno));
|
|
_captureCritSect->Leave();
|
|
return true;
|
|
}
|
|
}
|
|
VideoCaptureCapability frameInfo;
|
|
frameInfo.width = _currentWidth;
|
|
frameInfo.height = _currentHeight;
|
|
frameInfo.rawType = _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)
|
|
{
|
|
WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideoCapture, _id,
|
|
"Failed to enqueue capture buffer");
|
|
}
|
|
}
|
|
_captureCritSect->Leave();
|
|
usleep(0);
|
|
return true;
|
|
}
|
|
|
|
int32_t VideoCaptureModuleV4L2::CaptureSettings(VideoCaptureCapability& settings)
|
|
{
|
|
settings.width = _currentWidth;
|
|
settings.height = _currentHeight;
|
|
settings.maxFPS = _currentFrameRate;
|
|
settings.rawType=_captureVideoType;
|
|
|
|
return 0;
|
|
}
|
|
} // namespace videocapturemodule
|
|
} // namespace webrtc
|