From 00ee003d1e9a0a10e72637eb6ed07cd9a82f1f65 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 23 Jun 2010 08:51:53 +0000 Subject: [PATCH] Video4Linux2 support for WebcamVideo and a few minor improvements to the webcam code. --- panda/src/vision/Sources.pp | 16 +- panda/src/vision/config_vision.cxx | 4 + panda/src/vision/vision_composite1.cxx | 2 + panda/src/vision/webcamVideo.I | 19 +- panda/src/vision/webcamVideo.cxx | 10 +- panda/src/vision/webcamVideo.h | 8 +- panda/src/vision/webcamVideoCursorV4L.cxx | 413 ++++++++++++++++++++++ panda/src/vision/webcamVideoCursorV4L.h | 69 ++++ panda/src/vision/webcamVideoDS.cxx | 122 ++++--- panda/src/vision/webcamVideoV4L.cxx | 135 +++++++ panda/src/vision/webcamVideoV4L.h | 58 +++ 11 files changed, 780 insertions(+), 76 deletions(-) create mode 100644 panda/src/vision/webcamVideoCursorV4L.cxx create mode 100644 panda/src/vision/webcamVideoCursorV4L.h create mode 100644 panda/src/vision/webcamVideoV4L.cxx create mode 100644 panda/src/vision/webcamVideoV4L.h diff --git a/panda/src/vision/Sources.pp b/panda/src/vision/Sources.pp index 3253ee7934..a5a4642300 100644 --- a/panda/src/vision/Sources.pp +++ b/panda/src/vision/Sources.pp @@ -9,26 +9,30 @@ #define TARGET p3vision #define LOCAL_LIBS \ display text pgraph gobj linmath putil audio movies - + #define COMBINED_SOURCES vision_composite1.cxx #define SOURCES \ arToolKit.I arToolKit.h \ config_vision.h \ openCVTexture.I openCVTexture.h \ - webcamVideo.h webcamVideo.I - + webcamVideo.h webcamVideo.I \ + webcamVideoCursorV4L.h webcamVideoV4L.h + #define INCLUDED_SOURCES \ arToolKit.cxx \ config_vision.cxx \ openCVTexture.cxx \ webcamVideo.cxx \ - webcamVideoDS.cxx - + webcamVideoDS.cxx \ + webcamVideoCursorV4L.cxx \ + webcamVideoV4L.cxx + #define INSTALL_HEADERS \ arToolKit.I arToolKit.h \ openCVTexture.I openCVTexture.h \ - webcamVideo.h webcamVideo.I + webcamVideo.h webcamVideo.I \ + webcamVideoCursorV4L.h webcamVideoV4L.h #define IGATESCAN all diff --git a/panda/src/vision/config_vision.cxx b/panda/src/vision/config_vision.cxx index 6b36d1aed1..daed16952d 100644 --- a/panda/src/vision/config_vision.cxx +++ b/panda/src/vision/config_vision.cxx @@ -15,6 +15,8 @@ #include "config_vision.h" #include "openCVTexture.h" #include "webcamVideo.h" +#include "webcamVideoCursorV4L.h" +#include "webcamVideoV4L.h" #include "pandaSystem.h" #include "texturePool.h" @@ -44,6 +46,8 @@ init_libvision() { initialized = true; WebcamVideo::init_type(); + WebcamVideoCursorV4L::init_type(); + WebcamVideoV4L::init_type(); #ifdef HAVE_OPENCV OpenCVTexture::init_type(); diff --git a/panda/src/vision/vision_composite1.cxx b/panda/src/vision/vision_composite1.cxx index db78df9d67..abbfe49d6b 100644 --- a/panda/src/vision/vision_composite1.cxx +++ b/panda/src/vision/vision_composite1.cxx @@ -2,5 +2,7 @@ #include "config_vision.cxx" #include "openCVTexture.cxx" #include "webcamVideo.cxx" +#include "webcamVideoCursorV4L.cxx" #include "webcamVideoDS.cxx" +#include "webcamVideoV4L.cxx" diff --git a/panda/src/vision/webcamVideo.I b/panda/src/vision/webcamVideo.I index 81a401a67b..356e5c568b 100644 --- a/panda/src/vision/webcamVideo.I +++ b/panda/src/vision/webcamVideo.I @@ -37,10 +37,27 @@ get_size_y() const { // Function: WebcamVideo::get_fps // Access: Published // Description: Returns the camera's framerate. This -// is a maximum theoretical: the actual performance +// is a maximum theoretical: the actual performance // will depend on the speed of the hardware. //////////////////////////////////////////////////////////////////// INLINE int WebcamVideo:: get_fps() const { return _fps; } + +//////////////////////////////////////////////////////////////////// +// Function: WebcamVideo::output +// Access: Public +// Description: Outputs the WebcamVideo. This function simply +// writes the name, size and FPS to the output stream. +//////////////////////////////////////////////////////////////////// +INLINE void WebcamVideo:: +output(ostream &out) const { + out << get_name() << ": " << get_size_x() << "x" << get_size_y() << " @ " << get_fps() << "Hz"; +} + +INLINE ostream &operator << (ostream &out, const WebcamVideo &n) { + n.output(out); + return out; +} + diff --git a/panda/src/vision/webcamVideo.cxx b/panda/src/vision/webcamVideo.cxx index 851cc9ed8d..a3474ac85c 100644 --- a/panda/src/vision/webcamVideo.cxx +++ b/panda/src/vision/webcamVideo.cxx @@ -23,7 +23,7 @@ TypeHandle WebcamVideo::_type_handle; //////////////////////////////////////////////////////////////////// // Function: WebcamVideo::Destructor // Access: Published, Virtual -// Description: +// Description: //////////////////////////////////////////////////////////////////// WebcamVideo:: ~WebcamVideo() { @@ -55,7 +55,7 @@ find_all_webcams() { find_all_webcams_ds(); #endif -#ifdef HAVE_VIDEO4LINUX +#ifdef IS_LINUX extern void find_all_webcams_v4l(); find_all_webcams_v4l(); #endif @@ -63,11 +63,11 @@ find_all_webcams() { //////////////////////////////////////////////////////////////////// // Function: WebcamVideo::get_num_options -// Access: Public +// Access: Public, Static // Description: Returns the number of webcam options. An "option" // consists of a device plus a set of configuration // parameters. For example, "Creative Webcam Live at -// 640x480, 30 fps" is an option. +// 640x480, 30 fps" is an option. //////////////////////////////////////////////////////////////////// int WebcamVideo:: get_num_options() { @@ -77,7 +77,7 @@ get_num_options() { //////////////////////////////////////////////////////////////////// // Function: WebcamVideo::get_option -// Access: Public +// Access: Public, Static // Description: Returns the nth webcam option. //////////////////////////////////////////////////////////////////// PT(WebcamVideo) WebcamVideo:: diff --git a/panda/src/vision/webcamVideo.h b/panda/src/vision/webcamVideo.h index ad0f5e834e..94a0b431c7 100644 --- a/panda/src/vision/webcamVideo.h +++ b/panda/src/vision/webcamVideo.h @@ -30,13 +30,15 @@ PUBLISHED: static int get_num_options(); static PT(WebcamVideo) get_option(int n); MAKE_SEQ(get_options, get_num_options, get_option); - + INLINE int get_size_x() const; INLINE int get_size_y() const; INLINE int get_fps() const; - + virtual PT(MovieVideoCursor) open() = 0; + INLINE void output(ostream &out) const; + public: static void find_all_webcams(); @@ -65,6 +67,8 @@ private: static TypeHandle _type_handle; }; +INLINE ostream &operator << (ostream &out, const WebcamVideo &n); + #include "webcamVideo.I" #endif diff --git a/panda/src/vision/webcamVideoCursorV4L.cxx b/panda/src/vision/webcamVideoCursorV4L.cxx new file mode 100644 index 0000000000..ffa520636b --- /dev/null +++ b/panda/src/vision/webcamVideoCursorV4L.cxx @@ -0,0 +1,413 @@ +// Filename: webcamVideoCursorV4L.cxx +// Created by: rdb (11Jun2010) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifdef IS_LINUX + +#include "webcamVideoV4L.h" + +#include +#include +#include + +#ifdef HAVE_JPEG +extern "C" { + #include + #include + #include +} + +#include +#endif + +TypeHandle WebcamVideoCursorV4L::_type_handle; + +#define clamp(x) min(max(x, 0.0), 255.0) + +INLINE static void yuv_to_rgb(unsigned char *dest, const unsigned char *src) { + double y1 = (255 / 219.0) * (src[0] - 16); + double pb = (255 / 224.0) * (src[1] - 128); + double pr = (255 / 224.0) * (src[2] - 128); + dest[2] = clamp(1.0 * y1 + 0 * pb + 1.402 * pr); + dest[1] = clamp(1.0 * y1 - 0.344 * pb - 0.714 * pr); + dest[0] = clamp(1.0 * y1 + 1.772 * pb + 0 * pr); +} + +INLINE static void yuyv_to_rgbrgb(unsigned char *dest, const unsigned char *src) { + unsigned char yuv[] = {src[0], src[1], src[3]}; + yuv_to_rgb(dest, yuv); + yuv[0] = src[2]; + yuv_to_rgb(dest + 3, yuv); +} + +INLINE static void yuyv_to_rgbargba(unsigned char *dest, const unsigned char *src) { + unsigned char yuv[] = {src[0], src[1], src[3]}; + yuv_to_rgb(dest, yuv); + yuv[0] = src[2]; + yuv_to_rgb(dest + 4, yuv); + dest[3] = (unsigned char) -1; + dest[7] = (unsigned char) -1; +} + +#if defined(HAVE_JPEG) && !defined(CPPPARSER) + +struct my_error_mgr { + struct jpeg_error_mgr pub; + jmp_buf setjmp_buffer; +}; + +typedef struct my_error_mgr *my_error_ptr; + +static void my_error_exit (j_common_ptr cinfo) { + // Output the error message + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message) (cinfo, buffer); + vision_cat.error() << buffer << "\n"; + // cinfo->err really points to a my_error_mgr struct, so coerce pointer + my_error_ptr myerr = (my_error_ptr) cinfo->err; + // Return control to the setjmp point + longjmp(myerr->setjmp_buffer, 1); +} + +static void my_output_message (j_common_ptr cinfo){ + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message) (cinfo, buffer); + vision_cat.warning() << buffer << "\n"; +} + +static void my_init_source(j_decompress_ptr cinfo) { +} + +static boolean my_fill_input_buffer(j_decompress_ptr cinfo) { + struct jpeg_source_mgr *src = cinfo->src; + static JOCTET FakeEOI[] = {0xFF, JPEG_EOI}; + + WARNMS(cinfo, JWRN_JPEG_EOF); + + src->next_input_byte = FakeEOI; + src->bytes_in_buffer = 2; + + return TRUE; +} + +static void my_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { + struct jpeg_source_mgr *src = cinfo->src; + + if (num_bytes >= (long) src->bytes_in_buffer) { + my_fill_input_buffer(cinfo); + return; + } + + src->bytes_in_buffer -= num_bytes; + src->next_input_byte += num_bytes; +} + +static void my_term_source(j_decompress_ptr cinfo) { +} + +#endif + +//////////////////////////////////////////////////////////////////// +// Function: WebcamVideoCursorV4L::Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +WebcamVideoCursorV4L:: +WebcamVideoCursorV4L(WebcamVideoV4L *src) : MovieVideoCursor(src) { + _size_x = src->_size_x; + _size_y = src->_size_y; + _num_components = 3; + _length = 1.0E10; + _can_seek = false; + _can_seek_fast = false; + _aborted = false; + _last_start = -1.0; + _next_start = 0.0; + _streaming = true; + _ready = false; + _format = (struct v4l2_format *) malloc(sizeof(struct v4l2_format)); + memset(_format, 0, sizeof(struct v4l2_format)); + _cinfo = NULL; + _buffers = NULL; + _buflens = NULL; + _fd = open(src->_device.c_str(), O_RDWR); + if (-1 == _fd) { + vision_cat.error() << "Failed to open " << src->_device.c_str() << "\n"; + return; + } + + _format->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + pvector::iterator it; + for (it = src->_pformats.begin(); it != src->_pformats.end(); ++it) { +#ifdef HAVE_JPEG + if (*it == V4L2_PIX_FMT_MJPEG) { + _format->fmt.pix.pixelformat = *it; + break; + } else +#endif + if (*it == V4L2_PIX_FMT_YUYV) { + _format->fmt.pix.pixelformat = *it; + break; + } + } + if (it == src->_pformats.end()) { + vision_cat.error() << "Failed to find a suitable pixel format!\n"; + _ready = false; + close(_fd); + _fd = -1; + return; + } + + // No interlacing + _format->fmt.pix.field = V4L2_FIELD_NONE; + + // This really should be there, but for + // some reason it causes weird memory corruption. + + _format->fmt.pix.width = _size_x; + _format->fmt.pix.height = _size_y; + + // Now politely ask the driver to switch to this format + if (-1 == ioctl(_fd, VIDIOC_S_FMT, _format)) { + vision_cat.error() << "Driver rejected format!\n"; + _ready = false; + close(_fd); + _fd = -1; + return; + } + + _size_x = _format->fmt.pix.width; + _size_y = _format->fmt.pix.height; + + struct v4l2_requestbuffers req; + memset(&req, 0, sizeof req); + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + if (-1 == ioctl (_fd, VIDIOC_REQBUFS, &req)) { + vision_cat.error() << "Failed to request buffers from webcam!\n"; + } + + if (req.count < 2) { + vision_cat.error() << "Insufficient buffer memory!\n"; + } + + _bufcount = req.count; + _buffers = (void* *) calloc (req.count, sizeof (void*)); + _buflens = (size_t*) calloc (req.count, sizeof (size_t)); + + if (!_buffers || !_buflens) { + vision_cat.error() << "Not enough memory!\n"; + } + + struct v4l2_buffer buf; + for (int i = 0; i < _bufcount; ++i) { + memset(&buf, 0, sizeof buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + if (-1 == ioctl(_fd, VIDIOC_QUERYBUF, &buf)) { + vision_cat.error() << "Failed to query buffer!\n"; + } + + _buflens[i] = buf.length; + _buffers[i] = mmap (NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, buf.m.offset); + + if (_buffers[i] == MAP_FAILED) { + vision_cat.error() << "Failed to map buffer!\n"; + } + + if (-1 == ioctl(_fd, VIDIOC_QBUF, &buf)) { + vision_cat.error() << "Failed to exchange buffer with driver!\n"; + } + } + + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == ioctl(_fd, VIDIOC_STREAMON, &type)) { + vision_cat.error() << "Failed to stream from buffer!\n"; + } + +#ifdef HAVE_JPEG + // Initialize the JPEG library, if necessary + if (_format->fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) { + _cinfo = (struct jpeg_decompress_struct *) malloc(sizeof(struct jpeg_decompress_struct)); + jpeg_create_decompress(_cinfo); + + _cinfo->src = (struct jpeg_source_mgr *) + (*_cinfo->mem->alloc_small) ((j_common_ptr) _cinfo, JPOOL_PERMANENT, + sizeof(struct jpeg_source_mgr)); + // Set up function pointers + _cinfo->src->init_source = my_init_source; + _cinfo->src->fill_input_buffer = my_fill_input_buffer; + _cinfo->src->skip_input_data = my_skip_input_data; + _cinfo->src->resync_to_restart = jpeg_resync_to_restart; + _cinfo->src->term_source = my_term_source; + } +#endif + _ready = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: WebcamVideoCursorV4L::Destructor +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +WebcamVideoCursorV4L:: +~WebcamVideoCursorV4L() { +#ifdef HAVE_JPEG + if (_cinfo != NULL) { + jpeg_destroy_decompress(_cinfo); + free(_cinfo); + } +#endif + if (_format != NULL) { + free(_format); + } + if (-1 != _fd) { + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ioctl(_fd, VIDIOC_STREAMOFF, &type); + close(_fd); + } + if (_buffers) { + for (int i = 0; i < _bufcount; ++i) { + munmap(_buffers[i], _buflens[i]); + } + free(_buffers); + } + if (_buflens) { + free(_buflens); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: WebcamVideoCursorV4L::fetch_into_buffer +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void WebcamVideoCursorV4L:: +fetch_into_buffer(double time, unsigned char *block, bool bgra) { + if (!_ready) { + return; + } + + struct v4l2_buffer vbuf; + memset(&vbuf, 0, sizeof vbuf); + vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vbuf.memory = V4L2_MEMORY_MMAP; + if (-1 == ioctl(_fd, VIDIOC_DQBUF, &vbuf) && errno != EIO) { + vision_cat.error() << "Failed to dequeue buffer!\n"; + return; + } + nassertv(vbuf.index < _bufcount); + size_t bufsize = _buflens[vbuf.index]; + size_t old_bpl = _format->fmt.pix.bytesperline; + size_t new_bpl = bgra ? _size_x * 4 : _size_x * 3; + unsigned char *buf = (unsigned char *) _buffers[vbuf.index]; + + if (_format->fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) { +#ifdef HAVE_JPEG + nassertv(!bgra); + struct my_error_mgr jerr; + _cinfo->err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = my_error_exit; + jerr.pub.output_message = my_output_message; + + unsigned char *newbuf = (unsigned char*) malloc(new_bpl * _size_y); + + // Establish the setjmp return context for my_error_exit to use + if (setjmp(jerr.setjmp_buffer)) { + if (_cinfo->global_state > DSTATE_READY) { + jpeg_abort_decompress(_cinfo); + } + } else { + // Set up data pointer + _cinfo->src->bytes_in_buffer = bufsize; + _cinfo->src->next_input_byte = buf; + + if (jpeg_read_header(_cinfo, TRUE) == JPEG_HEADER_OK) { + _cinfo->scale_num = 1; + _cinfo->scale_denom = 1; + _cinfo->out_color_space = JCS_RGB; + + if (jpeg_start_decompress(_cinfo) && _cinfo->output_components == 3 + && _size_x == _cinfo->output_width && _size_y == _cinfo->output_height) { + + JSAMPLE *buffer_end = newbuf + new_bpl * _cinfo->output_height; + JSAMPLE *rowptr = newbuf; + while (_cinfo->output_scanline < _cinfo->output_height) { + nassertd(rowptr + new_bpl <= buffer_end) break; + jpeg_read_scanlines(_cinfo, &rowptr, 1); + rowptr += new_bpl; + } + + if (_cinfo->output_scanline < _cinfo->output_height) { + jpeg_abort_decompress(_cinfo); + } else { + jpeg_finish_decompress(_cinfo); + } + } + } + } + + // Flip the image vertically + for (size_t row = 0; row < _size_y; ++row) { + memcpy(block + (_size_y - row - 1) * new_bpl, newbuf + row * new_bpl, new_bpl); + } + free(newbuf); + + // Swap red / blue + unsigned char ex; + if (bgra) { + for (size_t i = 0; i < new_bpl * _size_y; i += 4) { + ex = block[i]; + block[i] = block[i + 2]; + block[i + 2] = ex; + } + } else { + for (size_t i = 0; i < new_bpl * _size_y; i += 3) { + ex = block[i]; + block[i] = block[i + 2]; + block[i + 2] = ex; + } + } +#else + nassertv(false); // Not compiled with JPEG support +#endif + } else { + if (bgra) { + for (size_t row = 0; row < _size_y; ++row) { + size_t c = 0; + for (size_t i = 0; i < old_bpl; i += 4) { + yuyv_to_rgbargba(block + (_size_y - row - 1) * new_bpl + c, buf + row * old_bpl + i); + c += 8; + } + } + } else { + for (size_t row = 0; row < _size_y; ++row) { + size_t c = 0; + for (size_t i = 0; i < old_bpl; i += 4) { + yuyv_to_rgbrgb(block + (_size_y - row - 1) * new_bpl + c, buf + row * old_bpl + i); + c += 6; + } + } + } + } + + if (-1 == ioctl(_fd, VIDIOC_QBUF, &vbuf)) { + vision_cat.error() << "Failed to exchange buffer with driver!\n"; + } +} + +#endif diff --git a/panda/src/vision/webcamVideoCursorV4L.h b/panda/src/vision/webcamVideoCursorV4L.h new file mode 100644 index 0000000000..9463957e2f --- /dev/null +++ b/panda/src/vision/webcamVideoCursorV4L.h @@ -0,0 +1,69 @@ +// Filename: webcamVideoCursorV4L.h +// Created by: rdb (11Jun2010) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef WEBCAMVIDEOCURSORV4L_H +#define WEBCAMVIDEOCURSORV4L_H + +#ifdef IS_LINUX + +#include "webcamVideo.h" + +struct v4l2_format; +#ifdef HAVE_JPEG +struct jpeg_decompress_struct; +#endif + +class WebcamVideoV4L; + +//////////////////////////////////////////////////////////////////// +// Class : WebcamVideoCursorV4L +// Description : The Video4Linux implementation of webcams. +//////////////////////////////////////////////////////////////////// +class WebcamVideoCursorV4L : public MovieVideoCursor { +public: + WebcamVideoCursorV4L(WebcamVideoV4L *src); + virtual ~WebcamVideoCursorV4L(); + virtual void fetch_into_buffer(double time, unsigned char *block, bool rgba); + +private: + int _fd; + void **_buffers; + size_t *_buflens; + size_t _bufcount; + struct v4l2_format *_format; +#ifdef HAVE_JPEG + struct jpeg_decompress_struct *_cinfo; +#endif + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + MovieVideoCursor::init_type(); + register_type(_type_handle, "WebcamVideoCursorV4L", + MovieVideoCursor::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#endif // IS_LINUX + +#endif diff --git a/panda/src/vision/webcamVideoDS.cxx b/panda/src/vision/webcamVideoDS.cxx index 5a8613f42c..5d434ed9c1 100644 --- a/panda/src/vision/webcamVideoDS.cxx +++ b/panda/src/vision/webcamVideoDS.cxx @@ -23,7 +23,7 @@ // This code was created by studying and adapting the VDOGRAB // library by Shu-Kai Yang and the videoInput library by Theodore // Watson. We owe both of them a great deal of thanks for -// figuring all this out. Both of their libraries have +// figuring all this out. Both of their libraries have // informal licenses (the "do whatever you want and don't blame // me" sort), so I think there's not a problem using their code. // @@ -31,7 +31,7 @@ #ifdef HAVE_DIRECTCAM -#define WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN #undef Configure @@ -102,7 +102,7 @@ private: IMoniker *_moniker; AM_MEDIA_TYPE *_media; - + friend class WebcamVideoCursorDS; public: @@ -141,14 +141,14 @@ public: public: void cleanup(); - class CSampleGrabberCB : public ISampleGrabberCB - { + class CSampleGrabberCB : public ISampleGrabberCB + { public: WebcamVideoCursorDS *_host; - + ULONG __stdcall AddRef() { return 2; } ULONG __stdcall Release() { return 1; } - + HRESULT __stdcall QueryInterface(REFIID riid, void ** ppv); HRESULT __stdcall SampleCB(double SampleTime, IMediaSample *pSample); HRESULT __stdcall BufferCB(double dblSampleTime, BYTE *pBuffer, long lBufferSize); @@ -161,12 +161,12 @@ public: ICaptureGraphBuilder2 *_pCaptureBuilder; IBaseFilter *_pSrcFilter; IAMStreamConfig *_pStreamConfig; - CComPtr _pSampleGrabber; + CComPtr _pSampleGrabber; IBaseFilter *_pStreamRenderer; IMediaControl *_pMediaCtrl; // IMemAllocator *_pAllocator; CSampleGrabberCB _sample_cb; - + public: static TypeHandle get_class_type() { return _type_handle; @@ -329,30 +329,28 @@ add_device(WebcamVideoList &list, IMoniker *pMoniker, AM_MEDIA_TYPE *media) { } } PT(WebcamVideoDS) wc = new WebcamVideoDS; - ostringstream name; - name << "DirectShow: " << get_moniker_name(pMoniker) << " @ " << media_x(media) << " x " << media_y(media) << " FPS:" << media_fps(media); - wc->set_name(name.str()); + wc->set_name(get_moniker_name(pMoniker)); wc->_size_x = media_x(media); wc->_size_y = media_y(media); wc->_fps = media_fps(media); wc->_moniker = pMoniker; wc->_media = media; list.push_back(wc); - cerr << "Added device: " << wc->get_name() << "\n"; + cerr << "Added device: DirectShow: " << wc << "\n"; } //////////////////////////////////////////////////////////////////// // Function: WebcamVideoDS::find_all_webcams_ds // Access: Public, Static -// Description: Finds all DirectShow webcams and adds them to +// Description: Finds all DirectShow webcams and adds them to // the global list _all_webcams. //////////////////////////////////////////////////////////////////// void WebcamVideoDS:: find_all_webcams_ds() { pvector list; - + ICreateDevEnum *pCreateDevEnum=NULL; IEnumMoniker *pEnumMoniker=NULL; IGraphBuilder *pGraphBuilder=NULL; @@ -362,7 +360,7 @@ find_all_webcams_ds() { IAMStreamConfig *pStreamConfig=NULL; HRESULT hResult; ULONG cFetched; - + hResult=CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder,(void**)&pGraphBuilder); if (hResult != S_OK) goto cleanup; @@ -377,7 +375,7 @@ find_all_webcams_ds() { hResult=pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnumMoniker, 0); if(hResult != S_OK) goto cleanup; - + while(1) { if (pMoniker) { pMoniker->Release(); pMoniker=0; } if (pBaseFilter) { pBaseFilter->Release(); pBaseFilter=0; } @@ -390,7 +388,7 @@ find_all_webcams_ds() { if (hResult != S_OK) continue; hResult = pCaptureGraphBuilder2->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pBaseFilter, IID_IAMStreamConfig, (void **)&pStreamConfig); - if (hResult != S_OK) continue; + if (hResult != S_OK) continue; int iCount, iSize; hResult = pStreamConfig->GetNumberOfCapabilities(&iCount, &iSize); if (hResult != S_OK || (iSize != sizeof(VIDEO_STREAM_CONFIG_CAPS))) continue; @@ -405,10 +403,10 @@ find_all_webcams_ds() { add_device(list, pMoniker, mtype); } } - + pMoniker = 0; } - + cleanup: if (pCreateDevEnum) { pCreateDevEnum->Release(); pCreateDevEnum=0; } if (pEnumMoniker) { pEnumMoniker->Release(); pEnumMoniker=0; } @@ -417,7 +415,7 @@ find_all_webcams_ds() { if (pMoniker) { pMoniker->Release(); pMoniker=0; } if (pBaseFilter) { pBaseFilter->Release(); pBaseFilter=0; } if (pStreamConfig) { pStreamConfig->Release(); pStreamConfig=0; } - + for (int i=0; i<(int)list.size(); i++) { WebcamVideoDS *obj = list[i]; _all_webcams.push_back(obj); @@ -443,7 +441,7 @@ open() { //////////////////////////////////////////////////////////////////// // Function: WebcamVideoCursorDS::Constructor // Access: Published -// Description: +// Description: //////////////////////////////////////////////////////////////////// WebcamVideoCursorDS:: WebcamVideoCursorDS(WebcamVideoDS *src) : @@ -457,39 +455,39 @@ WebcamVideoCursorDS(WebcamVideoDS *src) : { AM_MEDIA_TYPE mediaType; VIDEOINFOHEADER *pVideoInfo; - + HRESULT hResult; - + hResult=CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder,(void**)&_pGraphBuilder); if(hResult != S_OK) { cleanup(); return; } - + hResult=CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC, IID_ICaptureGraphBuilder2, (void**)&_pCaptureBuilder); if(hResult != S_OK) { cleanup(); return; } - + _pCaptureBuilder->SetFiltergraph(_pGraphBuilder); cerr << " IID_IGraphBuilder & IID_ICaptureGraphBuilder2 are established.\n"; - + hResult=_pGraphBuilder->QueryInterface(IID_IMediaControl, (void **)&_pMediaCtrl); if(hResult != S_OK) { cerr << " Can not get the IID_IMediaControl interface!"; cleanup(); return; } cerr << " IID_IMediaControl interface is acquired.\n"; - + src->_moniker->BindToObject(NULL,NULL,IID_IBaseFilter, (void**)&_pSrcFilter); if(_pSrcFilter == NULL) { cerr << " Such capture device is not found.\n"; cleanup(); return; } cerr << " The capture filter is acquired.\n"; - + hResult=_pGraphBuilder->AddFilter(_pSrcFilter, L"Capture Filter"); if(hResult != DD_OK) { cerr << " The capture filter can not be added to the graph.\n"; cleanup(); return; } cerr << " The capture filter has been added to the graph.\n"; - - + + hResult = _pCaptureBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, _pSrcFilter, IID_IAMStreamConfig, (void **)&_pStreamConfig); if (hResult != S_OK) { @@ -501,16 +499,16 @@ WebcamVideoCursorDS(WebcamVideoDS *src) : cerr << "Could not select desired video resolution\n"; cleanup(); return; } - + _pSampleGrabber.CoCreateInstance(CLSID_SampleGrabber); if(!_pSampleGrabber) { cerr << " Can not create the sample grabber, maybe qedit.dll is not registered?"; cleanup(); return; } - - + + CComQIPtr< IBaseFilter, &IID_IBaseFilter > pGrabberFilter(_pSampleGrabber); cerr << " IID_IBaseFilter of CLSID_SampleGrabber is acquired.\n"; - + ZeroMemory(&mediaType, sizeof(AM_MEDIA_TYPE)); mediaType.majortype=MEDIATYPE_Video; mediaType.subtype=MEDIASUBTYPE_RGB24; @@ -519,14 +517,14 @@ WebcamVideoCursorDS(WebcamVideoDS *src) : { cerr << " Fail to set the media type!"; cleanup(); return; } cerr << " The media type of the sample grabber is set 24-bit RGB.\n"; - + hResult=_pGraphBuilder->AddFilter(pGrabberFilter, L"Sample Grabber"); if(hResult != S_OK) { cerr << " Fail to add the sample grabber to the graph."; cleanup(); return; } cerr << " The sample grabber has been added to the graph.\n"; - //used to give the video stream somewhere to go to. + //used to give the video stream somewhere to go to. hResult = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&_pStreamRenderer); if(hResult != S_OK) { cerr << " Can not create the null renderer."; @@ -538,20 +536,20 @@ WebcamVideoCursorDS(WebcamVideoDS *src) : { cerr << " Fail to add the Null Renderer to the graph."; cleanup(); return; } cerr << " The Null Renderer has been added to the graph.\n"; - + hResult=_pCaptureBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, _pSrcFilter, pGrabberFilter, _pStreamRenderer); if(hResult != S_OK) { cerr << " ICaptureGraphBuilder2::RenderStream() can not connect the pins\n"; cleanup(); return; } - + hResult=_pSampleGrabber->GetConnectedMediaType(&mediaType); if(hResult != S_OK) { cerr << " Failed to read the connected media type."; - cleanup(); return; + cleanup(); return; } - + // IPin *iPin; // hResult = FindInputPin(pGrabberFilter, &iPin); // if ((iPin == 0)||(hResult != S_OK)) { @@ -579,12 +577,12 @@ WebcamVideoCursorDS(WebcamVideoDS *src) : // cerr << "Could not set allocator properties.\n"; // } // cerr << "Allocator properties (adjusted): cBuffers=" << aprops.cBuffers << "\n"; - + pVideoInfo=(VIDEOINFOHEADER*)mediaType.pbFormat; _size_x = pVideoInfo->bmiHeader.biWidth; _size_y = pVideoInfo->bmiHeader.biHeight; cerr << "Connected media type " << _size_x << " x " << _size_y << "\n"; - + _sample_cb._host = this; _num_components = 3; _length = 1.0E10; @@ -602,28 +600,28 @@ WebcamVideoCursorDS(WebcamVideoDS *src) : mediaType.cbFormat=0; mediaType.pbFormat=NULL; } - + if(mediaType.pUnk != NULL) { mediaType.pUnk->Release(); mediaType.pUnk=NULL; } - + _pSampleGrabber->SetBufferSamples(FALSE); _pSampleGrabber->SetOneShot(FALSE); - + hResult=_pSampleGrabber->SetCallback(&_sample_cb, 0); if(hResult != S_OK) { cerr << " Can not set the callback interface!"; cleanup(); return; } - + _pMediaCtrl->Run(); } //////////////////////////////////////////////////////////////////// // Function: WebcamVideoCursorDS::cleanup // Access: Published -// Description: +// Description: //////////////////////////////////////////////////////////////////// void WebcamVideoCursorDS:: cleanup() { @@ -635,7 +633,7 @@ cleanup() { if (_pMediaCtrl) { _pMediaCtrl->Stop(); } - + if(_pMediaCtrl) { _pMediaCtrl->Release(); _pMediaCtrl=NULL; } if(_pCaptureBuilder) { _pCaptureBuilder->Release(); _pCaptureBuilder=NULL; } if(_pGraphBuilder) { _pGraphBuilder->Release(); _pGraphBuilder=NULL; } @@ -648,7 +646,7 @@ cleanup() { //////////////////////////////////////////////////////////////////// // Function: WebcamVideoCursorDS::Destructor // Access: Published -// Description: +// Description: //////////////////////////////////////////////////////////////////// WebcamVideoCursorDS:: ~WebcamVideoCursorDS() { @@ -658,16 +656,16 @@ WebcamVideoCursorDS:: //////////////////////////////////////////////////////////////////// // Function: WebcamVideoCursorDS::fetch_into_buffer // Access: Published -// Description: +// Description: //////////////////////////////////////////////////////////////////// void WebcamVideoCursorDS:: fetch_into_buffer(double time, unsigned char *block, bool bgra) { - + if (!_ready) { return; } -#ifdef LOCKING_MODE +#ifdef LOCKING_MODE unsigned char *ptr; int pixels = _size_x * _size_y; HRESULT res = _saved->GetPointer(&ptr); @@ -700,7 +698,7 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) { memcpy(block, _buffer, pixels * 3); } #endif - + _ready = false; } @@ -708,14 +706,14 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) { //////////////////////////////////////////////////////////////////// // Function: WebcamVideoCursorDS::CSampleGrabberCB::QueryInterface // Access: Private -// Description: +// Description: //////////////////////////////////////////////////////////////////// HRESULT __stdcall WebcamVideoCursorDS::CSampleGrabberCB::QueryInterface(REFIID riid, void **ppv) { if((riid == IID_ISampleGrabberCB) || (riid == IID_IUnknown)) { *ppv=(void *)static_cast (this); return NOERROR; - } + } return E_NOINTERFACE; } @@ -723,7 +721,7 @@ HRESULT __stdcall WebcamVideoCursorDS::CSampleGrabberCB::QueryInterface(REFIID r //////////////////////////////////////////////////////////////////// // Function: WebcamVideoCursorDS::CSampleGrabberCB::SampleCB // Access: Private -// Description: +// Description: //////////////////////////////////////////////////////////////////// HRESULT __stdcall WebcamVideoCursorDS::CSampleGrabberCB::SampleCB(double SampleTime, IMediaSample *pSample) { @@ -731,7 +729,7 @@ HRESULT __stdcall WebcamVideoCursorDS::CSampleGrabberCB::SampleCB(double SampleT return 0; } -#ifdef LOCKING_MODE +#ifdef LOCKING_MODE pSample->AddRef(); _host->_saved = pSample; #else @@ -753,10 +751,10 @@ HRESULT __stdcall WebcamVideoCursorDS::CSampleGrabberCB::SampleCB(double SampleT //////////////////////////////////////////////////////////////////// // Function: WebcamVideoCursorDS::CSampleGrabberCB::BufferCB // Access: Private -// Description: +// Description: //////////////////////////////////////////////////////////////////// HRESULT __stdcall WebcamVideoCursorDS::CSampleGrabberCB::BufferCB(double dblSampleTime, BYTE *pBuffer, long lBufferSize) -{ +{ // Not used. return 0; } @@ -765,7 +763,7 @@ HRESULT __stdcall WebcamVideoCursorDS::CSampleGrabberCB::BufferCB(double dblSamp //{ // if (!pFilter || ! ppPin) // return E_POINTER; -// +// // *ppPin = 0; // HRESULT hr; // //Find the output pin of the Source Filter @@ -773,7 +771,7 @@ HRESULT __stdcall WebcamVideoCursorDS::CSampleGrabberCB::BufferCB(double dblSamp // hr = pFilter->EnumPins(&pPinEnum); // if (FAILED(hr)) // return E_FAIL; -// +// // IPin *pSearchPin; // while (pPinEnum->Next(1, &pSearchPin, NULL) == S_OK) // { diff --git a/panda/src/vision/webcamVideoV4L.cxx b/panda/src/vision/webcamVideoV4L.cxx new file mode 100644 index 0000000000..c9a14e261f --- /dev/null +++ b/panda/src/vision/webcamVideoV4L.cxx @@ -0,0 +1,135 @@ +// Filename: webcamVideoV4L.cxx +// Created by: rdb (11Jun2010) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifdef IS_LINUX + +#include "webcamVideoV4L.h" +#include "webcamVideoCursorV4L.h" + +#include +#include + +TypeHandle WebcamVideoV4L::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: find_all_webcams_v4l +// Access: Public, Static +// Description: Finds all Video4Linux webcams and adds them to +// the global list _all_webcams. +//////////////////////////////////////////////////////////////////// +void find_all_webcams_v4l() { + struct video_capability cap; + struct v4l2_capability cap2; + + vector_string devs; + GlobPattern pattern ("/dev/video*"); + pattern.match_files(devs); + for (vector_string::iterator it = devs.begin(); it != devs.end(); ++it) { + int fd = open(it->c_str(), O_RDWR); + if (fd != -1) { + // Check for Video4Linux2 capabilities + if (ioctl(fd, VIDIOC_QUERYCAP, &cap2) != -1) { + if ((cap2.capabilities & V4L2_CAP_VIDEO_CAPTURE) && + (cap2.capabilities & V4L2_CAP_STREAMING)) { + struct v4l2_fmtdesc fmt; + for (int i = 0;; i++) { + memset(&fmt, 0, sizeof fmt); + fmt.index = i; + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) == -1) { + break; + } + struct v4l2_frmsizeenum frmsizeenum; + for (int j = 0;; j++) { + memset(&frmsizeenum, 0, sizeof frmsizeenum); + frmsizeenum.index = j; + frmsizeenum.pixel_format = fmt.pixelformat; + if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == -1) { + break; + } + if (frmsizeenum.type != V4L2_FRMSIZE_TYPE_DISCRETE) { + continue; + } + struct v4l2_frmivalenum frmivalenum; + for (int k = 0;; k++) { + memset(&frmivalenum, 0, sizeof frmivalenum); + frmivalenum.index = k; + frmivalenum.pixel_format = fmt.pixelformat; + frmivalenum.width = frmsizeenum.discrete.width; + frmivalenum.height = frmsizeenum.discrete.height; + if (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmivalenum) == -1) { + break; + } + if (frmivalenum.type != V4L2_FRMIVAL_TYPE_DISCRETE) { + continue; + } + + // Create a new webcam video object + PT(WebcamVideoV4L) wc = new WebcamVideoV4L; + wc->set_name((const char*) cap2.card); + wc->_device = *it; + wc->_size_x = frmsizeenum.discrete.width; + wc->_size_y = frmsizeenum.discrete.height; + wc->_fps = ((double) frmivalenum.discrete.denominator) / ((double) frmivalenum.discrete.numerator); + wc->_pformats.push_back(fmt.pixelformat); + + // Iterate through the webcams to make sure we don't put any duplicates in there + pvector::iterator wvi; + for (wvi = WebcamVideoV4L::_all_webcams.begin(); wvi != WebcamVideoV4L::_all_webcams.end(); ++wvi) { + if ((*wvi)->is_of_type(WebcamVideoV4L::get_class_type())) { + PT(WebcamVideoV4L) wv_v4l = DCAST(WebcamVideoV4L, *wvi); + if (wv_v4l->_device == wc->_device && + wv_v4l->_size_x == wc->_size_x && + wv_v4l->_size_y == wc->_size_y && + wv_v4l->_fps == wc->_fps) { + wv_v4l->_pformats.push_back(fmt.pixelformat); + break; + } + } + } + // Did the loop finish, meaning that a webcam of these + // properties does not exist? Add it. + if (wvi == WebcamVideoV4L::_all_webcams.end()) { + WebcamVideoV4L::_all_webcams.push_back(DCAST(WebcamVideo, wc)); + } + } + } + } + continue; + } + } + + // Check for Video4Linux capabilities + if (ioctl(fd, VIDIOCGCAP, &cap) != -1) { + if (cap.type & VID_TYPE_CAPTURE) { + //TODO: Video4Linux support + continue; + } + } + } + close(fd); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: WebcamVideoV4L::open +// Access: Published, Virtual +// Description: Open this video, returning a MovieVideoCursor. +//////////////////////////////////////////////////////////////////// +PT(MovieVideoCursor) WebcamVideoV4L:: +open() { + return new WebcamVideoCursorV4L(this); +} + +#endif diff --git a/panda/src/vision/webcamVideoV4L.h b/panda/src/vision/webcamVideoV4L.h new file mode 100644 index 0000000000..ed92c23e88 --- /dev/null +++ b/panda/src/vision/webcamVideoV4L.h @@ -0,0 +1,58 @@ +// Filename: webcamVideoV4L.h +// Created by: rdb (11Jun2010) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef WEBCAMVIDEOV4L_H +#define WEBCAMVIDEOV4L_H + +#ifdef IS_LINUX + +#include "webcamVideo.h" + +class WebcamVideoCursorV4L; + +//////////////////////////////////////////////////////////////////// +// Class : WebcamVideoV4L +// Description : The Video4Linux implementation of webcams. +//////////////////////////////////////////////////////////////////// +class WebcamVideoV4L : public WebcamVideo { +private: + virtual PT(MovieVideoCursor) open(); + + friend class WebcamVideoCursorV4L; + friend void find_all_webcams_v4l(); + + string _device; + pvector _pformats; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + WebcamVideo::init_type(); + register_type(_type_handle, "WebcamVideoV4L", + WebcamVideo::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#endif // IS_LINUX + +#endif