- merge v0.01 --> Añadido fileselector - Añadidas fuentes de Gem y Pure Data - pix2jpg incluído en Gem. Archivos de construcción de Gem modificados. - Añadido fichero ompiling.txt con instrucciones de compilación
1188 lines
31 KiB
C++
1188 lines
31 KiB
C++
////////////////////////////////////////////////////////
|
|
//
|
|
// GEM - Graphics Environment for Multimedia
|
|
//
|
|
// zmoelnig@iem.kug.ac.at
|
|
//
|
|
// Implementation file
|
|
//
|
|
// Copyright (c) 1997-1998 Mark Danks.
|
|
// Copyright (c) Günther Geiger.
|
|
// Copyright (c) 2001-2011 IOhannes m zmölnig. forum::für::umläute. IEM. zmoelnig@iem.at
|
|
// For information on usage and redistribution, and for a DISCLAIMER OF ALL
|
|
// WARRANTIES, see the file, "GEM.LICENSE.TERMS" in this distribution.
|
|
//
|
|
/////////////////////////////////////////////////////////
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include "videoV4L2.h"
|
|
#include "plugins/PluginFactory.h"
|
|
|
|
using namespace gem::plugins;
|
|
|
|
#include "Gem/RTE.h"
|
|
#include "Gem/Files.h"
|
|
|
|
#ifndef HAVE_LIBV4L2
|
|
# define v4l2_open ::open
|
|
# define v4l2_close ::close
|
|
# define v4l2_dup ::dup
|
|
# define v4l2_ioctl ::ioctl
|
|
# define v4l2_read ::read
|
|
# define v4l2_mmap ::mmap
|
|
# define v4l2_munmap ::munmap
|
|
#endif /* libv4l-2 */
|
|
|
|
|
|
/* debugging helpers */
|
|
#define debugPost
|
|
#define debugThread
|
|
#define debugIOCTL
|
|
|
|
#if 0
|
|
# undef debugPost
|
|
# define debugPost ::startpost("%s:%s[%d]", __FILE__, __FUNCTION__, __LINE__); ::post
|
|
#endif
|
|
|
|
#if 0
|
|
# undef debugThread
|
|
# define debugThread ::startpost("%s:%s[%d]", __FILE__, __FUNCTION__, __LINE__); ::post
|
|
#endif
|
|
|
|
#if 0
|
|
# undef debugIOCTL
|
|
# define debugIOCTL ::post
|
|
#endif
|
|
|
|
|
|
#define WIDTH_FLAG 1
|
|
#define HEIGHT_FLAG 2
|
|
|
|
/////////////////////////////////////////////////////////
|
|
//
|
|
// videoV4L2
|
|
//
|
|
/////////////////////////////////////////////////////////
|
|
// Constructor
|
|
//
|
|
/////////////////////////////////////////////////////////
|
|
#ifdef HAVE_VIDEO4LINUX2
|
|
|
|
#include <sys/stat.h>
|
|
|
|
REGISTER_VIDEOFACTORY("v4l2", videoV4L2);
|
|
|
|
videoV4L2 :: videoV4L2() : videoBase("v4l2", 0)
|
|
, m_gotFormat(0), m_colorConvert(0),
|
|
m_tvfd(0),
|
|
m_buffers(NULL), m_nbuffers(0),
|
|
m_currentBuffer(NULL),
|
|
m_frame(0), m_last_frame(0),
|
|
m_maxwidth(844), m_minwidth(32),
|
|
m_maxheight(650), m_minheight(32),
|
|
m_thread_id(0), m_continue_thread(false), m_frame_ready(false),
|
|
m_rendering(false),
|
|
m_stopTransfer(false),
|
|
m_frameSize(0)
|
|
{
|
|
if (!m_width)m_width=320;
|
|
if (!m_height)m_height=240;
|
|
m_capturing=false;
|
|
m_devicenum=V4L2_DEVICENO;
|
|
|
|
provide("analog");
|
|
}
|
|
|
|
////////////////////////////////////////////////////////
|
|
// Destructor
|
|
//
|
|
////////////////////////////////////////////////////////
|
|
videoV4L2 :: ~videoV4L2()
|
|
{
|
|
close();
|
|
}
|
|
|
|
static int xioctl(int fd,
|
|
int request,
|
|
void * arg)
|
|
{
|
|
int r;
|
|
const unsigned char req=(request&0xFF);
|
|
|
|
debugIOCTL("V4L2: xioctl %d\n", req);
|
|
do {
|
|
r = v4l2_ioctl (fd, request, arg);
|
|
debugIOCTL("V4L2: xioctl %d->%d\n", r, errno);
|
|
}
|
|
while (-1 == r && EINTR == errno);
|
|
|
|
debugIOCTL("V4L2: xioctl done %d\n", r);
|
|
#if 0
|
|
if(r!=0) {
|
|
char buf[13];
|
|
snprintf(buf, 13, "xioctl[%03d]:", req);
|
|
buf[12]=0;
|
|
perror(buf);
|
|
}
|
|
#endif
|
|
return r;
|
|
}
|
|
|
|
static int reqbufs(int fd, unsigned int numbufs) {
|
|
struct v4l2_requestbuffers req;
|
|
memset (&(req), 0, sizeof (req));
|
|
|
|
req.count = numbufs;
|
|
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
req.memory = V4L2_MEMORY_MMAP;
|
|
|
|
if (-1 == xioctl (fd, VIDIOC_REQBUFS, &req)) {
|
|
return -1;
|
|
}
|
|
|
|
return req.count;
|
|
}
|
|
|
|
int videoV4L2::init_mmap (void)
|
|
{
|
|
const char *devname=(m_devicename.empty())?"device":m_devicename.c_str();
|
|
int count = reqbufs(m_tvfd, V4L2_NBUF);
|
|
|
|
if(count<0) {
|
|
if (EINVAL == errno) {
|
|
error("%s does not support memory mapping", devname);
|
|
return 0;
|
|
} else {
|
|
perror("v4l2: VIDIOC_REQBUFS");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (count < V4L2_NBUF) {
|
|
//error("Insufficient buffer memory on %s: %d", devname, count);
|
|
//return(0);
|
|
}
|
|
|
|
m_buffers = (t_v4l2_buffer*)calloc (count, sizeof (*m_buffers));
|
|
|
|
if (!m_buffers) {
|
|
perror("v4l2: out of memory");
|
|
return(0);
|
|
}
|
|
|
|
for (m_nbuffers = 0; m_nbuffers < count; ++m_nbuffers) {
|
|
struct v4l2_buffer buf;
|
|
|
|
memset (&(buf), 0, sizeof (buf));
|
|
|
|
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
buf.memory = V4L2_MEMORY_MMAP;
|
|
buf.index = m_nbuffers;
|
|
debugPost("v4l2: buf.index==%d", buf.index);
|
|
|
|
if (-1 == xioctl (m_tvfd, VIDIOC_QUERYBUF, &buf)){
|
|
perror("v4l2: VIDIOC_QUERYBUF");
|
|
return(0);
|
|
}
|
|
|
|
m_buffers[m_nbuffers].length = buf.length;
|
|
m_buffers[m_nbuffers].start =
|
|
v4l2_mmap (NULL /* start anywhere */,
|
|
buf.length,
|
|
PROT_READ | PROT_WRITE /* required */,
|
|
MAP_SHARED /* recommended */,
|
|
m_tvfd, buf.m.offset);
|
|
|
|
if (MAP_FAILED == m_buffers[m_nbuffers].start){
|
|
perror("v4l2: mmap");
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////
|
|
// this is the work-horse
|
|
// a thread that does the capturing
|
|
//
|
|
/////////////////////////////////////////////////////////
|
|
void *videoV4L2::capturing_(void*you)
|
|
{
|
|
videoV4L2 *me=(videoV4L2 *)you;
|
|
return me->capturing();
|
|
}
|
|
void *videoV4L2 :: capturing(void)
|
|
{
|
|
int errorcount=0;
|
|
|
|
t_v4l2_buffer*buffers=m_buffers;
|
|
void *currentBuffer=NULL;
|
|
|
|
const __u32 expectedSize=m_frameSize;
|
|
__u32 gotSize=0;
|
|
|
|
|
|
struct v4l2_buffer buf;
|
|
int nbuf=m_nbuffers;
|
|
|
|
fd_set fds;
|
|
struct timeval tv;
|
|
int r;
|
|
|
|
unsigned int i;
|
|
|
|
|
|
m_capturing=true;
|
|
|
|
debugThread("V4L2: memset");
|
|
memset(&(buf), 0, sizeof (buf));
|
|
|
|
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
buf.memory = V4L2_MEMORY_MMAP;
|
|
|
|
while(m_continue_thread){
|
|
bool captureerror=false;
|
|
FD_ZERO (&fds);
|
|
FD_SET (m_tvfd, &fds);
|
|
|
|
debugThread("V4L2: grab");
|
|
|
|
m_frame++;
|
|
m_frame%=nbuf;
|
|
|
|
|
|
/* Timeout. */
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 100;
|
|
r = select(0,0,0,0,&tv);
|
|
debugThread("V4L2: waited...");
|
|
|
|
|
|
if (-1 == r) {
|
|
if (EINTR == errno)
|
|
continue;
|
|
perror("v4l2: select");//exit
|
|
}
|
|
|
|
memset(&(buf), 0, sizeof (buf));
|
|
debugThread("V4L2: memset...");
|
|
|
|
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
buf.memory = V4L2_MEMORY_MMAP;
|
|
|
|
if (-1 == xioctl (m_tvfd, VIDIOC_DQBUF, &buf)) {
|
|
switch (errno) {
|
|
case EAGAIN:
|
|
perror("v4l2: VIDIOC_DQBUF: stopping capture thread!");
|
|
m_stopTransfer=true;
|
|
m_continue_thread=false;
|
|
case EIO:
|
|
/* Could ignore EIO, see spec. */
|
|
/* fall through */
|
|
default:
|
|
captureerror=true;
|
|
perror("v4l2: VIDIOC_DQBUF");
|
|
}
|
|
}
|
|
|
|
debugThread("V4L2: grabbed %d", buf.index);
|
|
|
|
gotSize=buf.bytesused;
|
|
currentBuffer=buffers[buf.index].start;
|
|
//process_image (m_buffers[buf.index].start);
|
|
|
|
if (-1 == xioctl (m_tvfd, VIDIOC_QBUF, &buf)){
|
|
perror("v4l2: VIDIOC_QBUF");
|
|
captureerror=true;
|
|
}
|
|
|
|
debugThread("V4L2: dequeueued");
|
|
|
|
if(expectedSize==gotSize) {
|
|
m_frame_ready = 1;
|
|
m_last_frame=m_frame;
|
|
m_currentBuffer=currentBuffer;
|
|
} else {
|
|
post("oops, skipping incomplete capture %d of %d bytes", gotSize, expectedSize);
|
|
}
|
|
|
|
if(captureerror) {
|
|
errorcount++;
|
|
if(errorcount>1000) {
|
|
error("v4L2: %d capture errors in a row... I think I better stop now...", errorcount);
|
|
m_continue_thread=false;
|
|
m_stopTransfer=true;
|
|
}
|
|
} else {
|
|
errorcount=0;
|
|
}
|
|
}
|
|
|
|
// stop capturing
|
|
m_capturing=false;
|
|
debugThread("V4L2: thread finished");
|
|
return NULL;
|
|
}
|
|
|
|
//////////////////
|
|
// this reads the data that was captured by capturing() and returns it within a pixBlock
|
|
pixBlock *videoV4L2 :: getFrame(){
|
|
if(!(m_haveVideo && m_capturing))return NULL;
|
|
if(m_stopTransfer) {
|
|
bool rendering=m_rendering;
|
|
stopTransfer();
|
|
m_rendering=rendering;
|
|
return NULL;
|
|
}
|
|
//debugPost("v4l2: getting frame %d", m_frame_ready);
|
|
m_image.newfilm=0;
|
|
if (!m_frame_ready) m_image.newimage = 0;
|
|
else {
|
|
unsigned char*data=(unsigned char*)m_currentBuffer;
|
|
if (m_colorConvert){
|
|
m_image.image.notowned = false;
|
|
switch(m_gotFormat){
|
|
#if 1
|
|
# define CONVERT(type) m_image.image.from##type (data)
|
|
#else
|
|
# define CONVERT(type) post("from " #type "!");m_image.image.from##type (data)
|
|
#endif
|
|
case V4L2_PIX_FMT_RGB24: CONVERT(RGB ); break;
|
|
case V4L2_PIX_FMT_BGR32: CONVERT(BGRA ); break;
|
|
case V4L2_PIX_FMT_RGB32: CONVERT(ARGB ); break;
|
|
case V4L2_PIX_FMT_GREY : CONVERT(Gray ); break;
|
|
case V4L2_PIX_FMT_UYVY : CONVERT(YUV422); break;
|
|
case V4L2_PIX_FMT_YUYV : CONVERT(YUY2 ); break;
|
|
case V4L2_PIX_FMT_YUV420:CONVERT(YU12 ); break;
|
|
|
|
|
|
default: // ? what should we do ?
|
|
m_image.image.data=data;
|
|
m_image.image.notowned = true;
|
|
}
|
|
} else {
|
|
m_image.image.data=data;
|
|
m_image.image.notowned = true;
|
|
}
|
|
m_image.image.upsidedown=true;
|
|
|
|
m_image.newimage = 1;
|
|
m_frame_ready = false;
|
|
}
|
|
return &m_image;
|
|
}
|
|
|
|
bool videoV4L2 :: openDevice(gem::Properties&props) {
|
|
close();
|
|
|
|
/* check the device */
|
|
// if we don't have a devicename, create one
|
|
std::string devname = m_devicename;
|
|
|
|
|
|
if(devname.empty()) {
|
|
devname="/dev/video";
|
|
if(m_devicenum>=0) {
|
|
char buf[255];
|
|
snprintf(buf, 255, "%d", m_devicenum);
|
|
buf[255]=0;
|
|
devname+=buf;
|
|
}
|
|
}
|
|
|
|
|
|
if (devname.at(0) != '/'){ // assuming all v4l2 device's paths starts with '/'
|
|
|
|
std::vector<std::string> alldev = enumerate();
|
|
int i;
|
|
for(i=0; i<alldev.size(); i++) {
|
|
std::string dev=alldev[i];
|
|
verbose(2, "V4L2: found possible device %s", dev.c_str());
|
|
int fd=v4l2_open(dev.c_str(), O_RDWR);
|
|
verbose(2, "V4L2: v4l2_open returned %d", fd);
|
|
if(fd<0)continue;
|
|
struct v4l2_capability cap;
|
|
if (-1 != xioctl (fd, VIDIOC_QUERYCAP, &cap)) {
|
|
|
|
std::string bus;
|
|
bus = (char*) cap.bus_info;
|
|
if ( devname == bus ) {
|
|
devname = dev;
|
|
break;
|
|
}
|
|
} else {
|
|
verbose(1, "v4l2 : %s is no v4l2 device\n", dev.c_str());
|
|
}
|
|
v4l2_close(fd);
|
|
}
|
|
if ( i >= alldev.size() ) {
|
|
error("v4l2 : no v4l2 input device on bus %s\n", devname.c_str());
|
|
devname = "";
|
|
}
|
|
}
|
|
|
|
|
|
const char*dev_name=devname.c_str();
|
|
debugPost("v4l2: device: %s", dev_name);
|
|
|
|
// try to open the device
|
|
m_tvfd = v4l2_open (dev_name, O_RDWR /* required */, 0);
|
|
|
|
if (-1 == m_tvfd) {
|
|
error("Cannot open '%s': %d, %s", dev_name, errno, strerror (errno));
|
|
closeDevice(); return false;
|
|
}
|
|
|
|
struct stat st;
|
|
if (-1 == fstat (m_tvfd, &st)) {
|
|
error("Cannot identify '%s': %d, %s", dev_name, errno, strerror (errno));
|
|
closeDevice(); return false;
|
|
}
|
|
|
|
if (!S_ISCHR (st.st_mode)) {
|
|
error("%s is no device", dev_name);
|
|
closeDevice(); return false;
|
|
}
|
|
|
|
// by now, we have an open file-descriptor
|
|
// check whether this is really a v4l2-device
|
|
struct v4l2_capability cap;
|
|
if (-1 == xioctl (m_tvfd, VIDIOC_QUERYCAP, &cap)) {
|
|
if (EINVAL == errno) {
|
|
error("%s is no V4L2 device", dev_name);
|
|
closeDevice(); return false;
|
|
} else {
|
|
perror("v4l2: VIDIOC_QUERYCAP");//exit
|
|
closeDevice(); return false;
|
|
}
|
|
}
|
|
|
|
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
|
|
error("%s is no video capture device", dev_name);
|
|
closeDevice(); return false;
|
|
}
|
|
|
|
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
|
|
error("%s does not support streaming i/o", dev_name);
|
|
closeDevice(); return false;
|
|
}
|
|
|
|
verbose(1, "v4l2: successfully opened %s", dev_name);
|
|
|
|
setProperties(props);
|
|
|
|
return true;
|
|
}
|
|
void videoV4L2 :: closeDevice() {
|
|
verbose(1, "v4l: closing device %d", m_tvfd);
|
|
if (m_tvfd>=0) v4l2_close(m_tvfd);
|
|
m_tvfd=-1;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////
|
|
// restartTransfer
|
|
//
|
|
/////////////////////////////////////////////////////////
|
|
bool videoV4L2 :: restartTransfer()
|
|
{
|
|
bool rendering=m_rendering;
|
|
debugPost("v4l2: restart transfer");
|
|
if(m_capturing)stopTransfer();
|
|
debugPost("v4l2: restart stopped");
|
|
if (rendering)startTransfer();
|
|
debugPost("v4l2: restart started");
|
|
|
|
return true;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////
|
|
// startTransfer
|
|
//
|
|
/////////////////////////////////////////////////////////
|
|
bool videoV4L2 :: startTransfer()
|
|
{
|
|
if(m_tvfd<0)return false;
|
|
debugPost("v4l2: startTransfer: %d", m_capturing);
|
|
if(m_capturing)stopTransfer(); // just in case we are already running!
|
|
debugPost("v4l2: start transfer");
|
|
m_stopTransfer=false;
|
|
m_rendering=true;
|
|
// verbose(1, "starting transfer");
|
|
int i;
|
|
|
|
__u32 pixelformat=0;
|
|
struct v4l2_format fmt;
|
|
|
|
enum v4l2_buf_type type;
|
|
|
|
m_frame = 0;
|
|
m_last_frame = 0;
|
|
|
|
/* Select video format */
|
|
memset (&(fmt), 0, sizeof (fmt));
|
|
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
|
|
/* initialize the format struct to what we currently have */
|
|
if (-1 == xioctl (m_tvfd, VIDIOC_G_FMT, &fmt)){
|
|
perror("v4l2: VIDIOC_G_FMT");//exit
|
|
}
|
|
|
|
switch(m_reqFormat){
|
|
case GL_YCBCR_422_GEM:
|
|
pixelformat = V4L2_PIX_FMT_UYVY;
|
|
break;
|
|
case GL_LUMINANCE:
|
|
pixelformat = V4L2_PIX_FMT_GREY;
|
|
break;
|
|
case GL_RGB:
|
|
pixelformat = V4L2_PIX_FMT_RGB24;
|
|
break;
|
|
default:
|
|
pixelformat = V4L2_PIX_FMT_RGB32;
|
|
m_reqFormat=GL_RGBA;
|
|
break;
|
|
}
|
|
|
|
if(fmt.fmt.pix.pixelformat != pixelformat) {
|
|
fmt.fmt.pix.pixelformat = pixelformat;
|
|
|
|
verbose(1, "v4l2: want 0x%X == '%c%c%c%c' ", m_reqFormat,
|
|
(char)(fmt.fmt.pix.pixelformat),
|
|
(char)(fmt.fmt.pix.pixelformat>>8),
|
|
(char)(fmt.fmt.pix.pixelformat>>16),
|
|
(char)(fmt.fmt.pix.pixelformat>>24));
|
|
|
|
if (-1 == xioctl (m_tvfd, VIDIOC_S_FMT, &fmt)){
|
|
perror("v4l2: VIDIOC_S_FMT(fmt)");//exit
|
|
}
|
|
|
|
// query back what we have set
|
|
/* in theory this should not be needed,
|
|
* as S_FMT is supposed to return the actual data
|
|
* however, some buggy drivers seem to not do that,
|
|
* so we have to make sure...
|
|
*/
|
|
if (-1 == xioctl (m_tvfd, VIDIOC_G_FMT, &fmt)){
|
|
perror("v4l2: VIDIOC_G_FMT");//exit
|
|
}
|
|
}
|
|
|
|
m_gotFormat=fmt.fmt.pix.pixelformat;
|
|
switch(m_gotFormat){
|
|
case V4L2_PIX_FMT_RGB32: debugPost("v4l2: ARGB");break;
|
|
case V4L2_PIX_FMT_RGB24: debugPost("v4l2: RGB");break;
|
|
case V4L2_PIX_FMT_UYVY: debugPost("v4l2: YUV ");break;
|
|
case V4L2_PIX_FMT_GREY: debugPost("v4l2: gray");break;
|
|
case V4L2_PIX_FMT_YUV420:debugPost("v4l2: YUV 4:2:0");break;
|
|
default:
|
|
/* hmm, we don't know how to handle this
|
|
* let's try formats that should be always supported by libv4l2
|
|
*/
|
|
switch(m_reqFormat){
|
|
case GL_YCBCR_422_GEM:
|
|
case GL_LUMINANCE:
|
|
pixelformat = V4L2_PIX_FMT_YUV420;
|
|
break;
|
|
default:
|
|
pixelformat = V4L2_PIX_FMT_RGB24;
|
|
break;
|
|
}
|
|
fmt.fmt.pix.pixelformat = pixelformat;
|
|
|
|
if (-1 == xioctl (m_tvfd, VIDIOC_S_FMT, &fmt)){
|
|
perror("v4l2: VIDIOC_S_FMT(fmt2)");
|
|
}
|
|
// query back what we have set
|
|
if (-1 == xioctl (m_tvfd, VIDIOC_G_FMT, &fmt)){
|
|
perror("v4l2: VIDIOC_G_FMT(fmt2)");
|
|
}
|
|
m_gotFormat=fmt.fmt.pix.pixelformat;
|
|
}
|
|
|
|
switch(m_gotFormat){
|
|
case V4L2_PIX_FMT_RGB32: case V4L2_PIX_FMT_RGB24:
|
|
case V4L2_PIX_FMT_UYVY: case V4L2_PIX_FMT_YUV420: case V4L2_PIX_FMT_YUYV:
|
|
case V4L2_PIX_FMT_GREY:
|
|
break;
|
|
default:
|
|
error("unknown format '%c%c%c%c'",
|
|
(char)(m_gotFormat),
|
|
(char)(m_gotFormat>>8),
|
|
(char)(m_gotFormat>>16),
|
|
(char)(m_gotFormat>>24));
|
|
/* we should really return here! */
|
|
}
|
|
|
|
verbose(1, "v4l2: got '%c%c%c%c'",
|
|
(char)(m_gotFormat),
|
|
(char)(m_gotFormat>>8),
|
|
(char)(m_gotFormat>>16),
|
|
(char)(m_gotFormat>>24));
|
|
|
|
if(!init_mmap ())goto closit;
|
|
|
|
for (i = 0; i < m_nbuffers; ++i) {
|
|
struct v4l2_buffer buf;
|
|
|
|
memset (&(buf), 0, sizeof (buf));
|
|
|
|
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
buf.memory = V4L2_MEMORY_MMAP;
|
|
buf.index = i;
|
|
|
|
if (-1 == xioctl (m_tvfd, VIDIOC_QBUF, &buf)){
|
|
perror("v4l2: VIDIOC_QBUF");//exit
|
|
}
|
|
}
|
|
|
|
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
if (-1 == xioctl (m_tvfd, VIDIOC_STREAMON, &type)){
|
|
perror("v4l2: VIDIOC_STREAMON");//exit
|
|
}
|
|
|
|
m_frameSize=fmt.fmt.pix.sizeimage;
|
|
|
|
/* fill in image specifics for Gem pixel object. Could we have
|
|
just used RGB, I wonder? */
|
|
m_image.image.xsize = fmt.fmt.pix.width;
|
|
m_image.image.ysize = fmt.fmt.pix.height;
|
|
m_image.image.setCsizeByFormat(m_reqFormat);
|
|
m_image.image.reallocate();
|
|
|
|
debugPost("v4l2: format: %c%c%c%c -> 0x%X",
|
|
(char)(m_gotFormat),
|
|
(char)(m_gotFormat>>8),
|
|
(char)(m_gotFormat>>16),
|
|
(char)(m_gotFormat>>24),
|
|
m_reqFormat);
|
|
switch(m_gotFormat){
|
|
case V4L2_PIX_FMT_GREY : m_colorConvert=(m_reqFormat!=GL_LUMINANCE); break;
|
|
case V4L2_PIX_FMT_RGB24 : m_colorConvert=(m_reqFormat!=GL_BGR); break;
|
|
case V4L2_PIX_FMT_RGB32 : m_colorConvert=true; break;
|
|
case V4L2_PIX_FMT_UYVY : m_colorConvert=(m_reqFormat!=GL_YCBCR_422_GEM); break;
|
|
case V4L2_PIX_FMT_YUV420: m_colorConvert=1; break;
|
|
default: m_colorConvert=true;
|
|
}
|
|
|
|
debugPost("v4l2: colorconvert=%d", m_colorConvert);
|
|
|
|
/* create thread */
|
|
m_continue_thread = 1;
|
|
m_frame_ready = 0;
|
|
pthread_create(&m_thread_id, 0, capturing_, this);
|
|
while(!m_capturing){
|
|
usleep(10);
|
|
debugPost("v4l2: waiting for thread to come up");
|
|
}
|
|
|
|
post("v4l2: GEM: pix_video: Opened video connection 0x%X", m_tvfd);
|
|
|
|
return(1);
|
|
|
|
closit:
|
|
debugPost("v4l2: closing it!");
|
|
stopTransfer();
|
|
debugPost("v4l2: closed it");
|
|
return(0);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////
|
|
// stopTransfer
|
|
//
|
|
/////////////////////////////////////////////////////////
|
|
bool videoV4L2 :: stopTransfer()
|
|
{
|
|
debugPost("v4l2: stoptransfer");
|
|
if(!m_capturing)return false;
|
|
int i=0;
|
|
/* close the v4l2 device and dealloc buffer */
|
|
/* terminate thread if there is one */
|
|
if(m_continue_thread){
|
|
void *dummy;
|
|
m_continue_thread = 0;
|
|
pthread_join (m_thread_id, &dummy);
|
|
}
|
|
while(m_capturing){
|
|
usleep(10);
|
|
debugPost("v4l2: waiting for thread to finish");
|
|
}
|
|
|
|
// unmap the mmap
|
|
debugPost("v4l2: unmapping %d buffers: %x", m_nbuffers, m_buffers);
|
|
if(m_buffers){
|
|
for (i = 0; i < m_nbuffers; ++i)
|
|
if (-1 == v4l2_munmap (m_buffers[i].start, m_buffers[i].length)){
|
|
// oops: couldn't unmap the memory
|
|
}
|
|
debugPost("v4l2: freeing buffers: %x", m_buffers);
|
|
free (m_buffers);
|
|
}
|
|
m_buffers=NULL;
|
|
debugPost("v4l2: freed");
|
|
|
|
// stop streaming
|
|
if(m_tvfd){
|
|
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
if (-1 == xioctl (m_tvfd, VIDIOC_STREAMOFF, &type)){
|
|
perror("v4l2: VIDIOC_STREAMOFF");
|
|
}
|
|
}
|
|
|
|
debugPost("v4l2: de-requesting buffers");
|
|
reqbufs(m_tvfd, 0);
|
|
|
|
m_frame_ready = 0;
|
|
m_rendering=false;
|
|
debugPost("v4l2: stoppedTransfer");
|
|
return true;
|
|
}
|
|
|
|
bool videoV4L2 :: setColor(int format)
|
|
{
|
|
if (format<=0 || format==m_reqFormat)return -1;
|
|
m_reqFormat=format;
|
|
restartTransfer();
|
|
return true;
|
|
}
|
|
|
|
std::vector<std::string> videoV4L2::enumerate() {
|
|
std::vector<std::string> result;
|
|
std::vector<std::string> glob, allglob;
|
|
int i=0;
|
|
glob=gem::files::getFilenameListing("/dev/video*");
|
|
for(i=0; i<glob.size(); i++)
|
|
allglob.push_back(glob[i]);
|
|
|
|
glob=gem::files::getFilenameListing("/dev/v4l/*");
|
|
for(i=0; i<glob.size(); i++)
|
|
allglob.push_back(glob[i]);
|
|
|
|
for(i=0; i<allglob.size(); i++) {
|
|
std::string dev=allglob[i];
|
|
verbose(2, "V4L2: found possible device %s", dev.c_str());
|
|
int fd=v4l2_open(dev.c_str(), O_RDWR);
|
|
verbose(2, "V4L2: v4l2_open returned %d", fd);
|
|
if(fd<0)continue;
|
|
struct v4l2_capability cap;
|
|
memset (&cap, 0, sizeof (cap));
|
|
if (-1 != xioctl (fd, VIDIOC_QUERYCAP, &cap)) {
|
|
if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
|
|
result.push_back(dev);
|
|
} else verbose(1, "%s is v4l2 but cannot capture", dev.c_str());
|
|
} else {
|
|
verbose(1, "%s is no v4l2 device", dev.c_str());
|
|
}
|
|
v4l2_close(fd);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void videoV4L2::addProperties(struct v4l2_queryctrl queryctrl,
|
|
gem::Properties&readable,
|
|
gem::Properties&writeable) {
|
|
const char* name=NULL;
|
|
gem::any typ;
|
|
|
|
if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)
|
|
return;
|
|
|
|
switch(queryctrl.type) {
|
|
case V4L2_CTRL_TYPE_BUTTON:
|
|
break;
|
|
case V4L2_CTRL_TYPE_BOOLEAN:
|
|
typ=1;
|
|
break;
|
|
case V4L2_CTRL_TYPE_MENU:
|
|
typ=queryctrl.maximum;
|
|
break;
|
|
case V4L2_CTRL_TYPE_INTEGER:
|
|
typ=queryctrl.maximum;
|
|
break;
|
|
case V4L2_CTRL_TYPE_INTEGER64:
|
|
typ=0;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
name=(const char*)(queryctrl.name);
|
|
|
|
m_readprops[name]=queryctrl;
|
|
readable.set(name, typ);
|
|
|
|
if (!(queryctrl.flags & V4L2_CTRL_FLAG_READ_ONLY)) {
|
|
m_writeprops[name]=queryctrl;
|
|
writeable.set(name, typ);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
bool videoV4L2 :: enumProperties(gem::Properties&readable,
|
|
gem::Properties&writeable) {
|
|
struct v4l2_queryctrl queryctrl;
|
|
__u32 id=0;
|
|
std::string dummy_s;
|
|
|
|
if(m_tvfd<0)
|
|
return false;
|
|
|
|
readable.clear();
|
|
writeable.clear();
|
|
|
|
m_readprops.clear();
|
|
m_writeprops.clear();
|
|
|
|
memset (&queryctrl, 0, sizeof (queryctrl));
|
|
|
|
for (id = V4L2_CID_BASE;
|
|
id < V4L2_CID_LASTP1;
|
|
id++) {
|
|
queryctrl.id = id;
|
|
if (0 == xioctl (m_tvfd, VIDIOC_QUERYCTRL, &queryctrl)) {
|
|
addProperties(queryctrl, readable, writeable);
|
|
|
|
} else {
|
|
if (errno == EINVAL)
|
|
continue;
|
|
}
|
|
}
|
|
|
|
for (id = V4L2_CID_PRIVATE_BASE;;
|
|
id++) {
|
|
queryctrl.id = id;
|
|
if (0 == xioctl (m_tvfd, VIDIOC_QUERYCTRL, &queryctrl)) {
|
|
addProperties(queryctrl, readable, writeable);
|
|
} else {
|
|
if (errno == EINVAL)
|
|
break;
|
|
}
|
|
}
|
|
|
|
readable.set("channel",0);
|
|
readable.set("frequency",0);
|
|
readable.set("norm", dummy_s);
|
|
readable.set("width",0);
|
|
readable.set("height",0);
|
|
|
|
writeable.set("channel",0);
|
|
writeable.set("frequency",0);
|
|
writeable.set("norm", dummy_s);
|
|
writeable.set("width",0);
|
|
writeable.set("height",0);
|
|
|
|
if (-1 != xioctl (m_tvfd, VIDIOC_QUERYCAP, &m_caps)) {
|
|
readable.set("driver", (char*) m_caps.driver);
|
|
readable.set("card", (char*) m_caps.card);
|
|
readable.set("bus_info", (char*) m_caps.bus_info);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
void videoV4L2 :: getProperties(gem::Properties&props) {
|
|
int getformat=0;
|
|
|
|
if(m_tvfd<0) {
|
|
props.clear();
|
|
return;
|
|
}
|
|
std::vector<std::string>keys=props.keys();
|
|
int i=0;
|
|
for(i=0; i<keys.size(); i++) {
|
|
std::string key=keys[i];
|
|
std::map<std::string, struct v4l2_queryctrl>::iterator it = m_readprops.find(key);
|
|
if(it != m_readprops.end()) {
|
|
struct v4l2_queryctrl qc=it->second;
|
|
struct v4l2_control vc;
|
|
memset (&vc, 0, sizeof (vc));
|
|
vc.id=qc.id;
|
|
vc.value=0;
|
|
|
|
int err=xioctl(m_tvfd, VIDIOC_G_CTRL, &vc);
|
|
if(0==err) {
|
|
props.set(key, vc.value);
|
|
} else {
|
|
props.erase(key);
|
|
}
|
|
} else {
|
|
if("norm" == key) {
|
|
v4l2_std_id stdid = 0;
|
|
if(0==xioctl(m_tvfd, VIDIOC_G_STD, &stdid)) {
|
|
std::string std;
|
|
switch(stdid) {
|
|
default:
|
|
case V4L2_STD_UNKNOWN: std="UNKNOWN"; break;
|
|
case V4L2_STD_ALL: std="ALL"; break;
|
|
|
|
case V4L2_STD_ATSC: std="ATSC"; break;
|
|
case V4L2_STD_625_50: std="625_50"; break;
|
|
case V4L2_STD_525_60: std="525_60"; break;
|
|
case V4L2_STD_SECAM: std="SECAM"; break;
|
|
case V4L2_STD_SECAM_DK: std="SECAM_DK"; break;
|
|
case V4L2_STD_NTSC: std="NTSC"; break;
|
|
case V4L2_STD_PAL: std="PAL"; break;
|
|
case V4L2_STD_PAL_DK: std="PAL_DK"; break;
|
|
case V4L2_STD_PAL_BG: std="PAL_BG"; break;
|
|
case V4L2_STD_DK: std="DK"; break;
|
|
case V4L2_STD_GH: std="GH"; break;
|
|
case V4L2_STD_B: std="B"; break;
|
|
case V4L2_STD_MN: std="MN"; break;
|
|
case V4L2_STD_ATSC_16_VSB: std="ATSC_16_VSB"; break;
|
|
case V4L2_STD_ATSC_8_VSB: std="ATSC_8_VSB"; break;
|
|
case V4L2_STD_SECAM_LC: std="SECAM_LC"; break;
|
|
case V4L2_STD_SECAM_L: std="SECAM_L"; break;
|
|
case V4L2_STD_SECAM_K1: std="SECAM_K1"; break;
|
|
case V4L2_STD_SECAM_K: std="SECAM_K"; break;
|
|
case V4L2_STD_SECAM_H: std="SECAM_H"; break;
|
|
case V4L2_STD_SECAM_G: std="SECAM_G"; break;
|
|
case V4L2_STD_SECAM_D: std="SECAM_D"; break;
|
|
case V4L2_STD_SECAM_B: std="SECAM_B"; break;
|
|
case V4L2_STD_NTSC_M_KR: std="NTSC_M_KR"; break;
|
|
case V4L2_STD_NTSC_443: std="NTSC_443"; break;
|
|
case V4L2_STD_NTSC_M_JP: std="NTSC_M_JP"; break;
|
|
case V4L2_STD_NTSC_M: std="NTSC_M"; break;
|
|
case V4L2_STD_PAL_60: std="PAL_60"; break;
|
|
case V4L2_STD_PAL_Nc: std="PAL_Nc"; break;
|
|
case V4L2_STD_PAL_N: std="PAL_N"; break;
|
|
case V4L2_STD_PAL_M: std="PAL_M"; break;
|
|
case V4L2_STD_PAL_K: std="PAL_K"; break;
|
|
case V4L2_STD_PAL_D1: std="PAL_D1"; break;
|
|
case V4L2_STD_PAL_D: std="PAL_D"; break;
|
|
case V4L2_STD_PAL_I: std="PAL_I"; break;
|
|
case V4L2_STD_PAL_H: std="PAL_H"; break;
|
|
case V4L2_STD_PAL_G: std="PAL_G"; break;
|
|
case V4L2_STD_PAL_B1: std="PAL_B1"; break;
|
|
case V4L2_STD_PAL_B: std="PAL_B"; break;
|
|
}
|
|
props.set("norm", std);
|
|
}
|
|
} else if("channel" == key) {
|
|
int channel=0;
|
|
if(0==xioctl(m_tvfd, VIDIOC_G_INPUT, &channel))
|
|
props.set("channel", channel);
|
|
} else if("frequency" == key) {
|
|
struct v4l2_frequency freq;
|
|
memset (&(freq), 0, sizeof (freq));
|
|
freq.tuner=0; /* FIXXME: this should be a bit more intelligent... */
|
|
|
|
if(0==xioctl(m_tvfd, VIDIOC_G_FREQUENCY, &freq)) {
|
|
props.set("frequency", freq.frequency);
|
|
}
|
|
} else if("width" == key) {
|
|
getformat|=WIDTH_FLAG;
|
|
} else if("height" == key) {
|
|
getformat|=HEIGHT_FLAG;
|
|
} else if("driver" == key) {
|
|
props.set("driver", (char*) m_caps.driver);
|
|
} else if("card" == key) {
|
|
props.set("card", (char*) m_caps.card);
|
|
} else if("bus_info" == key) {
|
|
props.set("bus_info", (char*) m_caps.bus_info);
|
|
} else {
|
|
}
|
|
}
|
|
}
|
|
|
|
if(getformat) {
|
|
struct v4l2_format fmt;
|
|
memset (&(fmt), 0, sizeof (fmt));
|
|
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
|
|
if (0 == xioctl (m_tvfd, VIDIOC_G_FMT, &fmt)) {
|
|
if(getformat & WIDTH_FLAG)
|
|
props.set("width", fmt.fmt.pix.width);
|
|
if(getformat & HEIGHT_FLAG)
|
|
props.set("height", fmt.fmt.pix.height);
|
|
} else {
|
|
perror("VIDIOC_G_FMT");
|
|
}
|
|
}
|
|
}
|
|
void videoV4L2 :: setProperties(gem::Properties&props) {
|
|
if(m_tvfd<0) {
|
|
return;
|
|
}
|
|
bool restart=false;
|
|
|
|
bool setformat=false, setcropping=false;
|
|
|
|
|
|
std::vector<std::string>keys=props.keys();
|
|
int i=0;
|
|
for(i=0; i<keys.size(); i++) {
|
|
std::string key=keys[i];
|
|
std::map<std::string, struct v4l2_queryctrl>::iterator it = m_writeprops.find(key);
|
|
if(it != m_writeprops.end()) {
|
|
double d=0;
|
|
struct v4l2_queryctrl qc=it->second;
|
|
struct v4l2_control vc;
|
|
memset (&vc, 0, sizeof (vc));
|
|
vc.id=qc.id;
|
|
if(props.get(key, d)) {
|
|
vc.value=d;
|
|
}
|
|
|
|
int err=xioctl(m_tvfd, VIDIOC_S_CTRL, &vc);
|
|
|
|
if(V4L2_CTRL_TYPE_BUTTON == qc.type) {
|
|
props.erase(key);
|
|
}
|
|
} else {
|
|
if("norm" == key) {
|
|
v4l2_std_id stdid = V4L2_STD_UNKNOWN;
|
|
std::string std;
|
|
switch(props.type(key)) {
|
|
case gem::Properties::STRING:
|
|
if(props.get(key, std)) {
|
|
if("ALL" == std) stdid=V4L2_STD_ALL;
|
|
else if("PAL_B" == std) stdid=V4L2_STD_PAL_B;
|
|
else if("PAL_B1" == std) stdid=V4L2_STD_PAL_B1;
|
|
else if("PAL_G" == std) stdid=V4L2_STD_PAL_G;
|
|
else if("PAL_H" == std) stdid=V4L2_STD_PAL_H;
|
|
else if("PAL_I" == std) stdid=V4L2_STD_PAL_I;
|
|
else if("PAL_D" == std) stdid=V4L2_STD_PAL_D;
|
|
else if("PAL_D1" == std) stdid=V4L2_STD_PAL_D1;
|
|
else if("PAL_K" == std) stdid=V4L2_STD_PAL_K;
|
|
else if("PAL_M" == std) stdid=V4L2_STD_PAL_M;
|
|
else if("PAL_N" == std) stdid=V4L2_STD_PAL_N;
|
|
else if("PAL_Nc" == std) stdid=V4L2_STD_PAL_Nc;
|
|
else if("PAL_60" == std) stdid=V4L2_STD_PAL_60;
|
|
else if("NTSC_M" == std) stdid=V4L2_STD_NTSC_M;
|
|
else if("NTSC_M_JP" == std) stdid=V4L2_STD_NTSC_M_JP;
|
|
else if("NTSC_443" == std) stdid=V4L2_STD_NTSC_443;
|
|
else if("NTSC_M_KR" == std) stdid=V4L2_STD_NTSC_M_KR;
|
|
else if("SECAM_B" == std) stdid=V4L2_STD_SECAM_B;
|
|
else if("SECAM_D" == std) stdid=V4L2_STD_SECAM_D;
|
|
else if("SECAM_G" == std) stdid=V4L2_STD_SECAM_G;
|
|
else if("SECAM_H" == std) stdid=V4L2_STD_SECAM_H;
|
|
else if("SECAM_K" == std) stdid=V4L2_STD_SECAM_K;
|
|
else if("SECAM_K1" == std) stdid=V4L2_STD_SECAM_K1;
|
|
else if("SECAM_L" == std) stdid=V4L2_STD_SECAM_L;
|
|
else if("SECAM_LC" == std) stdid=V4L2_STD_SECAM_LC;
|
|
else if("ATSC_8_VSB" == std) stdid=V4L2_STD_ATSC_8_VSB;
|
|
else if("ATSC_16_VSB" == std) stdid=V4L2_STD_ATSC_16_VSB;
|
|
else if("MN" == std) stdid=V4L2_STD_MN;
|
|
else if("B" == std) stdid=V4L2_STD_B;
|
|
else if("GH" == std) stdid=V4L2_STD_GH;
|
|
else if("DK" == std) stdid=V4L2_STD_DK;
|
|
else if("PAL_BG" == std) stdid=V4L2_STD_PAL_BG;
|
|
else if("PAL_DK" == std) stdid=V4L2_STD_PAL_DK;
|
|
else if("PAL" == std) stdid=V4L2_STD_PAL;
|
|
else if("NTSC" == std) stdid=V4L2_STD_NTSC;
|
|
else if("SECAM_DK" == std) stdid=V4L2_STD_SECAM_DK;
|
|
else if("SECAM" == std) stdid=V4L2_STD_SECAM;
|
|
else if("525_60" == std) stdid=V4L2_STD_525_60;
|
|
else if("625_50" == std) stdid=V4L2_STD_625_50;
|
|
else if("ATSC" == std) stdid=V4L2_STD_ATSC;
|
|
}
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
xioctl(m_tvfd, VIDIOC_S_STD, &stdid);
|
|
|
|
} else if("channel" == key) {
|
|
double ch;
|
|
if(props.get("channel", ch)) {
|
|
int channel=ch;
|
|
xioctl(m_tvfd, VIDIOC_S_INPUT, &channel);
|
|
}
|
|
} else if("frequency" == key) {
|
|
} else if("width" == key) {
|
|
double d=0.;
|
|
if(props.get("width", d)) {
|
|
int i=d;
|
|
if(m_width != i)
|
|
setformat=true;
|
|
m_width = i;
|
|
}
|
|
} else if("height" == key) {
|
|
double d=0.;
|
|
if(props.get("height", d)) {
|
|
int i=d;
|
|
if(m_height != i)
|
|
setformat=true;
|
|
m_height = i;
|
|
}
|
|
} else {
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if(setformat || setcropping)
|
|
restart=true;
|
|
|
|
if(restart) {
|
|
bool rendering=m_rendering;
|
|
if(m_capturing)stopTransfer();
|
|
|
|
#if 0
|
|
setcropping=true;
|
|
if(setcropping) {
|
|
struct v4l2_cropcap cropcap;
|
|
cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
|
|
if (-1 == xioctl (m_tvfd, VIDIOC_CROPCAP, &cropcap)) {
|
|
/* Errors ignored. */
|
|
}
|
|
|
|
memset(&(cropcap), 0, sizeof (cropcap));
|
|
cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
|
|
if (0 == xioctl (m_tvfd, VIDIOC_CROPCAP, &cropcap)) {
|
|
struct v4l2_crop crop;
|
|
|
|
crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
crop.c = cropcap.defrect; /* reset to default */
|
|
|
|
if (-1 == xioctl (m_tvfd, VIDIOC_S_CROP, &crop)) {
|
|
perror("v4l2: vidioc_s_crop");
|
|
switch (errno) {
|
|
case EINVAL:
|
|
/* Cropping not supported. */
|
|
break;
|
|
default:
|
|
/* Errors ignored. */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} // cropping
|
|
#endif
|
|
|
|
if(setformat) {
|
|
struct v4l2_format fmt;
|
|
memset (&(fmt), 0, sizeof (fmt));
|
|
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
|
|
if (0 == xioctl (m_tvfd, VIDIOC_G_FMT, &fmt)) {
|
|
double d;
|
|
debugPost("current format %dx%d", fmt.fmt.pix.width, fmt.fmt.pix.height);
|
|
|
|
if(props.get("width", d))
|
|
fmt.fmt.pix.width=d;
|
|
if(props.get("height", d))
|
|
fmt.fmt.pix.height=d;
|
|
|
|
debugPost("want format %dx%d", fmt.fmt.pix.width, fmt.fmt.pix.height);
|
|
if(0 != xioctl (m_tvfd, VIDIOC_S_FMT, &fmt)) {
|
|
perror("VIDIOC_S_FMT(dim)");
|
|
}
|
|
debugPost("new format %dx%d", fmt.fmt.pix.width, fmt.fmt.pix.height);
|
|
}
|
|
} // format
|
|
|
|
if (rendering)startTransfer();
|
|
}
|
|
}
|
|
|
|
|
|
#else
|
|
videoV4L2 :: videoV4L2() : videoBase("") {}
|
|
videoV4L2 :: ~videoV4L2() {}
|
|
#endif /* HAVE_VIDEO4LINUX2 */
|