diff --git a/examples/c++/CMakeLists.txt b/examples/c++/CMakeLists.txt index b6b860ce..aeeb8f25 100644 --- a/examples/c++/CMakeLists.txt +++ b/examples/c++/CMakeLists.txt @@ -264,6 +264,7 @@ if (BACNET_FOUND) include_directories(${PROJECT_SOURCE_DIR}/src/bacnetmstp) add_example (e50hx) endif() +add_example (vcap) # These are special cases where you specify example binary, source file and module(s) include_directories (${PROJECT_SOURCE_DIR}/src) diff --git a/examples/c++/vcap.cxx b/examples/c++/vcap.cxx new file mode 100644 index 00000000..fe0d8a79 --- /dev/null +++ b/examples/c++/vcap.cxx @@ -0,0 +1,70 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include + +#include "vcap.hpp" + +using namespace std; + +int main(int argc, char **argv) +{ +//! [Interesting] + + string defaultDev = "/dev/video0"; + + // if an argument was specified, use it as the device instead + if (argc > 1) + defaultDev = string(argv[1]); + + cout << "Using device " << defaultDev << endl; + cout << "Initializing..." << endl; + + // Instantiate an VCAP instance, using the specified video device + upm::VCAP *sensor = new upm::VCAP(defaultDev); + + // enable some debug/verbose output + sensor->setDebug(true); + + // This is just a hint. The kernel can change this to a lower + // resolution that the hardware supports. Use getWidth() and + // getHeight() methods to see what the kernel actually chose if you + // care. + sensor->setResolution(1920, 1080); + + // capture an image + sensor->captureImage(); + + // convert and save it as a jpeg + sensor->saveImage("video-img1.jpg"); + + cout << "Exiting..." << endl; + + delete sensor; + +//! [Interesting] + + return 0; +} diff --git a/examples/java/CMakeLists.txt b/examples/java/CMakeLists.txt index fbcb8e16..62fc018a 100644 --- a/examples/java/CMakeLists.txt +++ b/examples/java/CMakeLists.txt @@ -120,6 +120,7 @@ endif() if (BACNET_FOUND) add_example(E50HX_Example e50hx) endif() +add_example(VCAP_Example vcap) add_example_with_path(Jhd1313m1_lcdSample lcd i2clcd) add_example_with_path(Jhd1313m1Sample lcd i2clcd) diff --git a/examples/java/VCAP_Example.java b/examples/java/VCAP_Example.java new file mode 100644 index 00000000..73d06adc --- /dev/null +++ b/examples/java/VCAP_Example.java @@ -0,0 +1,60 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import upm_vcap.VCAP; + +public class VCAP_Example +{ + private static String defaultDev = "/dev/video0"; + + public static void main(String[] args) throws InterruptedException + { +// ! [Interesting] + if (args.length > 0) + defaultDev = args[0]; + + System.out.println("Using device " + defaultDev); + System.out.println("Initializing..."); + + // Instantiate an VCAP instance, using the specified video device + VCAP sensor = new VCAP(defaultDev); + + // enable some debug/verbose output + sensor.setDebug(true); + + // This is just a hint. The kernel can change this to a lower + // resolution that the hardware supports. Use getWidth() and + // getHeight() methods to see what the kernel actually chose if you + // care. + sensor.setResolution(1920, 1080); + + // capture an image + sensor.captureImage(); + + // convert and save it as a jpeg + sensor.saveImage("video-img1.jpg"); + +// ! [Interesting] + } +} diff --git a/examples/javascript/vcap.js b/examples/javascript/vcap.js new file mode 100644 index 00000000..d198b53c --- /dev/null +++ b/examples/javascript/vcap.js @@ -0,0 +1,69 @@ +/*jslint node:true, vars:true, bitwise:true, unparam:true */ +/*jshint unused:true */ + +/* + * Author: Jon Trulson + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +var sensorObj = require('jsupm_vcap'); + + +/************** Main code **************/ + +var defaultDev = "/dev/video0"; + +// if an argument was specified, use it as the device instead +if (process.argv.length > 2) +{ + defaultDev = process.argv[2]; +} + +console.log("Using device " + defaultDev); +console.log("Initializing..."); + +// Instantiate an VCAP instance, using the specified video device +var sensor = new sensorObj.VCAP(defaultDev); + +// enable some debug/verbose output +sensor.setDebug(true); + +// This is just a hint. The kernel can change this to a lower +// resolution that the hardware supports. Use getWidth() and +// getHeight() methods to see what the kernel actually chose if you +// care. +sensor.setResolution(1920, 1080); + +// capture an image +sensor.captureImage(); + +// convert and save it as a jpeg +sensor.saveImage("video-img1.jpg"); + +// make sure we clean up +sensor = null; +sensorObj.cleanUp(); +sensorObj = null; +console.log("Exiting..."); +process.exit(0); + diff --git a/examples/python/vcap.py b/examples/python/vcap.py new file mode 100644 index 00000000..dfd23a46 --- /dev/null +++ b/examples/python/vcap.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +# Author: Jon Trulson +# Copyright (c) 2016 Intel Corporation. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import time, sys, signal, atexit +import pyupm_vcap as sensorObj + +## Exit handlers ## +# This function stops python from printing a stacktrace when you hit control-C +def SIGINTHandler(signum, frame): + raise SystemExit + +# This function lets you run code on exit +def exitHandler(): + print "Exiting..." + sys.exit(0) + +# Register exit handlers +atexit.register(exitHandler) +signal.signal(signal.SIGINT, SIGINTHandler) + +defaultDev = "/dev/video0" + +# if an argument was specified, use it as the device instead +if (len(sys.argv) > 1): + defaultDev = sys.argv[1] + +print "Using device", defaultDev +print "Initializing..." + +# Instantiate an VCAP instance, using the specified video device +sensor = sensorObj.VCAP(defaultDev) + +# enable some debug/verbose output +sensor.setDebug(True); + +# This is just a hint. The kernel can change this to a lower +# resolution that the hardware supports. Use getWidth() and +# getHeight() methods to see what the kernel actually chose if you +# care. +sensor.setResolution(1920, 1080); + +# capture an image +sensor.captureImage(); + +# convert and save it as a jpeg +sensor.saveImage("video-img1.jpg"); + diff --git a/src/upm.h b/src/upm.h index 5a454913..e287c828 100644 --- a/src/upm.h +++ b/src/upm.h @@ -221,6 +221,12 @@ * @ingroup bycat */ +/** + * @brief Provide video or video camera access + * @defgroup video Video + * @ingroup bycat + */ + /** * @brief Provide WiFi, Bluetooth, RF communication * @defgroup wifi Wireless Communication diff --git a/src/vcap/CMakeLists.txt b/src/vcap/CMakeLists.txt new file mode 100644 index 00000000..4d596443 --- /dev/null +++ b/src/vcap/CMakeLists.txt @@ -0,0 +1,18 @@ +set (libname "vcap") +set (libdescription "upm Video Frame Capture and image save utility") +set (module_src ${libname}.cxx) +set (module_h ${libname}.hpp) +set (reqlibname "jpeg") +upm_module_init() +target_link_libraries(${libname} jpeg) +if (BUILDSWIG) + if (BUILDSWIGNODE) + swig_link_libraries (jsupm_${libname} jpeg ${MRAA_LIBRARIES} ${NODE_LIBRARIES}) + endif() + if (BUILDSWIGPYTHON) + swig_link_libraries (pyupm_${libname} jpeg ${PYTHON_LIBRARIES} ${MRAA_LIBRARIES}) + endif() + if (BUILDSWIGJAVA) + swig_link_libraries (javaupm_${libname} jpeg ${MRAAJAVA_LDFLAGS} ${JAVA_LDFLAGS}) + endif() +endif() diff --git a/src/vcap/javaupm_vcap.i b/src/vcap/javaupm_vcap.i new file mode 100644 index 00000000..da116499 --- /dev/null +++ b/src/vcap/javaupm_vcap.i @@ -0,0 +1,19 @@ +%module javaupm_vcap +%include "../upm.i" +%include "std_string.i" + +%include "vcap.hpp" +%{ + #include "vcap.hpp" +%} + +%pragma(java) jniclasscode=%{ + static { + try { + System.loadLibrary("javaupm_vcap"); + } catch (UnsatisfiedLinkError e) { + System.err.println("Native code library failed to load. \n" + e); + System.exit(1); + } + } +%} diff --git a/src/vcap/jsupm_vcap.i b/src/vcap/jsupm_vcap.i new file mode 100644 index 00000000..0b5e5ebc --- /dev/null +++ b/src/vcap/jsupm_vcap.i @@ -0,0 +1,10 @@ +%module jsupm_vcap +%include "../upm.i" +%include "std_string.i" + +%include "vcap.hpp" +%{ + #include "vcap.hpp" +%} + + diff --git a/src/vcap/pyupm_vcap.i b/src/vcap/pyupm_vcap.i new file mode 100644 index 00000000..c56ec4b4 --- /dev/null +++ b/src/vcap/pyupm_vcap.i @@ -0,0 +1,14 @@ +// Include doxygen-generated documentation +%include "pyupm_doxy2swig.i" +%module pyupm_vcap +%include "../upm.i" +%include "std_string.i" + +%feature("autodoc", "3"); + +%include "vcap.hpp" +%{ + #include "vcap.hpp" +%} + + diff --git a/src/vcap/vcap.cxx b/src/vcap/vcap.cxx new file mode 100644 index 00000000..8a992cec --- /dev/null +++ b/src/vcap/vcap.cxx @@ -0,0 +1,524 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include "vcap.hpp" + +using namespace upm; +using namespace std; + +#define CLAMP(_val, _min, _max) \ + (((_val) < (_min)) ? (_min) : (((_val) > (_max)) ? (_max) : (_val))) + +VCAP::VCAP(string videoDev) : + m_buffer(0), m_fd(-1) +{ + memset(&m_caps, 0, sizeof(struct v4l2_capability)); + memset(&m_format, 0, sizeof(struct v4l2_format)); + + m_debugging = false; + m_bufferLen = 0; + m_videoDevice = videoDev; + setJPGQuality(VCAP_DEFAULT_JPEG_QUALITY); + + // try to open the video device, and set a default format. + if (!initVideoDevice()) + throw std::runtime_error(std::string(__FUNCTION__) + + ": initVideoDevice() failed"); + + m_height = 0; + m_width = 0; + m_imageCaptured = false; +} + +VCAP::~VCAP() +{ + releaseBuffer(); + + if (m_fd >= 0) + close(m_fd); + + m_fd = -1; +} + +bool VCAP::initVideoDevice() +{ + if (m_videoDevice.empty()) + return false; + + if ((m_fd = open(m_videoDevice.c_str(), O_RDWR)) < 0) + { + cerr << __FUNCTION__ << ": open failed: " << strerror(errno) << endl; + return false; + } + + if (!checkCapabilities()) + { + close(m_fd); + m_fd = -1; + return false; + } + + return true; +} + +// This seems... odd, but appears to be necessary. +// Ignore error and retry if the ioctl fails due to EINTR +int VCAP::xioctl(int fd, int request, void* argp) +{ + int r; + + do { + r = ioctl(fd, request, argp); + } + while (r == -1 && errno == EINTR); + + return r; +} + +bool VCAP::checkCapabilities() +{ + if (xioctl(m_fd, VIDIOC_QUERYCAP, &m_caps) < 0) + { + cerr << __FUNCTION__ << ": ioctl(VIDIOC_QUERYCAP) failed: " + << strerror(errno) << endl; + return false; + } + + if (m_debugging) + { + cerr << "Driver: " << m_caps.driver << endl; + cerr << "Device: " << m_caps.card << endl; + cerr << "Caps : 0x" << std::hex << m_caps.capabilities << std::dec + << endl; + } + + // see if capturing is supported + if (!(m_caps.capabilities & V4L2_CAP_VIDEO_CAPTURE)) + { + cerr << __FUNCTION__ << ": Device does not support video capture" + << endl; + return false; + } + + if (!(m_caps.capabilities & V4L2_CAP_STREAMING)) + { + cerr << __FUNCTION__ << ": Device does not support streaming I/O" + << endl; + return false; + } + + return true; +} + +bool VCAP::setResolution(int width, int height) +{ + // in case we already created one + releaseBuffer(); + + m_width = width; + m_height = height; + + m_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + // initialize with the current format + if (xioctl(m_fd, VIDIOC_G_FMT, &m_format) < 0) + { + cerr << __FUNCTION__ << ": ioctl(VIDIOC_G_FMT) failed: " + << strerror(errno) << endl; + return false; + } + + // make our changes... + m_format.fmt.pix.width = m_width; + m_format.fmt.pix.height = m_height; + m_format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; + m_format.fmt.pix.field = V4L2_FIELD_ANY; + + if (xioctl(m_fd, VIDIOC_S_FMT, &m_format) < 0) + { + cerr << __FUNCTION__ << ": ioctl(VIDIOC_S_FMT) failed: " + << strerror(errno) << endl; + + // If it's just busy, then this still might work, so don't fail here + if (errno != EBUSY) + return false; + } + + // Now retrieve the driver's selected format and check it - + // specifically, the width and height might change, causing + // coredumps if we don't adjust them accordingly. + + if (xioctl(m_fd, VIDIOC_G_FMT, &m_format) < 0) + { + cerr << __FUNCTION__ << ": ioctl(VIDIOC_G_FMT) failed: " + << strerror(errno) << endl; + return false; + } + + // G_FMT will have adjusted these if neccessary, so verify + if (m_format.fmt.pix.width != m_width) + { + if (m_debugging) + cerr << __FUNCTION__ << ": Warning: Selected width " + << std::to_string(m_width) + << " adjusted by driver to " + << std::to_string(m_format.fmt.pix.width) + << endl; + + m_width = m_format.fmt.pix.width; + } + + if (m_format.fmt.pix.height != m_height) + { + if (m_debugging) + cerr << __FUNCTION__ << ": Warning: Selected height " + << std::to_string(m_height) + << " adjusted by driver to " + << std::to_string(m_format.fmt.pix.height) + << endl; + + m_height = m_format.fmt.pix.height; + } + + // now alloc the buffers here + if (!allocBuffer()) + return false; + + return true; +} + +bool VCAP::allocBuffer() +{ + struct v4l2_requestbuffers rb; + memset(&rb, 0, sizeof(rb)); + + // we just want one buffer, and we only support mmap(). + rb.count = 1; + rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + rb.memory = V4L2_MEMORY_MMAP; + + if (xioctl(m_fd, VIDIOC_REQBUFS, &rb) < 0) + { + if (errno == EINVAL) + { + cerr << __FUNCTION__ << ": Capture device does not support mmapped " + << "buffers" + << endl; + } + cerr << __FUNCTION__ << ": ioctl(VIDIOC_REQBUFS) failed: " + << strerror(errno) << endl; + + return false; + } + + // get the buffer and mmap it + struct v4l2_buffer mbuf; + memset(&mbuf, 0, sizeof(mbuf)); + + mbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + mbuf.memory = V4L2_MEMORY_MMAP; + mbuf.index = 0; + + if (xioctl(m_fd, VIDIOC_QUERYBUF, &mbuf) < 0) + { + cerr << __FUNCTION__ << ": ioctl(VIDIOC_QUERYBUF) failed: " + << strerror(errno) << endl; + return false; + } + + // map it + m_buffer = (unsigned char *)mmap(NULL, mbuf.length, + PROT_READ | PROT_WRITE, MAP_SHARED, + m_fd, mbuf.m.offset); + + if (m_buffer == MAP_FAILED) + { + cerr << __FUNCTION__ << ": mmap() failed: " + << strerror(errno) << endl; + return false; + } + + // we'll need this when unmapping + m_bufferLen = mbuf.length; + + return true; +} + +void VCAP::releaseBuffer() +{ + // first unmap any buffers + if (m_buffer) + munmap(m_buffer, m_bufferLen); + + m_buffer = 0; + m_bufferLen = 0; + + // then, tell the kernel driver to free any allocated buffer(s)... + struct v4l2_requestbuffers rb; + memset(&rb, 0, sizeof(rb)); + + rb.count = 0; + rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + rb.memory = V4L2_MEMORY_MMAP; + + if (xioctl(m_fd, VIDIOC_REQBUFS, &rb) < 0) + { + cerr << __FUNCTION__ << ": ioctl(VIDIOC_REQBUFS) failed while freeing: " + << strerror(errno) << endl; + } + + // reset captured flag + m_imageCaptured = false; +} + + +bool VCAP::YUYV2JPEG(FILE *file) +{ + struct jpeg_compress_struct jpgInfo; + struct jpeg_error_mgr jerr; + JSAMPROW row_pointer[1]; + unsigned char *row_buffer = NULL; + unsigned char *yuyv = NULL; + int z; + + row_buffer = (unsigned char *)calloc(m_width * 3, 1); + if (!row_buffer) + { + cerr << __FUNCTION__ << ": allocation of line buffer failed." + << endl; + return false; + } + + yuyv = m_buffer; + + jpgInfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&jpgInfo); + jpeg_stdio_dest(&jpgInfo, file); + + jpgInfo.image_width = m_width; + jpgInfo.image_height = m_height; + + // components R, G, B + jpgInfo.input_components = 3; + jpgInfo.in_color_space = JCS_RGB; + + jpeg_set_defaults(&jpgInfo); + jpeg_set_quality(&jpgInfo, m_jpgQuality, TRUE); + + jpeg_start_compress(&jpgInfo, TRUE); + + z = 0; + + while (jpgInfo.next_scanline < jpgInfo.image_height) + { + int x; + unsigned char *ptr = row_buffer; + + for (x = 0; x < m_width; x++) + { + int r, g, b; + int y, u, v; + + if (!z) + y = yuyv[0] << 8; + else + y = yuyv[2] << 8; + u = yuyv[1] - 128; + v = yuyv[3] - 128; + + r = (y + (359 * v)) >> 8; + g = (y - (88 * u) - (183 * v)) >> 8; + b = (y + (454 * u)) >> 8; + + *(ptr++) = CLAMP(r, 0, 255); + *(ptr++) = CLAMP(g, 0, 255); + *(ptr++) = CLAMP(b, 0, 255); + + if (z++) + { + z = 0; + yuyv += 4; + } + } + + row_pointer[0] = row_buffer; + jpeg_write_scanlines(&jpgInfo, row_pointer, 1); + } + + jpeg_finish_compress(&jpgInfo); + jpeg_destroy_compress(&jpgInfo); + + free(row_buffer); + + return true; +} + +bool VCAP::saveImage(string filename) +{ + // check m_buffer to make sure we have an actual buffer... If not, + // we throw here. + if (!m_buffer) + { + throw std::runtime_error(std::string(__FUNCTION__) + + ": no buffer. Call setResolution() first"); + } + + // if we haven't done at least one capture yet... + if (!m_imageCaptured) + { + throw std::runtime_error(std::string(__FUNCTION__) + + ": No data, call captureImage() first"); + } + + FILE *file; + if ((file = fopen(filename.c_str(), "wb")) == NULL) + { + cerr << __FUNCTION__ << ": fopen() failed: " + << strerror(errno) << endl; + return false; + } + + YUYV2JPEG(file); + fclose(file); + + if (m_debugging) + cerr << __FUNCTION__ << ": Saved image to " << filename << endl; + + return true; +} + +bool VCAP::captureImage() +{ + // first, make sure a resolution was specified. If not, set the + // default + if (m_width == 0 || m_height == 0) + { + if (!setResolution(VCAP_DEFAULT_WIDTH, VCAP_DEFAULT_HEIGHT)) + throw std::runtime_error(std::string(__FUNCTION__) + + ": setResolution() failed"); + } + + // we basically just call doCaptureImage() twice - once to grab and + // discard the first frame (which is usually a remnent of a previous + // capture), and another to grab the real frame we are interesed in. + + if (!doCaptureImage()) + { + cerr << __FUNCTION__ << ": capture of first frame failed" + << endl; + } + + return doCaptureImage(); +} + + +// the real workhorse +bool VCAP::doCaptureImage() +{ + struct v4l2_buffer buf = {0}; + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = 0; + + // queue our buffer + if (xioctl(m_fd, VIDIOC_QBUF, &buf) < 0) + { + cerr << __FUNCTION__ << ": ioctl(VIDIOC_QBUF) failed: " + << strerror(errno) << endl; + + return false; + } + + // enable streaming + if (xioctl(m_fd, VIDIOC_STREAMON, &buf.type) < 0) + { + cerr << __FUNCTION__ << ": ioctl(VIDIOC_STREAMON) failed: " + << strerror(errno) << endl; + + return false; + } + + // use select to wait for a complete frame. + fd_set fds; + + FD_ZERO(&fds); + FD_SET(m_fd, &fds); + + struct timeval tv; + memset(&tv, 0, sizeof(tv)); + + // 5 seconds should be more than enough + tv.tv_sec = 5; + + int rv; + if ((rv = select(m_fd + 1, &fds, NULL, NULL, &tv)) < 0) + { + cerr << __FUNCTION__ << ": select() failed: " + << strerror(errno) << endl; + return false; + } + + if (!rv) + { + // timed out + cerr << __FUNCTION__ << ": select() timed out waiting for frame" + << endl; + + return false; + } + + // de-queue the buffer, we're now free to access it via the mmapped + // ptr (m_buffer) + if (xioctl(m_fd, VIDIOC_DQBUF, &buf) < 0) + { + cerr << __FUNCTION__ << ": ioctl(VIDIOC_DQBUF) failed: " + << strerror(errno) << endl; + + return false; + } + + // turn off streaming + if (xioctl(m_fd, VIDIOC_STREAMOFF, &buf.type) < 0) + { + cerr << __FUNCTION__ << ": ioctl(VIDIOC_STREAMOFF) failed: " + << strerror(errno) << endl; + + return false; + } + + m_imageCaptured = true; + + return true; +} + + void VCAP::setJPGQuality(unsigned int qual) + { + m_jpgQuality = CLAMP(qual, 0, 100); + } diff --git a/src/vcap/vcap.hpp b/src/vcap/vcap.hpp new file mode 100644 index 00000000..9b222d76 --- /dev/null +++ b/src/vcap/vcap.hpp @@ -0,0 +1,214 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +#define VCAP_DEFAULT_VIDEODEV "/dev/video0" +#define VCAP_DEFAULT_OUTPUTFILE "vcap.jpg" +#define VCAP_DEFAULT_WIDTH 640 +#define VCAP_DEFAULT_HEIGHT 480 +#define VCAP_DEFAULT_JPEG_QUALITY 99 + +namespace upm { + /** + * @brief Take a snapshot from a video camera and save as a JPEG + * @defgroup vcap libupm-vcap + * @ingroup video + */ + + /** + * @library vcap + * @sensor vcap + * @comname Video Capture + * @type video + * + * @brief API for the Video Capture driver + * + * This UPM module captures a still frame from a Linux V4L device, + * such as a USB webcam, and and then allows you to save it as a + * JPEG image into a file. + * + * The camera and driver in use must support streaming, mmap-able + * buffers and must provide data in YUYV format. This should + * encompass most video cameras out there. It has been tested + * with a few off the shelf cameras without any problems. + * + * @snippet vcap.cxx Interesting + */ + + class VCAP { + public: + + /** + * VCAP object constructor + * + * @param videoDev The path to the video device, default is /dev/video0. + */ + VCAP(std::string videoDev=VCAP_DEFAULT_VIDEODEV); + + /** + * VCAP object destructor + */ + ~VCAP(); + + /** + * Set the desired resolution of the output image. Note, this is + * a hint to the underlying video driver. The video driver is + * free to lower the specified resolution if the hardware cannot + * support it. You can use getHeight() and getWidth() after + * calling this method to see what the video driver chose. + * + * @param width The desired width of the image. + * @param width The desired height of the image. + * @return true if the operation succeeded, false otherwise. + */ + bool setResolution(int width, int height); + + /** + * Capture an image from the camera. + * + * @return true if the operation succeeded, false otherwise. + */ + bool captureImage(); + + /** + * Save the captured image (created with captureImage()) to a file + * in JPEG format. The file will be overwritten if it already + * exists. + * + * @param filename The name of the file in which to store the image. + * @return true if the operation succeeded, false otherwise. + */ + bool saveImage(std::string filename=VCAP_DEFAULT_OUTPUTFILE); + + /** + * Return the current width of the image. You can use this method + * to determine if the video driver downgraded it after a call to + * setResolution(). + * + * @return true Current width of capture. + */ + int getWidth() const + { + return m_width; + }; + + /** + * Return the current height of the image. You can use this method + * to determine if the video driver downgraded it after a call to + * setResolution(). + * + * @return true Current height of capture. + */ + int getHeight() const + { + return m_height; + }; + + /** + * Set the JPEG quality. + * + * @param quality A number between 0-100, with higher numbers + * meaning higher quality. Numbers less than 0 will be clamped to + * 0, numbers higher than 100 will be clamped to 100. + */ + void setJPGQuality(unsigned int quality); + + /** + * Get the current JPEG quality setting. + * + * @return the current JPEG quality setting. + */ + int getJPGQuality() const + { + return m_jpgQuality; + }; + + /** + * Enable or disable debugging output. + * + * @param enable true to enable debugging, false otherwise + */ + void setDebug(bool enable) + { + m_debugging = enable; + }; + + protected: + // open the device and check that it meats minimum requirements + bool initVideoDevice(); + + // make sure device is streamable, supports mmap and capture + bool checkCapabilities(); + + // read the mmapped buffer in YUYV format and create a jpeg image + bool YUYV2JPEG(FILE *file); + + // buffer management + bool allocBuffer(); + void releaseBuffer(); + + // does the actual capture + bool doCaptureImage(); + + private: + // internal ioctl + int xioctl(int fd, int request, void* argp); + + std::string m_videoDevice; + + // our file descriptor to the video device + int m_fd; + + // v4l info + struct v4l2_capability m_caps; + struct v4l2_format m_format; + + // our mmaped buffer + unsigned char *m_buffer; + size_t m_bufferLen; + + // the resolution and quality + int m_width; + int m_height; + int m_jpgQuality; + + // at least one image captured with current settings? + bool m_imageCaptured; + + // are we debugging? + bool m_debugging; + }; +} + +