From e474c90fcd4aae038c743d9f8682050b34cec1a8 Mon Sep 17 00:00:00 2001 From: johan Date: Sat, 4 Jan 2014 13:24:05 +0100 Subject: [PATCH 01/21] V4L2 test added Former-commit-id: eec49d94ffbb0e1be0639e9cbbebba5955c79f4d --- CMakeLists.txt | 5 +- HyperionConfig.h.in | 3 + test/CMakeLists.txt | 4 + test/v4l2png/CMakeLists.txt | 17 ++ test/v4l2png/V4L2Grabber.cpp | 531 +++++++++++++++++++++++++++++++++++ test/v4l2png/V4L2Grabber.h | 62 ++++ test/v4l2png/v4l2png.cpp | 48 ++++ 7 files changed, 669 insertions(+), 1 deletion(-) create mode 100644 test/v4l2png/CMakeLists.txt create mode 100644 test/v4l2png/V4L2Grabber.cpp create mode 100644 test/v4l2png/V4L2Grabber.h create mode 100644 test/v4l2png/v4l2png.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 99d3baf3..3d0056c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,9 @@ cmake_minimum_required(VERSION 2.8) option (ENABLE_DISPMANX "Enable the RPi dispmanx grabber" ON) message(STATUS "ENABLE_DISPMANX = " ${ENABLE_DISPMANX}) +option (ENABLE_V4L2 "Enable the V4L2 grabber" ON) +message(STATUS "ENABLE_V4L2 = " ${ENABLE_V4L2}) + # Createt the configuration file # configure a header file to pass some of the CMake settings # to the source code @@ -39,7 +42,7 @@ include_directories(${CMAKE_SOURCE_DIR}/include) set(CMAKE_BUILD_TYPE "Release") # enable C++11 -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall -g") # Configure the use of QT4 find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED QUIET) diff --git a/HyperionConfig.h.in b/HyperionConfig.h.in index 875e1193..16bb5776 100644 --- a/HyperionConfig.h.in +++ b/HyperionConfig.h.in @@ -2,3 +2,6 @@ // Define to enable the dispmanx grabber #cmakedefine ENABLE_DISPMANX + +// Define to enable the v4l2 grabber +#cmakedefine ENABLE_V4L2 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 45f99a5e..0d511d7c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -31,6 +31,10 @@ if (ENABLE_DISPMANX) add_subdirectory(dispmanx2png) endif (ENABLE_DISPMANX) +if (ENABLE_V4L2) + add_subdirectory(v4l2png) +endif (ENABLE_V4L2) + add_executable(test_blackborderdetector TestBlackBorderDetector.cpp) target_link_libraries(test_blackborderdetector diff --git a/test/v4l2png/CMakeLists.txt b/test/v4l2png/CMakeLists.txt new file mode 100644 index 00000000..dd0cba27 --- /dev/null +++ b/test/v4l2png/CMakeLists.txt @@ -0,0 +1,17 @@ + +# find Qt4 +find_package(Qt4 REQUIRED QtCore QtGui) + +include_directories(${QT_INCLUDES}) + +add_executable(v4l_to_png + v4l2png.cpp + V4L2Grabber.h + V4L2Grabber.cpp) + +target_link_libraries(v4l_to_png + getoptPlusPlus) + +qt4_use_modules(v4l_to_png + Core + Gui) diff --git a/test/v4l2png/V4L2Grabber.cpp b/test/v4l2png/V4L2Grabber.cpp new file mode 100644 index 00000000..bee23e30 --- /dev/null +++ b/test/v4l2png/V4L2Grabber.cpp @@ -0,0 +1,531 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "V4L2Grabber.h" + +#define CLEAR(x) memset(&(x), 0, sizeof(x)) + +V4L2Grabber::V4L2Grabber() : + _deviceName("/dev/video0"), + _ioMethod(IO_METHOD_MMAP), + _fileDescriptor(-1), + _buffers() +{ +} + +V4L2Grabber::~V4L2Grabber() +{ +} + +void V4L2Grabber::start() +{ + open_device(); + init_device(); + start_capturing(); + + int count = 100; + while (count-- > 0) { + for (;;) { + fd_set fds; + struct timeval tv; + int r; + + FD_ZERO(&fds); + FD_SET(_fileDescriptor, &fds); + + /* Timeout. */ + tv.tv_sec = 2; + tv.tv_usec = 0; + + r = select(_fileDescriptor + 1, &fds, NULL, NULL, &tv); + + if (-1 == r) + { + if (EINTR == errno) + continue; + errno_exit("select"); + } + + if (0 == r) + { + fprintf(stderr, "select timeout\n"); + exit(EXIT_FAILURE); + } + + if (read_frame()) + break; + /* EAGAIN - continue select loop. */ + } + } + + stop_capturing(); + uninit_device(); + close_device(); +} + +void V4L2Grabber::open_device() +{ + struct stat st; + + if (-1 == stat(_deviceName.c_str(), &st)) + { + fprintf(stderr, "Cannot identify '%s': %d, %s\n", _deviceName.c_str(), errno, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (!S_ISCHR(st.st_mode)) + { + fprintf(stderr, "%s is no device\n", _deviceName.c_str()); + exit(EXIT_FAILURE); + } + + _fileDescriptor = open(_deviceName.c_str(), O_RDWR /* required */ | O_NONBLOCK, 0); + + if (-1 == _fileDescriptor) + { + fprintf(stderr, "Cannot open '%s': %d, %s\n", _deviceName.c_str(), errno, strerror(errno)); + exit(EXIT_FAILURE); + } +} + +void V4L2Grabber::close_device() +{ + if (-1 == close(_fileDescriptor)) + errno_exit("close"); + + _fileDescriptor = -1; +} + +void V4L2Grabber::init_read(unsigned int buffer_size) +{ + _buffers.resize(1); + + _buffers[0].length = buffer_size; + _buffers[0].start = malloc(buffer_size); + + if (!_buffers[0].start) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } +} + +void V4L2Grabber::init_mmap() +{ + struct v4l2_requestbuffers req; + + CLEAR(req); + + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl(VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) { + fprintf(stderr, "%s does not support memory mapping\n", _deviceName.c_str()); + exit(EXIT_FAILURE); + } else { + errno_exit("VIDIOC_REQBUFS"); + } + } + + if (req.count < 2) { + fprintf(stderr, "Insufficient buffer memory on %s\n", _deviceName.c_str()); + exit(EXIT_FAILURE); + } + + _buffers.resize(req.count); + + for (size_t n_buffers = 0; n_buffers < req.count; ++n_buffers) { + struct v4l2_buffer buf; + + CLEAR(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = n_buffers; + + if (-1 == xioctl(VIDIOC_QUERYBUF, &buf)) + errno_exit("VIDIOC_QUERYBUF"); + + _buffers[n_buffers].length = buf.length; + _buffers[n_buffers].start = + mmap(NULL /* start anywhere */, + buf.length, + PROT_READ | PROT_WRITE /* required */, + MAP_SHARED /* recommended */, + _fileDescriptor, buf.m.offset); + + if (MAP_FAILED == _buffers[n_buffers].start) + errno_exit("mmap"); + } +} + +void V4L2Grabber::init_userp(unsigned int buffer_size) +{ + struct v4l2_requestbuffers req; + + CLEAR(req); + + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_USERPTR; + + if (-1 == xioctl(VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) + { + fprintf(stderr, "%s does not support user pointer i/o\n", _deviceName.c_str()); + exit(EXIT_FAILURE); + } else { + errno_exit("VIDIOC_REQBUFS"); + } + } + + _buffers.resize(4); + + for (size_t n_buffers = 0; n_buffers < 4; ++n_buffers) { + _buffers[n_buffers].length = buffer_size; + _buffers[n_buffers].start = malloc(buffer_size); + + if (!_buffers[n_buffers].start) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } + } +} + +void V4L2Grabber::init_device() +{ + struct v4l2_capability cap; + struct v4l2_cropcap cropcap; + struct v4l2_crop crop; + struct v4l2_format fmt; + + if (-1 == xioctl(VIDIOC_QUERYCAP, &cap)) + { + if (EINVAL == errno) { + fprintf(stderr, "%s is no V4L2 device\n", _deviceName.c_str()); + exit(EXIT_FAILURE); + } else { + errno_exit("VIDIOC_QUERYCAP"); + } + } + + if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) + { + fprintf(stderr, "%s is no video capture device\n", _deviceName.c_str()); + exit(EXIT_FAILURE); + } + + switch (_ioMethod) { + case IO_METHOD_READ: + if (!(cap.capabilities & V4L2_CAP_READWRITE)) + { + fprintf(stderr, "%s does not support read i/o\n", _deviceName.c_str()); + exit(EXIT_FAILURE); + } + break; + + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + if (!(cap.capabilities & V4L2_CAP_STREAMING)) + { + fprintf(stderr, "%s does not support streaming i/o\n", _deviceName.c_str()); + exit(EXIT_FAILURE); + } + break; + } + + + /* Select video input, video standard and tune here. */ + + + CLEAR(cropcap); + + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (0 == xioctl(VIDIOC_CROPCAP, &cropcap)) { + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop.c = cropcap.defrect; /* reset to default */ + + if (-1 == xioctl(VIDIOC_S_CROP, &crop)) { + switch (errno) { + case EINVAL: + /* Cropping not supported. */ + break; + default: + /* Errors ignored. */ + break; + } + } + } else { + /* Errors ignored. */ + } + + + CLEAR(fmt); + + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* Preserve original settings as set by v4l2-ctl for example */ + if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) + { + errno_exit("VIDIOC_G_FMT"); + } + +// /* Buggy driver paranoia. */ +// min = fmt.fmt.pix.width * 2; +// if (fmt.fmt.pix.bytesperline < min) +// fmt.fmt.pix.bytesperline = min; +// min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; +// if (fmt.fmt.pix.sizeimage < min) +// fmt.fmt.pix.sizeimage = min; + + switch (_ioMethod) { + case IO_METHOD_READ: + init_read(fmt.fmt.pix.sizeimage); + break; + + case IO_METHOD_MMAP: + init_mmap(); + break; + + case IO_METHOD_USERPTR: + init_userp(fmt.fmt.pix.sizeimage); + break; + } +} + +void V4L2Grabber::uninit_device() +{ + switch (_ioMethod) { + case IO_METHOD_READ: + free(_buffers[0].start); + break; + + case IO_METHOD_MMAP: + for (size_t i = 0; i < _buffers.size(); ++i) + if (-1 == munmap(_buffers[i].start, _buffers[i].length)) + errno_exit("munmap"); + break; + + case IO_METHOD_USERPTR: + for (size_t i = 0; i < _buffers.size(); ++i) + free(_buffers[i].start); + break; + } + + _buffers.resize(0); +} + +void V4L2Grabber::start_capturing() +{ + switch (_ioMethod) { + case IO_METHOD_READ: + /* Nothing to do. */ + break; + + case IO_METHOD_MMAP: + { + for (size_t i = 0; i < _buffers.size(); ++i) { + struct v4l2_buffer buf; + + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + if (-1 == xioctl(VIDIOC_QBUF, &buf)) + errno_exit("VIDIOC_QBUF"); + } + v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(VIDIOC_STREAMON, &type)) + errno_exit("VIDIOC_STREAMON"); + break; + } + case IO_METHOD_USERPTR: + { + for (size_t i = 0; i < _buffers.size(); ++i) { + struct v4l2_buffer buf; + + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + buf.index = i; + buf.m.userptr = (unsigned long)_buffers[i].start; + buf.length = _buffers[i].length; + + if (-1 == xioctl(VIDIOC_QBUF, &buf)) + errno_exit("VIDIOC_QBUF"); + } + v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(VIDIOC_STREAMON, &type)) + errno_exit("VIDIOC_STREAMON"); + break; + } + } +} + +void V4L2Grabber::stop_capturing() +{ + enum v4l2_buf_type type; + + switch (_ioMethod) { + case IO_METHOD_READ: + /* Nothing to do. */ + break; + + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(VIDIOC_STREAMOFF, &type)) + errno_exit("VIDIOC_STREAMOFF"); + break; + } +} + +int V4L2Grabber::read_frame() +{ + struct v4l2_buffer buf; + + switch (_ioMethod) { + case IO_METHOD_READ: + if (-1 == read(_fileDescriptor, _buffers[0].start, _buffers[0].length)) { + switch (errno) { + case EAGAIN: + return 0; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + errno_exit("read"); + } + } + + process_image(_buffers[0].start, _buffers[0].length); + break; + + case IO_METHOD_MMAP: + CLEAR(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl(VIDIOC_DQBUF, &buf)) { + switch (errno) { + case EAGAIN: + return 0; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + errno_exit("VIDIOC_DQBUF"); + } + } + + assert(buf.index < _buffers.size()); + + process_image(_buffers[buf.index].start, buf.bytesused); + + if (-1 == xioctl(VIDIOC_QBUF, &buf)) + errno_exit("VIDIOC_QBUF"); + break; + + case IO_METHOD_USERPTR: + CLEAR(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + + if (-1 == xioctl(VIDIOC_DQBUF, &buf)) { + switch (errno) { + case EAGAIN: + return 0; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + errno_exit("VIDIOC_DQBUF"); + } + } + + for (size_t i = 0; i < _buffers.size(); ++i) + if (buf.m.userptr == (unsigned long)_buffers[i].start + && buf.length == _buffers[i].length) + break; + + process_image((void *)buf.m.userptr, buf.bytesused); + + if (-1 == xioctl(VIDIOC_QBUF, &buf)) + errno_exit("VIDIOC_QBUF"); + break; + } + + return 1; +} + +void V4L2Grabber::process_image(const void *p, int size) +{ + if (size != 2*720*480) + { + std::cout << "Frame too small: " << size << "<" << (2*720*480) << std::endl; + return; + } + + std::cout << "process image of size = " << size << std::endl; + + const uint8_t * data = reinterpret_cast(p); + + + QImage image(720, 480, QImage::Format_RGB888); + + for (int y = 0; y < image.height(); ++y) + { + for (int x = 0; x < image.width(); ++x) + { + uint8_t value = data[(720 * y + x) * 2 + 1]; + image.setPixel(x, y, qRgb(value, value, value)); + } + } + + image.save("/home/pi/screenshot.png"); +} + +int V4L2Grabber::xioctl(int request, void *arg) +{ + int r; + + do { + r = ioctl(_fileDescriptor, request, arg); + } while (-1 == r && EINTR == errno); + + return r; +} + +void V4L2Grabber::errno_exit(const char *s) +{ + fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno)); + exit(EXIT_FAILURE); +} diff --git a/test/v4l2png/V4L2Grabber.h b/test/v4l2png/V4L2Grabber.h new file mode 100644 index 00000000..1484973d --- /dev/null +++ b/test/v4l2png/V4L2Grabber.h @@ -0,0 +1,62 @@ +#pragma once + +// stl includes +#include +#include + +/// Capture class for V4L2 devices +/// +/// @see http://linuxtv.org/downloads/v4l-dvb-apis/capture-example.html +class V4L2Grabber +{ +public: + V4L2Grabber(); + virtual ~V4L2Grabber(); + + void start(); + +private: + void open_device(); + + void close_device(); + + void init_read(unsigned int buffer_size); + + void init_mmap(); + + void init_userp(unsigned int buffer_size); + + void init_device(); + + void uninit_device(); + + void start_capturing(); + + void stop_capturing(); + + int read_frame(); + + void process_image(const void *p, int size); + + int xioctl(int request, void *arg); + + void errno_exit(const char *s); + +private: + enum io_method { + IO_METHOD_READ, + IO_METHOD_MMAP, + IO_METHOD_USERPTR + }; + + struct buffer { + void *start; + size_t length; + }; + +private: + const std::string _deviceName; + const io_method _ioMethod; + int _fileDescriptor; + std::vector _buffers; +}; diff --git a/test/v4l2png/v4l2png.cpp b/test/v4l2png/v4l2png.cpp new file mode 100644 index 00000000..a2e077ff --- /dev/null +++ b/test/v4l2png/v4l2png.cpp @@ -0,0 +1,48 @@ + +// STL includes +#include +#include + +// QT includes +#include + +// getoptPlusPLus includes +#include + +#include "V4L2Grabber.h" + +using namespace vlofgren; + +int main(int argc, char** argv) +{ + try + { + // create the option parser and initialize all parameters + OptionsParser optionParser("Simple application to send a command to hyperion using the Json interface"); + ParameterSet & parameters = optionParser.getParameters(); + + SwitchParameter<> & argHelp = parameters.add >('h', "help", "Show this help message and exit"); + + // parse all options + optionParser.parse(argc, const_cast(argv)); + + // check if we need to display the usage. exit if we do. + if (argHelp.isSet()) + { + optionParser.usage(); + return 0; + } + } + catch (const std::runtime_error & e) + { + // An error occured. Display error and quit + std::cerr << e.what() << std::endl; + return 1; + } + + V4L2Grabber grabber; + grabber.start(); + + return 0; +} + From 558ad6a11f8d10d7cd09bb795c81e29a5241ad6b Mon Sep 17 00:00:00 2001 From: johan Date: Sat, 11 Jan 2014 20:43:55 +0100 Subject: [PATCH 02/21] Added setting of PAL/NTSC/NO_CHANGE and input Former-commit-id: aee087b65b122e34e46b15854b94c720cbbec6b2 --- test/v4l2png/V4L2Grabber.cpp | 229 ++++++++++++++++++++++------------- test/v4l2png/V4L2Grabber.h | 20 ++- test/v4l2png/v4l2png.cpp | 5 +- 3 files changed, 166 insertions(+), 88 deletions(-) diff --git a/test/v4l2png/V4L2Grabber.cpp b/test/v4l2png/V4L2Grabber.cpp index bee23e30..03da150d 100644 --- a/test/v4l2png/V4L2Grabber.cpp +++ b/test/v4l2png/V4L2Grabber.cpp @@ -19,62 +19,70 @@ #define CLEAR(x) memset(&(x), 0, sizeof(x)) -V4L2Grabber::V4L2Grabber() : - _deviceName("/dev/video0"), +V4L2Grabber::V4L2Grabber(const std::string &device, int input, VideoStandard videoStandard, double fps) : + _deviceName(device), _ioMethod(IO_METHOD_MMAP), _fileDescriptor(-1), - _buffers() + _buffers(), + _width(0), + _height(0) { + open_device(); + init_device(videoStandard, input); } V4L2Grabber::~V4L2Grabber() { + uninit_device(); + close_device(); } void V4L2Grabber::start() { - open_device(); - init_device(); start_capturing(); +} - int count = 100; - while (count-- > 0) { - for (;;) { - fd_set fds; - struct timeval tv; - int r; +void V4L2Grabber::capture() +{ + int count = 1; + while (count-- > 0) { + for (;;) { + fd_set fds; + struct timeval tv; + int r; - FD_ZERO(&fds); - FD_SET(_fileDescriptor, &fds); + FD_ZERO(&fds); + FD_SET(_fileDescriptor, &fds); - /* Timeout. */ - tv.tv_sec = 2; - tv.tv_usec = 0; + /* Timeout. */ + tv.tv_sec = 2; + tv.tv_usec = 0; - r = select(_fileDescriptor + 1, &fds, NULL, NULL, &tv); + r = select(_fileDescriptor + 1, &fds, NULL, NULL, &tv); - if (-1 == r) - { - if (EINTR == errno) - continue; - errno_exit("select"); - } + if (-1 == r) + { + if (EINTR == errno) + continue; + errno_exit("select"); + } - if (0 == r) - { - fprintf(stderr, "select timeout\n"); - exit(EXIT_FAILURE); - } + if (0 == r) + { + fprintf(stderr, "select timeout\n"); + exit(EXIT_FAILURE); + } - if (read_frame()) - break; - /* EAGAIN - continue select loop. */ - } - } + if (read_frame()) + break; + /* EAGAIN - continue select loop. */ + } + } +} - stop_capturing(); - uninit_device(); - close_device(); +void V4L2Grabber::stop() +{ + stop_capturing(); } void V4L2Grabber::open_device() @@ -207,14 +215,10 @@ void V4L2Grabber::init_userp(unsigned int buffer_size) } } -void V4L2Grabber::init_device() +void V4L2Grabber::init_device(VideoStandard videoStandard, int input) { struct v4l2_capability cap; - struct v4l2_cropcap cropcap; - struct v4l2_crop crop; - struct v4l2_format fmt; - - if (-1 == xioctl(VIDIOC_QUERYCAP, &cap)) + if (-1 == xioctl(VIDIOC_QUERYCAP, &cap)) { if (EINVAL == errno) { fprintf(stderr, "%s is no V4L2 device\n", _deviceName.c_str()); @@ -252,13 +256,14 @@ void V4L2Grabber::init_device() /* Select video input, video standard and tune here. */ - + struct v4l2_cropcap cropcap; CLEAR(cropcap); cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (0 == xioctl(VIDIOC_CROPCAP, &cropcap)) { - crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + struct v4l2_crop crop; + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; crop.c = cropcap.defrect; /* reset to default */ if (-1 == xioctl(VIDIOC_S_CROP, &crop)) { @@ -275,24 +280,61 @@ void V4L2Grabber::init_device() /* Errors ignored. */ } + // set input if needed + if (input >= 0) + { + if (-1 == xioctl(VIDIOC_S_INPUT, &input)) + { + errno_exit("VIDIOC_S_INPUT"); + } + } + // set the video standard if needed + switch (videoStandard) + { + case PAL: + { + v4l2_std_id std_id = V4L2_STD_PAL; + if (-1 == xioctl(VIDIOC_S_STD, &std_id)) + { + errno_exit("VIDIOC_S_STD"); + } + } + break; + case NTSC: + { + v4l2_std_id std_id = V4L2_STD_NTSC; + if (-1 == xioctl(VIDIOC_S_STD, &std_id)) + { + errno_exit("VIDIOC_S_STD"); + } + } + break; + case NO_CHANGE: + default: + // No change to device settings + break; + } + + // get the current settings + struct v4l2_format fmt; CLEAR(fmt); - fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - - /* Preserve original settings as set by v4l2-ctl for example */ if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) { - errno_exit("VIDIOC_G_FMT"); + errno_exit("VIDIOC_G_FMT"); } -// /* Buggy driver paranoia. */ -// min = fmt.fmt.pix.width * 2; -// if (fmt.fmt.pix.bytesperline < min) -// fmt.fmt.pix.bytesperline = min; -// min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; -// if (fmt.fmt.pix.sizeimage < min) -// fmt.fmt.pix.sizeimage = min; + // check pixel format + if (fmt.fmt.pix.pixelformat != V4L2_PIX_FMT_UYVY) + { + exit(EXIT_FAILURE); + } + + // store width & height + _width = fmt.fmt.pix.width; + _height = fmt.fmt.pix.height; + std::cout << "V4L2 width=" << _width << " height=" << _height << std::endl; switch (_ioMethod) { case IO_METHOD_READ: @@ -403,8 +445,11 @@ int V4L2Grabber::read_frame() switch (_ioMethod) { case IO_METHOD_READ: - if (-1 == read(_fileDescriptor, _buffers[0].start, _buffers[0].length)) { - switch (errno) { + int size; + if ((size = read(_fileDescriptor, _buffers[0].start, _buffers[0].length)) == -1) + { + switch (errno) + { case EAGAIN: return 0; @@ -418,7 +463,7 @@ int V4L2Grabber::read_frame() } } - process_image(_buffers[0].start, _buffers[0].length); + process_image(_buffers[0].start, size); break; case IO_METHOD_MMAP: @@ -427,8 +472,10 @@ int V4L2Grabber::read_frame() buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; - if (-1 == xioctl(VIDIOC_DQBUF, &buf)) { - switch (errno) { + if (-1 == xioctl(VIDIOC_DQBUF, &buf)) + { + switch (errno) + { case EAGAIN: return 0; @@ -444,10 +491,13 @@ int V4L2Grabber::read_frame() assert(buf.index < _buffers.size()); - process_image(_buffers[buf.index].start, buf.bytesused); + process_image(_buffers[buf.index].start, buf.bytesused); if (-1 == xioctl(VIDIOC_QBUF, &buf)) + { errno_exit("VIDIOC_QBUF"); + } + break; case IO_METHOD_USERPTR: @@ -456,8 +506,10 @@ int V4L2Grabber::read_frame() buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_USERPTR; - if (-1 == xioctl(VIDIOC_DQBUF, &buf)) { - switch (errno) { + if (-1 == xioctl(VIDIOC_DQBUF, &buf)) + { + switch (errno) + { case EAGAIN: return 0; @@ -472,14 +524,19 @@ int V4L2Grabber::read_frame() } for (size_t i = 0; i < _buffers.size(); ++i) - if (buf.m.userptr == (unsigned long)_buffers[i].start - && buf.length == _buffers[i].length) + { + if (buf.m.userptr == (unsigned long)_buffers[i].start && buf.length == _buffers[i].length) + { break; + } + } - process_image((void *)buf.m.userptr, buf.bytesused); + process_image((void *)buf.m.userptr, buf.bytesused); - if (-1 == xioctl(VIDIOC_QBUF, &buf)) + if (-1 == xioctl(VIDIOC_QBUF, &buf)) + { errno_exit("VIDIOC_QBUF"); + } break; } @@ -488,38 +545,44 @@ int V4L2Grabber::read_frame() void V4L2Grabber::process_image(const void *p, int size) { - if (size != 2*720*480) - { - std::cout << "Frame too small: " << size << "<" << (2*720*480) << std::endl; - return; - } + if (size != 2*_width*_height) + { + std::cout << "Frame too small: " << size << " != " << (2*_width*_height) << std::endl; + } + else + { + process_image(reinterpret_cast(p)); + } +} - std::cout << "process image of size = " << size << std::endl; +void V4L2Grabber::process_image(const uint8_t * data) +{ + std::cout << "process image" << std::endl; - const uint8_t * data = reinterpret_cast(p); - - - QImage image(720, 480, QImage::Format_RGB888); + QImage image(_width, _height, QImage::Format_RGB888); for (int y = 0; y < image.height(); ++y) { for (int x = 0; x < image.width(); ++x) { - uint8_t value = data[(720 * y + x) * 2 + 1]; - image.setPixel(x, y, qRgb(value, value, value)); + uint8_t value = data[(image.width() * y + x) * 2 + 1]; + //std::cout << "data = " << int(value) << std::endl; + image.setPixel(x, y, qRgb(value, value, value)); } } - image.save("/home/pi/screenshot.png"); + image.save("screenshot.png"); } int V4L2Grabber::xioctl(int request, void *arg) { int r; - do { - r = ioctl(_fileDescriptor, request, arg); - } while (-1 == r && EINTR == errno); + do + { + r = ioctl(_fileDescriptor, request, arg); + } + while (-1 == r && EINTR == errno); return r; } diff --git a/test/v4l2png/V4L2Grabber.h b/test/v4l2png/V4L2Grabber.h index 1484973d..706fc493 100644 --- a/test/v4l2png/V4L2Grabber.h +++ b/test/v4l2png/V4L2Grabber.h @@ -10,11 +10,20 @@ class V4L2Grabber { public: - V4L2Grabber(); + enum VideoStandard { + PAL, NTSC, NO_CHANGE + }; + +public: + V4L2Grabber(const std::string & device, int input, VideoStandard videoStandard, double fps); virtual ~V4L2Grabber(); void start(); + void capture(); + + void stop(); + private: void open_device(); @@ -26,7 +35,7 @@ private: void init_userp(unsigned int buffer_size); - void init_device(); + void init_device(VideoStandard videoStandard, int input); void uninit_device(); @@ -36,7 +45,9 @@ private: int read_frame(); - void process_image(const void *p, int size); + void process_image(const void *p, int size); + + void process_image(const uint8_t *p); int xioctl(int request, void *arg); @@ -59,4 +70,7 @@ private: const io_method _ioMethod; int _fileDescriptor; std::vector _buffers; + + int _width; + int _height; }; diff --git a/test/v4l2png/v4l2png.cpp b/test/v4l2png/v4l2png.cpp index a2e077ff..09c664c2 100644 --- a/test/v4l2png/v4l2png.cpp +++ b/test/v4l2png/v4l2png.cpp @@ -40,9 +40,10 @@ int main(int argc, char** argv) return 1; } - V4L2Grabber grabber; + V4L2Grabber grabber("/dev/video0", 0, V4L2Grabber::PAL, 10.0); grabber.start(); + grabber.capture(); + grabber.stop(); return 0; } - From 421ec6926fd298affbf1449ac910138a997b0ed9 Mon Sep 17 00:00:00 2001 From: johan Date: Sun, 12 Jan 2014 14:18:27 +0100 Subject: [PATCH 03/21] Added framerate decimation Former-commit-id: 8f7eb09978aef5364cbab1f5dfe12afbf60e6cac --- test/v4l2png/V4L2Grabber.cpp | 280 ++++++++++++++++++----------------- test/v4l2png/V4L2Grabber.h | 26 ++-- test/v4l2png/v4l2png.cpp | 6 +- 3 files changed, 163 insertions(+), 149 deletions(-) diff --git a/test/v4l2png/V4L2Grabber.cpp b/test/v4l2png/V4L2Grabber.cpp index 03da150d..9d2676c6 100644 --- a/test/v4l2png/V4L2Grabber.cpp +++ b/test/v4l2png/V4L2Grabber.cpp @@ -19,22 +19,25 @@ #define CLEAR(x) memset(&(x), 0, sizeof(x)) -V4L2Grabber::V4L2Grabber(const std::string &device, int input, VideoStandard videoStandard, double fps) : - _deviceName(device), +V4L2Grabber::V4L2Grabber(const std::string &device, int input, VideoStandard videoStandard, int frameDecimation, int pixelDecimation) : + _deviceName(device), _ioMethod(IO_METHOD_MMAP), _fileDescriptor(-1), - _buffers(), - _width(0), - _height(0) + _buffers(), + _width(0), + _height(0), + _frameDecimation(frameDecimation), + _pixelDecimation(pixelDecimation), + _currentFrame(0) { - open_device(); - init_device(videoStandard, input); + open_device(); + init_device(videoStandard, input); } V4L2Grabber::~V4L2Grabber() { - uninit_device(); - close_device(); + uninit_device(); + close_device(); } void V4L2Grabber::start() @@ -42,47 +45,48 @@ void V4L2Grabber::start() start_capturing(); } -void V4L2Grabber::capture() +void V4L2Grabber::capture(int frameCount) { - int count = 1; - while (count-- > 0) { - for (;;) { - fd_set fds; - struct timeval tv; - int r; + for (int count = 0; count < frameCount || frameCount < 0; ++count) + { + for (;;) + { + // the set of file descriptors for select + fd_set fds; + FD_ZERO(&fds); + FD_SET(_fileDescriptor, &fds); - FD_ZERO(&fds); - FD_SET(_fileDescriptor, &fds); + // timeout + struct timeval tv; + tv.tv_sec = 2; + tv.tv_usec = 0; - /* Timeout. */ - tv.tv_sec = 2; - tv.tv_usec = 0; + // block until data is available + int r = select(_fileDescriptor + 1, &fds, NULL, NULL, &tv); - r = select(_fileDescriptor + 1, &fds, NULL, NULL, &tv); + if (-1 == r) + { + if (EINTR == errno) + continue; + errno_exit("select"); + } - if (-1 == r) - { - if (EINTR == errno) - continue; - errno_exit("select"); - } + if (0 == r) + { + fprintf(stderr, "select timeout\n"); + exit(EXIT_FAILURE); + } - if (0 == r) - { - fprintf(stderr, "select timeout\n"); - exit(EXIT_FAILURE); - } - - if (read_frame()) - break; - /* EAGAIN - continue select loop. */ - } - } + if (read_frame()) + break; + /* EAGAIN - continue select loop. */ + } + } } void V4L2Grabber::stop() { - stop_capturing(); + stop_capturing(); } void V4L2Grabber::open_device() @@ -218,7 +222,7 @@ void V4L2Grabber::init_userp(unsigned int buffer_size) void V4L2Grabber::init_device(VideoStandard videoStandard, int input) { struct v4l2_capability cap; - if (-1 == xioctl(VIDIOC_QUERYCAP, &cap)) + if (-1 == xioctl(VIDIOC_QUERYCAP, &cap)) { if (EINVAL == errno) { fprintf(stderr, "%s is no V4L2 device\n", _deviceName.c_str()); @@ -256,14 +260,14 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) /* Select video input, video standard and tune here. */ - struct v4l2_cropcap cropcap; + struct v4l2_cropcap cropcap; CLEAR(cropcap); cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (0 == xioctl(VIDIOC_CROPCAP, &cropcap)) { - struct v4l2_crop crop; - crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + struct v4l2_crop crop; + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; crop.c = cropcap.defrect; /* reset to default */ if (-1 == xioctl(VIDIOC_S_CROP, &crop)) { @@ -280,61 +284,61 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) /* Errors ignored. */ } - // set input if needed - if (input >= 0) - { - if (-1 == xioctl(VIDIOC_S_INPUT, &input)) - { - errno_exit("VIDIOC_S_INPUT"); - } - } + // set input if needed + if (input >= 0) + { + if (-1 == xioctl(VIDIOC_S_INPUT, &input)) + { + errno_exit("VIDIOC_S_INPUT"); + } + } - // set the video standard if needed - switch (videoStandard) - { - case PAL: - { - v4l2_std_id std_id = V4L2_STD_PAL; - if (-1 == xioctl(VIDIOC_S_STD, &std_id)) - { - errno_exit("VIDIOC_S_STD"); - } - } - break; - case NTSC: - { - v4l2_std_id std_id = V4L2_STD_NTSC; - if (-1 == xioctl(VIDIOC_S_STD, &std_id)) - { - errno_exit("VIDIOC_S_STD"); - } - } - break; - case NO_CHANGE: - default: - // No change to device settings - break; - } + // set the video standard if needed + switch (videoStandard) + { + case PAL: + { + v4l2_std_id std_id = V4L2_STD_PAL; + if (-1 == xioctl(VIDIOC_S_STD, &std_id)) + { + errno_exit("VIDIOC_S_STD"); + } + } + break; + case NTSC: + { + v4l2_std_id std_id = V4L2_STD_NTSC; + if (-1 == xioctl(VIDIOC_S_STD, &std_id)) + { + errno_exit("VIDIOC_S_STD"); + } + } + break; + case NO_CHANGE: + default: + // No change to device settings + break; + } - // get the current settings - struct v4l2_format fmt; + // get the current settings + struct v4l2_format fmt; CLEAR(fmt); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) { - errno_exit("VIDIOC_G_FMT"); + errno_exit("VIDIOC_G_FMT"); } - // check pixel format - if (fmt.fmt.pix.pixelformat != V4L2_PIX_FMT_UYVY) - { - exit(EXIT_FAILURE); - } + // check pixel format + if (fmt.fmt.pix.pixelformat != V4L2_PIX_FMT_UYVY) + { + exit(EXIT_FAILURE); + } - // store width & height - _width = fmt.fmt.pix.width; - _height = fmt.fmt.pix.height; - std::cout << "V4L2 width=" << _width << " height=" << _height << std::endl; + // store width & height + _width = fmt.fmt.pix.width; + _height = fmt.fmt.pix.height; + std::cout << "V4L2 width=" << _width << " height=" << _height << std::endl; switch (_ioMethod) { case IO_METHOD_READ: @@ -445,11 +449,11 @@ int V4L2Grabber::read_frame() switch (_ioMethod) { case IO_METHOD_READ: - int size; - if ((size = read(_fileDescriptor, _buffers[0].start, _buffers[0].length)) == -1) - { - switch (errno) - { + int size; + if ((size = read(_fileDescriptor, _buffers[0].start, _buffers[0].length)) == -1) + { + switch (errno) + { case EAGAIN: return 0; @@ -463,7 +467,7 @@ int V4L2Grabber::read_frame() } } - process_image(_buffers[0].start, size); + process_image(_buffers[0].start, size); break; case IO_METHOD_MMAP: @@ -472,10 +476,10 @@ int V4L2Grabber::read_frame() buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; - if (-1 == xioctl(VIDIOC_DQBUF, &buf)) - { - switch (errno) - { + if (-1 == xioctl(VIDIOC_DQBUF, &buf)) + { + switch (errno) + { case EAGAIN: return 0; @@ -491,12 +495,12 @@ int V4L2Grabber::read_frame() assert(buf.index < _buffers.size()); - process_image(_buffers[buf.index].start, buf.bytesused); + process_image(_buffers[buf.index].start, buf.bytesused); if (-1 == xioctl(VIDIOC_QBUF, &buf)) - { + { errno_exit("VIDIOC_QBUF"); - } + } break; @@ -506,10 +510,10 @@ int V4L2Grabber::read_frame() buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_USERPTR; - if (-1 == xioctl(VIDIOC_DQBUF, &buf)) - { - switch (errno) - { + if (-1 == xioctl(VIDIOC_DQBUF, &buf)) + { + switch (errno) + { case EAGAIN: return 0; @@ -524,19 +528,19 @@ int V4L2Grabber::read_frame() } for (size_t i = 0; i < _buffers.size(); ++i) - { - if (buf.m.userptr == (unsigned long)_buffers[i].start && buf.length == _buffers[i].length) - { + { + if (buf.m.userptr == (unsigned long)_buffers[i].start && buf.length == _buffers[i].length) + { break; - } - } + } + } - process_image((void *)buf.m.userptr, buf.bytesused); + process_image((void *)buf.m.userptr, buf.bytesused); - if (-1 == xioctl(VIDIOC_QBUF, &buf)) - { + if (-1 == xioctl(VIDIOC_QBUF, &buf)) + { errno_exit("VIDIOC_QBUF"); - } + } break; } @@ -545,44 +549,50 @@ int V4L2Grabber::read_frame() void V4L2Grabber::process_image(const void *p, int size) { - if (size != 2*_width*_height) - { - std::cout << "Frame too small: " << size << " != " << (2*_width*_height) << std::endl; - } - else - { - process_image(reinterpret_cast(p)); - } + if (++_currentFrame >= _frameDecimation) + { + // We do want a new frame... + + if (size != 2*_width*_height) + { + std::cout << "Frame too small: " << size << " != " << (2*_width*_height) << std::endl; + } + else + { + process_image(reinterpret_cast(p)); + _currentFrame = 0; // restart counting + } + } } void V4L2Grabber::process_image(const uint8_t * data) { - std::cout << "process image" << std::endl; + std::cout << "process image" << std::endl; - QImage image(_width, _height, QImage::Format_RGB888); + QImage image(_width, _height, QImage::Format_RGB888); for (int y = 0; y < image.height(); ++y) { for (int x = 0; x < image.width(); ++x) { - uint8_t value = data[(image.width() * y + x) * 2 + 1]; - //std::cout << "data = " << int(value) << std::endl; - image.setPixel(x, y, qRgb(value, value, value)); + uint8_t value = data[(image.width() * y + x) * 2 + 1]; + //std::cout << "data = " << int(value) << std::endl; + image.setPixel(x, y, qRgb(value, value, value)); } } - image.save("screenshot.png"); + image.save("screenshot.png"); } int V4L2Grabber::xioctl(int request, void *arg) { int r; - do - { - r = ioctl(_fileDescriptor, request, arg); - } - while (-1 == r && EINTR == errno); + do + { + r = ioctl(_fileDescriptor, request, arg); + } + while (-1 == r && EINTR == errno); return r; } diff --git a/test/v4l2png/V4L2Grabber.h b/test/v4l2png/V4L2Grabber.h index 706fc493..cc9de6af 100644 --- a/test/v4l2png/V4L2Grabber.h +++ b/test/v4l2png/V4L2Grabber.h @@ -10,19 +10,19 @@ class V4L2Grabber { public: - enum VideoStandard { - PAL, NTSC, NO_CHANGE - }; + enum VideoStandard { + PAL, NTSC, NO_CHANGE + }; public: - V4L2Grabber(const std::string & device, int input, VideoStandard videoStandard, double fps); + V4L2Grabber(const std::string & device, int input, VideoStandard videoStandard, int frameDecimation, int pixelDecimation); virtual ~V4L2Grabber(); void start(); - void capture(); + void capture(int frameCount = -1); - void stop(); + void stop(); private: void open_device(); @@ -35,7 +35,7 @@ private: void init_userp(unsigned int buffer_size); - void init_device(VideoStandard videoStandard, int input); + void init_device(VideoStandard videoStandard, int input); void uninit_device(); @@ -45,9 +45,9 @@ private: int read_frame(); - void process_image(const void *p, int size); + void process_image(const void *p, int size); - void process_image(const uint8_t *p); + void process_image(const uint8_t *p); int xioctl(int request, void *arg); @@ -71,6 +71,10 @@ private: int _fileDescriptor; std::vector _buffers; - int _width; - int _height; + int _width; + int _height; + const int _frameDecimation; + const int _pixelDecimation; + + int _currentFrame; }; diff --git a/test/v4l2png/v4l2png.cpp b/test/v4l2png/v4l2png.cpp index 09c664c2..343b67ec 100644 --- a/test/v4l2png/v4l2png.cpp +++ b/test/v4l2png/v4l2png.cpp @@ -40,10 +40,10 @@ int main(int argc, char** argv) return 1; } - V4L2Grabber grabber("/dev/video0", 0, V4L2Grabber::PAL, 10.0); + V4L2Grabber grabber("/dev/video0", 0, V4L2Grabber::PAL, 25, 8); grabber.start(); - grabber.capture(); - grabber.stop(); + grabber.capture(250); + grabber.stop(); return 0; } From 545f2ee114fbd441a96398f30846ffb4cbaeddbd Mon Sep 17 00:00:00 2001 From: johan Date: Sun, 12 Jan 2014 16:28:41 +0100 Subject: [PATCH 04/21] color decoding added; size decimation added Former-commit-id: ca3a959a4e842c86978855b005f97ce7a383a4fd --- test/v4l2png/V4L2Grabber.cpp | 43 ++++++++++++++++++++----- test/v4l2png/v4l2png.cpp | 61 ++++++++++++++++++++++++++++++++---- 2 files changed, 90 insertions(+), 14 deletions(-) diff --git a/test/v4l2png/V4L2Grabber.cpp b/test/v4l2png/V4L2Grabber.cpp index 9d2676c6..f9adbc3b 100644 --- a/test/v4l2png/V4L2Grabber.cpp +++ b/test/v4l2png/V4L2Grabber.cpp @@ -19,6 +19,24 @@ #define CLEAR(x) memset(&(x), 0, sizeof(x)) +static inline uint8_t clamp(int x) +{ + return (x<0) ? 0 : ((x>255) ? 255 : uint8_t(x)); +} + +static void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t & r, uint8_t & g, uint8_t & b) +{ + // see: http://en.wikipedia.org/wiki/YUV#Y.27UV444_to_RGB888_conversion + int c = y - 16; + int d = u - 128; + int e = v - 128; + + r = clamp((298 * c + 409 * e + 128) >> 8); + g = clamp((298 * c - 100 * d - 208 * e + 128) >> 8); + b = clamp((298 * c + 516 * d + 128) >> 8); +} + + V4L2Grabber::V4L2Grabber(const std::string &device, int input, VideoStandard videoStandard, int frameDecimation, int pixelDecimation) : _deviceName(device), _ioMethod(IO_METHOD_MMAP), @@ -26,8 +44,8 @@ V4L2Grabber::V4L2Grabber(const std::string &device, int input, VideoStandard vid _buffers(), _width(0), _height(0), - _frameDecimation(frameDecimation), - _pixelDecimation(pixelDecimation), + _frameDecimation(std::max(1, frameDecimation)), + _pixelDecimation(std::max(1, pixelDecimation)), _currentFrame(0) { open_device(); @@ -569,15 +587,24 @@ void V4L2Grabber::process_image(const uint8_t * data) { std::cout << "process image" << std::endl; - QImage image(_width, _height, QImage::Format_RGB888); + int width = (_width + _pixelDecimation/2) / _pixelDecimation; + int height = (_height + _pixelDecimation/2) / _pixelDecimation; - for (int y = 0; y < image.height(); ++y) + QImage image(width, height, QImage::Format_RGB888); + + for (int ySource = _pixelDecimation/2, yDest = 0; ySource < _height; ySource += _pixelDecimation, ++yDest) { - for (int x = 0; x < image.width(); ++x) + for (int xSource = _pixelDecimation/2, xDest = 0; xSource < _width; xSource += _pixelDecimation, ++xDest) { - uint8_t value = data[(image.width() * y + x) * 2 + 1]; - //std::cout << "data = " << int(value) << std::endl; - image.setPixel(x, y, qRgb(value, value, value)); + int index = (_width * ySource + xSource) * 2; + uint8_t y = data[index+1]; + uint8_t u = (xSource%2 == 0) ? data[index] : data[index-2]; + uint8_t v = (xSource%2 == 0) ? data[index+2] : data[index]; + + uint8_t r, g, b; + yuv2rgb(y, u, v, r, g, b); + + image.setPixel(xDest, yDest, qRgb(r, g, b)); } } diff --git a/test/v4l2png/v4l2png.cpp b/test/v4l2png/v4l2png.cpp index 343b67ec..29932578 100644 --- a/test/v4l2png/v4l2png.cpp +++ b/test/v4l2png/v4l2png.cpp @@ -13,6 +13,39 @@ using namespace vlofgren; +/// Data parameter for the video standard +typedef vlofgren::PODParameter VideoStandardParameter; + +namespace vlofgren { + /// Translates a string (as passed on the commandline) to a color standard + /// + /// @param[in] s The string (as passed on the commandline) + /// @return The color standard + /// @throws Parameter::ParameterRejected If the string did not result in a video standard + template<> + V4L2Grabber::VideoStandard VideoStandardParameter::validate(const std::string& s) throw (Parameter::ParameterRejected) + { + QString input = QString::fromStdString(s).toLower(); + + if (input == "pal") + { + return V4L2Grabber::PAL; + } + else if (input == "ntsc") + { + return V4L2Grabber::NTSC; + } + else if (input == "no-change") + { + return V4L2Grabber::NO_CHANGE; + } + + throw Parameter::ParameterRejected("Invalid value for video standard. Valid values are: PAL, NTSC, and NO-CHANGE"); + return V4L2Grabber::NO_CHANGE; + } +} + + int main(int argc, char** argv) { try @@ -21,7 +54,17 @@ int main(int argc, char** argv) OptionsParser optionParser("Simple application to send a command to hyperion using the Json interface"); ParameterSet & parameters = optionParser.getParameters(); - SwitchParameter<> & argHelp = parameters.add >('h', "help", "Show this help message and exit"); + StringParameter & argDevice = parameters.add ('d', "device", "The device to use [default=/dev/video0]"); + VideoStandardParameter & argVideoStandard = parameters.add('v', "video-standard", "The used video standard. Valid values are PAL. NYSC, or NO-CHANGE [default=PAL]"); + IntParameter & argInput = parameters.add ('i', "input", "Input channel [default=0]"); + IntParameter & argSizeDecimation = parameters.add ('s', "size-decimator", "Decimation factor for the output size [default=1]"); + SwitchParameter<> & argHelp = parameters.add > ('h', "help", "Show this help message and exit"); + + // set defaults + argDevice.setDefault("/dev/video0"); + argVideoStandard.setDefault(V4L2Grabber::PAL); + argInput.setDefault(0); + argSizeDecimation.setDefault(1); // parse all options optionParser.parse(argc, const_cast(argv)); @@ -32,6 +75,17 @@ int main(int argc, char** argv) optionParser.usage(); return 0; } + + V4L2Grabber grabber( + argDevice.getValue(), + argInput.getValue(), + argVideoStandard.getValue(), + 1, + argSizeDecimation.getValue()); + + grabber.start(); + grabber.capture(1); + grabber.stop(); } catch (const std::runtime_error & e) { @@ -40,10 +94,5 @@ int main(int argc, char** argv) return 1; } - V4L2Grabber grabber("/dev/video0", 0, V4L2Grabber::PAL, 25, 8); - grabber.start(); - grabber.capture(250); - grabber.stop(); - return 0; } From c65cf872d1a44e182c158ee9b4e618f617afd243 Mon Sep 17 00:00:00 2001 From: johan Date: Sun, 12 Jan 2014 19:54:10 +0100 Subject: [PATCH 05/21] renamed v4l2_to_png Former-commit-id: 87b89104673285a0a225fef53106f13c17bb22ac --- test/CMakeLists.txt | 2 +- test/{v4l2png => v4l2_to_png}/CMakeLists.txt | 8 ++++---- test/{v4l2png => v4l2_to_png}/V4L2Grabber.cpp | 4 ++++ test/{v4l2png => v4l2_to_png}/V4L2Grabber.h | 0 test/{v4l2png/v4l2png.cpp => v4l2_to_png/v4l2_to_png.cpp} | 0 5 files changed, 9 insertions(+), 5 deletions(-) rename test/{v4l2png => v4l2_to_png}/CMakeLists.txt (59%) rename test/{v4l2png => v4l2_to_png}/V4L2Grabber.cpp (99%) rename test/{v4l2png => v4l2_to_png}/V4L2Grabber.h (100%) rename test/{v4l2png/v4l2png.cpp => v4l2_to_png/v4l2_to_png.cpp} (100%) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bc2228c2..63751d1d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -36,7 +36,7 @@ if (ENABLE_DISPMANX) endif (ENABLE_DISPMANX) if (ENABLE_V4L2) - add_subdirectory(v4l2png) + add_subdirectory(v4l2_to_png) endif (ENABLE_V4L2) add_executable(test_blackborderdetector diff --git a/test/v4l2png/CMakeLists.txt b/test/v4l2_to_png/CMakeLists.txt similarity index 59% rename from test/v4l2png/CMakeLists.txt rename to test/v4l2_to_png/CMakeLists.txt index dd0cba27..ab259048 100644 --- a/test/v4l2png/CMakeLists.txt +++ b/test/v4l2_to_png/CMakeLists.txt @@ -4,14 +4,14 @@ find_package(Qt4 REQUIRED QtCore QtGui) include_directories(${QT_INCLUDES}) -add_executable(v4l_to_png - v4l2png.cpp +add_executable(v4l2_to_png + v4l2_to_png.cpp V4L2Grabber.h V4L2Grabber.cpp) -target_link_libraries(v4l_to_png +target_link_libraries(v4l2_to_png getoptPlusPlus) -qt4_use_modules(v4l_to_png +qt4_use_modules(v4l2_to_png Core Gui) diff --git a/test/v4l2png/V4L2Grabber.cpp b/test/v4l2_to_png/V4L2Grabber.cpp similarity index 99% rename from test/v4l2png/V4L2Grabber.cpp rename to test/v4l2_to_png/V4L2Grabber.cpp index f9adbc3b..3c959621 100644 --- a/test/v4l2png/V4L2Grabber.cpp +++ b/test/v4l2_to_png/V4L2Grabber.cpp @@ -96,7 +96,10 @@ void V4L2Grabber::capture(int frameCount) } if (read_frame()) + { break; + } + /* EAGAIN - continue select loop. */ } } @@ -590,6 +593,7 @@ void V4L2Grabber::process_image(const uint8_t * data) int width = (_width + _pixelDecimation/2) / _pixelDecimation; int height = (_height + _pixelDecimation/2) / _pixelDecimation; + QImage image(width, height, QImage::Format_RGB888); for (int ySource = _pixelDecimation/2, yDest = 0; ySource < _height; ySource += _pixelDecimation, ++yDest) diff --git a/test/v4l2png/V4L2Grabber.h b/test/v4l2_to_png/V4L2Grabber.h similarity index 100% rename from test/v4l2png/V4L2Grabber.h rename to test/v4l2_to_png/V4L2Grabber.h diff --git a/test/v4l2png/v4l2png.cpp b/test/v4l2_to_png/v4l2_to_png.cpp similarity index 100% rename from test/v4l2png/v4l2png.cpp rename to test/v4l2_to_png/v4l2_to_png.cpp From f90f076ca4d9bdd21317a186fc3576a982523dfe Mon Sep 17 00:00:00 2001 From: johan Date: Sun, 12 Jan 2014 20:04:22 +0100 Subject: [PATCH 06/21] Create an Hyperion image Former-commit-id: ea4c1a605f618b8581456c4db1ab015bcb46508b --- test/v4l2_to_png/V4L2Grabber.cpp | 13 ++++++------- test/v4l2_to_png/V4L2Grabber.h | 4 ++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/test/v4l2_to_png/V4L2Grabber.cpp b/test/v4l2_to_png/V4L2Grabber.cpp index 3c959621..f278a432 100644 --- a/test/v4l2_to_png/V4L2Grabber.cpp +++ b/test/v4l2_to_png/V4L2Grabber.cpp @@ -593,8 +593,7 @@ void V4L2Grabber::process_image(const uint8_t * data) int width = (_width + _pixelDecimation/2) / _pixelDecimation; int height = (_height + _pixelDecimation/2) / _pixelDecimation; - - QImage image(width, height, QImage::Format_RGB888); + Image image(width, height); for (int ySource = _pixelDecimation/2, yDest = 0; ySource < _height; ySource += _pixelDecimation, ++yDest) { @@ -605,14 +604,14 @@ void V4L2Grabber::process_image(const uint8_t * data) uint8_t u = (xSource%2 == 0) ? data[index] : data[index-2]; uint8_t v = (xSource%2 == 0) ? data[index+2] : data[index]; - uint8_t r, g, b; - yuv2rgb(y, u, v, r, g, b); - - image.setPixel(xDest, yDest, qRgb(r, g, b)); + ColorRgb & rgb = image(xDest, yDest); + yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); } } - image.save("screenshot.png"); + // store as PNG + QImage pngImage((const uint8_t *) image.memptr(), width, height, 3*width, QImage::Format_RGB888); + pngImage.save("screenshot.png"); } int V4L2Grabber::xioctl(int request, void *arg) diff --git a/test/v4l2_to_png/V4L2Grabber.h b/test/v4l2_to_png/V4L2Grabber.h index cc9de6af..1b23e761 100644 --- a/test/v4l2_to_png/V4L2Grabber.h +++ b/test/v4l2_to_png/V4L2Grabber.h @@ -4,6 +4,10 @@ #include #include +// util includes +#include +#include + /// Capture class for V4L2 devices /// /// @see http://linuxtv.org/downloads/v4l-dvb-apis/capture-example.html From 2cfbcc881b26cf40ed350adfa54f1d2e6888ac01 Mon Sep 17 00:00:00 2001 From: johan Date: Sun, 12 Jan 2014 20:27:19 +0100 Subject: [PATCH 07/21] Added cropping of the input picture Former-commit-id: 0dbb042c39dbdf841f6003391bf21f3f548433ee --- test/v4l2_to_png/V4L2Grabber.cpp | 12 +++++++----- test/v4l2_to_png/V4L2Grabber.h | 4 +++- test/v4l2_to_png/v4l2_to_png.cpp | 6 ++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/test/v4l2_to_png/V4L2Grabber.cpp b/test/v4l2_to_png/V4L2Grabber.cpp index f278a432..af1a3e9f 100644 --- a/test/v4l2_to_png/V4L2Grabber.cpp +++ b/test/v4l2_to_png/V4L2Grabber.cpp @@ -37,13 +37,15 @@ static void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t & r, uint8_t & g, u } -V4L2Grabber::V4L2Grabber(const std::string &device, int input, VideoStandard videoStandard, int frameDecimation, int pixelDecimation) : +V4L2Grabber::V4L2Grabber(const std::string &device, int input, VideoStandard videoStandard, int cropHorizontal, int cropVertical, int frameDecimation, int pixelDecimation) : _deviceName(device), _ioMethod(IO_METHOD_MMAP), _fileDescriptor(-1), _buffers(), _width(0), _height(0), + _cropWidth(cropHorizontal), + _cropHeight(cropVertical), _frameDecimation(std::max(1, frameDecimation)), _pixelDecimation(std::max(1, pixelDecimation)), _currentFrame(0) @@ -590,14 +592,14 @@ void V4L2Grabber::process_image(const uint8_t * data) { std::cout << "process image" << std::endl; - int width = (_width + _pixelDecimation/2) / _pixelDecimation; - int height = (_height + _pixelDecimation/2) / _pixelDecimation; + int width = (_width - 2 * _cropWidth + _pixelDecimation/2) / _pixelDecimation; + int height = (_height - 2 * _cropHeight + _pixelDecimation/2) / _pixelDecimation; Image image(width, height); - for (int ySource = _pixelDecimation/2, yDest = 0; ySource < _height; ySource += _pixelDecimation, ++yDest) + for (int ySource = _cropHeight + _pixelDecimation/2, yDest = 0; ySource < _height - _cropHeight; ySource += _pixelDecimation, ++yDest) { - for (int xSource = _pixelDecimation/2, xDest = 0; xSource < _width; xSource += _pixelDecimation, ++xDest) + for (int xSource = _cropWidth + _pixelDecimation/2, xDest = 0; xSource < _width - _cropWidth; xSource += _pixelDecimation, ++xDest) { int index = (_width * ySource + xSource) * 2; uint8_t y = data[index+1]; diff --git a/test/v4l2_to_png/V4L2Grabber.h b/test/v4l2_to_png/V4L2Grabber.h index 1b23e761..c028e745 100644 --- a/test/v4l2_to_png/V4L2Grabber.h +++ b/test/v4l2_to_png/V4L2Grabber.h @@ -19,7 +19,7 @@ public: }; public: - V4L2Grabber(const std::string & device, int input, VideoStandard videoStandard, int frameDecimation, int pixelDecimation); + V4L2Grabber(const std::string & device, int input, VideoStandard videoStandard, int cropHorizontal, int cropVertical, int frameDecimation, int pixelDecimation); virtual ~V4L2Grabber(); void start(); @@ -77,6 +77,8 @@ private: int _width; int _height; + const int _cropWidth; + const int _cropHeight; const int _frameDecimation; const int _pixelDecimation; diff --git a/test/v4l2_to_png/v4l2_to_png.cpp b/test/v4l2_to_png/v4l2_to_png.cpp index 29932578..7f418018 100644 --- a/test/v4l2_to_png/v4l2_to_png.cpp +++ b/test/v4l2_to_png/v4l2_to_png.cpp @@ -57,6 +57,8 @@ int main(int argc, char** argv) StringParameter & argDevice = parameters.add ('d', "device", "The device to use [default=/dev/video0]"); VideoStandardParameter & argVideoStandard = parameters.add('v', "video-standard", "The used video standard. Valid values are PAL. NYSC, or NO-CHANGE [default=PAL]"); IntParameter & argInput = parameters.add ('i', "input", "Input channel [default=0]"); + IntParameter & argCropWidth = parameters.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides in the picture before decimation [default=0]"); + IntParameter & argCropHeight = parameters.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom in the picture before decimation [default=0]"); IntParameter & argSizeDecimation = parameters.add ('s', "size-decimator", "Decimation factor for the output size [default=1]"); SwitchParameter<> & argHelp = parameters.add > ('h', "help", "Show this help message and exit"); @@ -64,6 +66,8 @@ int main(int argc, char** argv) argDevice.setDefault("/dev/video0"); argVideoStandard.setDefault(V4L2Grabber::PAL); argInput.setDefault(0); + argCropWidth.setDefault(0); + argCropHeight.setDefault(0); argSizeDecimation.setDefault(1); // parse all options @@ -80,6 +84,8 @@ int main(int argc, char** argv) argDevice.getValue(), argInput.getValue(), argVideoStandard.getValue(), + std::max(0, argCropWidth.getValue()), + std::max(0, argCropHeight.getValue()), 1, argSizeDecimation.getValue()); From a7110ec64cc72f6bb5e439bc7809c782834cab68 Mon Sep 17 00:00:00 2001 From: johan Date: Mon, 20 Jan 2014 20:46:38 +0100 Subject: [PATCH 08/21] Configurable blackborder threshold added Former-commit-id: 95b77dee2869b41bf556e0e374bea3c5e4534e61 --- config/hyperion.config.json | 12 ++++--- config/hyperion_x86.config.json | 44 ++++++++++++++++++++--- deploy/hyperion.tar.gz.REMOVED.git-id | 2 +- include/hyperion/BlackBorderDetector.h | 9 +++-- include/hyperion/BlackBorderProcessor.h | 4 ++- include/hyperion/ImageProcessor.h | 20 ++++++----- include/hyperion/ImageProcessorFactory.h | 7 +++- libsrc/hyperion/BlackBorderDetector.cpp | 3 +- libsrc/hyperion/BlackBorderProcessor.cpp | 8 ++--- libsrc/hyperion/Hyperion.cpp | 5 ++- libsrc/hyperion/ImageProcessor.cpp | 22 ++++++------ libsrc/hyperion/ImageProcessorFactory.cpp | 16 +++++++-- libsrc/hyperion/hyperion.schema.json | 6 ++++ test/TestBlackBorderDetector.cpp | 10 +++--- test/TestBlackBorderProcessor.cpp | 2 +- 15 files changed, 121 insertions(+), 49 deletions(-) diff --git a/config/hyperion.config.json b/config/hyperion.config.json index 648f970d..fa08d80d 100644 --- a/config/hyperion.config.json +++ b/config/hyperion.config.json @@ -56,21 +56,21 @@ "red" : { "threshold" : 0.0000, - "gamma" : 2.0000, + "gamma" : 1.0000, "blacklevel" : 0.0000, "whitelevel" : 1.0000 }, "green" : { "threshold" : 0.0000, - "gamma" : 2.0000, + "gamma" : 1.0000, "blacklevel" : 0.0000, "whitelevel" : 1.0000 }, "blue" : { "threshold" : 0.0000, - "gamma" : 2.0000, + "gamma" : 1.0000, "blacklevel" : 0.0000, "whitelevel" : 1.0000 } @@ -348,10 +348,12 @@ ], /// The black border configuration, contains the following items: - /// * enable : true if the detector should be activated + /// * enable : true if the detector should be activated + /// * threshold : Value below which a pixel is regarded as black (value between 0.0 and 1.0) "blackborderdetector" : { - "enable" : true + "enable" : true, + "threshold" : 0.01 }, /// The configuration of the effect engine, contains the following items: diff --git a/config/hyperion_x86.config.json b/config/hyperion_x86.config.json index 89376b20..071713aa 100644 --- a/config/hyperion_x86.config.json +++ b/config/hyperion_x86.config.json @@ -13,9 +13,9 @@ "device" : { "name" : "MyPi", - "type" : "test", - "output" : "~/hyperion.test.out", - "rate" : 250000, + "type" : "adalight", + "output" : "/dev/ttyUSB0", + "rate" : 115200, "colorOrder" : "rgb" }, @@ -348,10 +348,12 @@ ], /// The black border configuration, contains the following items: - /// * enable : true if the detector should be activated + /// * enable : true if the detector should be activated + /// * threshold : Value below which a pixel is regarded as black (value between 0.0 and 1.0) "blackborderdetector" : { - "enable" : true + "enable" : true, + "threshold" : 0.01 }, /// The configuration of the effect engine, contains the following items: @@ -371,6 +373,38 @@ "duration_ms" : 3000 }, + /// The configuration for the frame-grabber, contains the following items: + /// * width : The width of the grabbed frames [pixels] + /// * height : The height of the grabbed frames [pixels] + /// * frequency_Hz : The frequency of the frame grab [Hz] +// "framegrabber" : +// { +// "width" : 64, +// "height" : 64, +// "frequency_Hz" : 10.0 +// }, + + /// The configuration of the XBMC connection used to enable and disable the frame-grabber. Contains the following fields: + /// * xbmcAddress : The IP address of the XBMC-host + /// * xbmcTcpPort : The TCP-port of the XBMC-server + /// * grabVideo : Flag indicating that the frame-grabber is on(true) during video playback + /// * grabPictures : Flag indicating that the frame-grabber is on(true) during picture show + /// * grabAudio : Flag indicating that the frame-grabber is on(true) during audio playback + /// * grabMenu : Flag indicating that the frame-grabber is on(true) in the XBMC menu + /// * grabScreensaver : Flag indicating that the frame-grabber is on(true) when XBMC is on screensaver + /// * enable3DDetection : Flag indicating that the frame-grabber should switch to a 3D compatible modus if a 3D video is playing +// "xbmcVideoChecker" : +// { +// "xbmcAddress" : "127.0.0.1", +// "xbmcTcpPort" : 9090, +// "grabVideo" : true, +// "grabPictures" : true, +// "grabAudio" : true, +// "grabMenu" : false, +// "grabScreensaver" : true, +// "enable3DDetection" : true +// }, + /// The configuration of the Json server which enables the json remote interface /// * port : Port at which the json server is started "jsonServer" : diff --git a/deploy/hyperion.tar.gz.REMOVED.git-id b/deploy/hyperion.tar.gz.REMOVED.git-id index d1df5729..e41e5906 100644 --- a/deploy/hyperion.tar.gz.REMOVED.git-id +++ b/deploy/hyperion.tar.gz.REMOVED.git-id @@ -1 +1 @@ -96b8f6c7e0241930c944a7358af473dabecf1dae \ No newline at end of file +2ad39031528c40c14e352cbc84784f48564cc59b \ No newline at end of file diff --git a/include/hyperion/BlackBorderDetector.h b/include/hyperion/BlackBorderDetector.h index c3393bdb..ce0bf25d 100644 --- a/include/hyperion/BlackBorderDetector.h +++ b/include/hyperion/BlackBorderDetector.h @@ -48,8 +48,9 @@ namespace hyperion public: /// /// Constructs a black-border detector + /// @param[in] blackborderThreshold The threshold which the blackborder detector should use /// - BlackBorderDetector(); + BlackBorderDetector(uint8_t blackborderThreshold); /// /// Performs the actual black-border detection on the given image @@ -125,7 +126,11 @@ namespace hyperion inline bool isBlack(const Pixel_T & color) { // Return the simple compare of the color against black - return color.red < 3 && color.green < 3 && color.green < 3; + return color.red < _blackborderThreshold && color.green < _blackborderThreshold && color.green < _blackborderThreshold; } + + private: + /// Threshold for the blackborder detector [0 .. 255] + const uint8_t _blackborderThreshold; }; } // end namespace hyperion diff --git a/include/hyperion/BlackBorderProcessor.h b/include/hyperion/BlackBorderProcessor.h index 3ec2fe42..4d0c4fca 100644 --- a/include/hyperion/BlackBorderProcessor.h +++ b/include/hyperion/BlackBorderProcessor.h @@ -21,11 +21,13 @@ namespace hyperion /// horizontal border becomes the current border /// @param blurRemoveCnt The size to add to a horizontal or vertical border (because the /// outer pixels is blurred (black and color combined due to image scaling)) + /// @param[in] blackborderThreshold The threshold which the blackborder detector should use /// BlackBorderProcessor( const unsigned unknownFrameCnt, const unsigned borderFrameCnt, - const unsigned blurRemoveCnt); + const unsigned blurRemoveCnt, + uint8_t blackborderThreshold); /// /// Return the current (detected) border diff --git a/include/hyperion/ImageProcessor.h b/include/hyperion/ImageProcessor.h index 6c4b4739..990b6593 100644 --- a/include/hyperion/ImageProcessor.h +++ b/include/hyperion/ImageProcessor.h @@ -53,7 +53,7 @@ public: verifyBorder(image); // Create a result vector and call the 'in place' functionl - std::vector colors = mImageToLeds->getMeanLedColor(image); + std::vector colors = _imageToLeds->getMeanLedColor(image); // return the computed colors return colors; @@ -75,7 +75,7 @@ public: verifyBorder(image); // Determine the mean-colors of each led (using the existing mapping) - mImageToLeds->getMeanLedColor(image, ledColors); + _imageToLeds->getMeanLedColor(image, ledColors); } /// @@ -98,8 +98,10 @@ private: /// given led-string specification /// /// @param[in] ledString The led-string specification + /// @param[in] enableBlackBorderDetector Flag indicating if the blacborder detector should be enabled + /// @param[in] blackborderThreshold The threshold which the blackborder detector should use /// - ImageProcessor(const LedString &ledString, bool enableBlackBorderDetector); + ImageProcessor(const LedString &ledString, bool enableBlackBorderDetector, uint8_t blackborderThreshold); /// /// Performs black-border detection (if enabled) on the given image @@ -116,17 +118,17 @@ private: const hyperion::BlackBorder border = _borderProcessor->getCurrentBorder(); // Clean up the old mapping - delete mImageToLeds; + delete _imageToLeds; if (border.unknown) { // Construct a new buffer and mapping - mImageToLeds = new hyperion::ImageToLedsMap(image.width(), image.height(), 0, 0, mLedString.leds()); + _imageToLeds = new hyperion::ImageToLedsMap(image.width(), image.height(), 0, 0, _ledString.leds()); } else { // Construct a new buffer and mapping - mImageToLeds = new hyperion::ImageToLedsMap(image.width(), image.height(), border.horizontalSize, border.verticalSize, mLedString.leds()); + _imageToLeds = new hyperion::ImageToLedsMap(image.width(), image.height(), border.horizontalSize, border.verticalSize, _ledString.leds()); } std::cout << "CURRENT BORDER TYPE: unknown=" << border.unknown << " hor.size=" << border.horizontalSize << " vert.size=" << border.verticalSize << std::endl; @@ -135,14 +137,14 @@ private: private: /// The Led-string specification - const LedString mLedString; + const LedString _ledString; /// Flag the enables(true)/disabled(false) blackborder detector - bool _enableBlackBorderRemoval; + const bool _enableBlackBorderRemoval; /// The processor for black border detection hyperion::BlackBorderProcessor * _borderProcessor; /// The mapping of image-pixels to leds - hyperion::ImageToLedsMap* mImageToLeds; + hyperion::ImageToLedsMap* _imageToLeds; }; diff --git a/include/hyperion/ImageProcessorFactory.h b/include/hyperion/ImageProcessorFactory.h index a377bafa..cde64440 100644 --- a/include/hyperion/ImageProcessorFactory.h +++ b/include/hyperion/ImageProcessorFactory.h @@ -30,8 +30,10 @@ public: /// Initialises this factory with the given led-configuration /// /// @param[in] ledString The led configuration + /// @param[in] enableBlackBorderDetector Flag indicating if the blacborder detector should be enabled + /// @param[in] blackborderThreshold The threshold which the blackborder detector should use /// - void init(const LedString& ledString, bool enableBlackBorderDetector); + void init(const LedString& ledString, bool enableBlackBorderDetector, double blackborderThreshold); /// /// Creates a new ImageProcessor. The onwership of the processor is transferred to the caller. @@ -46,4 +48,7 @@ private: /// Flag indicating if the black border detector should be used bool _enableBlackBorderDetector; + + /// Threshold for the blackborder detector [0 .. 255] + uint8_t _blackborderThreshold; }; diff --git a/libsrc/hyperion/BlackBorderDetector.cpp b/libsrc/hyperion/BlackBorderDetector.cpp index e86ba9ed..ed97bebe 100644 --- a/libsrc/hyperion/BlackBorderDetector.cpp +++ b/libsrc/hyperion/BlackBorderDetector.cpp @@ -4,7 +4,8 @@ using namespace hyperion; -BlackBorderDetector::BlackBorderDetector() +BlackBorderDetector::BlackBorderDetector(uint8_t blackborderThreshold) : + _blackborderThreshold(blackborderThreshold) { // empty } diff --git a/libsrc/hyperion/BlackBorderProcessor.cpp b/libsrc/hyperion/BlackBorderProcessor.cpp index 87ada03f..efc97724 100644 --- a/libsrc/hyperion/BlackBorderProcessor.cpp +++ b/libsrc/hyperion/BlackBorderProcessor.cpp @@ -4,14 +4,14 @@ using namespace hyperion; -BlackBorderProcessor::BlackBorderProcessor( - const unsigned unknownFrameCnt, +BlackBorderProcessor::BlackBorderProcessor(const unsigned unknownFrameCnt, const unsigned borderFrameCnt, - const unsigned blurRemoveCnt) : + const unsigned blurRemoveCnt, + uint8_t blackborderThreshold) : _unknownSwitchCnt(unknownFrameCnt), _borderSwitchCnt(borderFrameCnt), _blurRemoveCnt(blurRemoveCnt), - _detector(), + _detector(blackborderThreshold), _currentBorder({true, -1, -1}), _previousDetectedBorder({true, -1, -1}), _consistentCnt(0) diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index bfe14a9d..a81e4f3c 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -270,7 +270,10 @@ Hyperion::Hyperion(const Json::Value &jsonConfig) : throw std::runtime_error("Color transformation incorrectly set"); } // initialize the image processor factory - ImageProcessorFactory::getInstance().init(_ledString, jsonConfig["blackborderdetector"].get("enable", true).asBool()); + ImageProcessorFactory::getInstance().init( + _ledString, + jsonConfig["blackborderdetector"].get("enable", true).asBool(), + jsonConfig["blackborderdetector"].get("threshold", 0.01).asDouble()); // initialize the color smoothing filter _device = createColorSmoothing(jsonConfig["color"]["smoothing"], _device); diff --git a/libsrc/hyperion/ImageProcessor.cpp b/libsrc/hyperion/ImageProcessor.cpp index ec10f52c..cd39dd1e 100644 --- a/libsrc/hyperion/ImageProcessor.cpp +++ b/libsrc/hyperion/ImageProcessor.cpp @@ -6,46 +6,46 @@ using namespace hyperion; -ImageProcessor::ImageProcessor(const LedString& ledString, bool enableBlackBorderDetector) : - mLedString(ledString), +ImageProcessor::ImageProcessor(const LedString& ledString, bool enableBlackBorderDetector, uint8_t blackborderThreshold) : + _ledString(ledString), _enableBlackBorderRemoval(enableBlackBorderDetector), - _borderProcessor(new BlackBorderProcessor(600, 50, 1)), - mImageToLeds(nullptr) + _borderProcessor(new BlackBorderProcessor(600, 50, 1, blackborderThreshold)), + _imageToLeds(nullptr) { // empty } ImageProcessor::~ImageProcessor() { - delete mImageToLeds; + delete _imageToLeds; delete _borderProcessor; } unsigned ImageProcessor::getLedCount() const { - return mLedString.leds().size(); + return _ledString.leds().size(); } void ImageProcessor::setSize(const unsigned width, const unsigned height) { // Check if the existing buffer-image is already the correct dimensions - if (mImageToLeds && mImageToLeds->width() == width && mImageToLeds->height() == height) + if (_imageToLeds && _imageToLeds->width() == width && _imageToLeds->height() == height) { return; } // Clean up the old buffer and mapping - delete mImageToLeds; + delete _imageToLeds; // Construct a new buffer and mapping - mImageToLeds = new ImageToLedsMap(width, height, 0, 0, mLedString.leds()); + _imageToLeds = new ImageToLedsMap(width, height, 0, 0, _ledString.leds()); } bool ImageProcessor::getScanParameters(size_t led, double &hscanBegin, double &hscanEnd, double &vscanBegin, double &vscanEnd) const { - if (led < mLedString.leds().size()) + if (led < _ledString.leds().size()) { - const Led & l = mLedString.leds()[led]; + const Led & l = _ledString.leds()[led]; hscanBegin = l.minX_frac; hscanEnd = l.maxX_frac; vscanBegin = l.minY_frac; diff --git a/libsrc/hyperion/ImageProcessorFactory.cpp b/libsrc/hyperion/ImageProcessorFactory.cpp index a5a7fb06..70987845 100644 --- a/libsrc/hyperion/ImageProcessorFactory.cpp +++ b/libsrc/hyperion/ImageProcessorFactory.cpp @@ -10,13 +10,25 @@ ImageProcessorFactory& ImageProcessorFactory::getInstance() return instance; } -void ImageProcessorFactory::init(const LedString& ledString, bool enableBlackBorderDetector) +void ImageProcessorFactory::init(const LedString& ledString, bool enableBlackBorderDetector, double blackborderThreshold) { _ledString = ledString; _enableBlackBorderDetector = enableBlackBorderDetector; + + int threshold = int(std::ceil(blackborderThreshold * 255)); + if (threshold < 0) + threshold = 0; + else if (threshold > 255) + threshold = 255; + _blackborderThreshold = uint8_t(threshold); + + if (_enableBlackBorderDetector) + { + std::cout << "Black border threshold set to " << blackborderThreshold << " (" << int(_blackborderThreshold) << ")" << std::endl; + } } ImageProcessor* ImageProcessorFactory::newImageProcessor() const { - return new ImageProcessor(_ledString, _enableBlackBorderDetector); + return new ImageProcessor(_ledString, _enableBlackBorderDetector, _blackborderThreshold); } diff --git a/libsrc/hyperion/hyperion.schema.json b/libsrc/hyperion/hyperion.schema.json index bd059aa4..38192312 100644 --- a/libsrc/hyperion/hyperion.schema.json +++ b/libsrc/hyperion/hyperion.schema.json @@ -223,6 +223,12 @@ "enable" : { "type" : "boolean", "required" : true + }, + "threshold" : { + "type" : "number", + "required" : false, + "minimum" : 0.0, + "maximum" : 1.0 } }, "additionalProperties" : false diff --git a/test/TestBlackBorderDetector.cpp b/test/TestBlackBorderDetector.cpp index 8a92cba9..1d91079f 100644 --- a/test/TestBlackBorderDetector.cpp +++ b/test/TestBlackBorderDetector.cpp @@ -41,7 +41,7 @@ int TC_NO_BORDER() { int result = 0; - BlackBorderDetector detector; + BlackBorderDetector detector(3); { Image image = createImage(64, 64, 0, 0); @@ -60,7 +60,7 @@ int TC_TOP_BORDER() { int result = 0; - BlackBorderDetector detector; + BlackBorderDetector detector(3); { Image image = createImage(64, 64, 12, 0); @@ -79,7 +79,7 @@ int TC_LEFT_BORDER() { int result = 0; - BlackBorderDetector detector; + BlackBorderDetector detector(3); { Image image = createImage(64, 64, 0, 12); @@ -98,7 +98,7 @@ int TC_DUAL_BORDER() { int result = 0; - BlackBorderDetector detector; + BlackBorderDetector detector(3); { Image image = createImage(64, 64, 12, 12); @@ -116,7 +116,7 @@ int TC_UNKNOWN_BORDER() { int result = 0; - BlackBorderDetector detector; + BlackBorderDetector detector(3); { Image image = createImage(64, 64, 30, 30); diff --git a/test/TestBlackBorderProcessor.cpp b/test/TestBlackBorderProcessor.cpp index f5317f05..e0eef0d2 100644 --- a/test/TestBlackBorderProcessor.cpp +++ b/test/TestBlackBorderProcessor.cpp @@ -48,7 +48,7 @@ int main() unsigned borderCnt = 50; unsigned blurCnt = 0; - BlackBorderProcessor processor(unknownCnt, borderCnt, blurCnt); + BlackBorderProcessor processor(unknownCnt, borderCnt, blurCnt, 3); // Start with 'no border' detection Image noBorderImage = createImage(64, 64, 0, 0); From 3a98b625280af7b803dd1deb894edb034463e161 Mon Sep 17 00:00:00 2001 From: johan Date: Mon, 20 Jan 2014 22:35:52 +0100 Subject: [PATCH 09/21] Fix OpenELEC shell scripts to handle args with spaces correctly Former-commit-id: 74743f04fe477388a2bd975f107f6ac952518ca0 --- deploy/hyperion.deps.openelec-rpi.tar.gz.REMOVED.git-id | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/hyperion.deps.openelec-rpi.tar.gz.REMOVED.git-id b/deploy/hyperion.deps.openelec-rpi.tar.gz.REMOVED.git-id index 9fe8e9cc..8dfa8584 100644 --- a/deploy/hyperion.deps.openelec-rpi.tar.gz.REMOVED.git-id +++ b/deploy/hyperion.deps.openelec-rpi.tar.gz.REMOVED.git-id @@ -1 +1 @@ -4d50c38a61c9f32a15b29ef3b3953c2835fa9cac \ No newline at end of file +5e8ca7ba33eb38d828b50971ec94b045025caa78 \ No newline at end of file From 6723c7bf2b386a7e5237fbb1f5737d83455c61c4 Mon Sep 17 00:00:00 2001 From: johan Date: Sat, 25 Jan 2014 17:35:06 +0100 Subject: [PATCH 10/21] Moved test/v4l2_to_png to src/hyperion-v4l2; Added json backend temporarily Former-commit-id: 8c4e0ef7add8016c522d0b4c6f4df8886b905e36 --- dependencies/build/getoptPlusPlus/getoptpp.cc | 2 +- libsrc/protoserver/CMakeLists.txt | 3 - src/CMakeLists.txt | 3 + src/hyperion-v4l2/CMakeLists.txt | 54 ++++++ src/hyperion-v4l2/ProtoConnection.cpp | 181 ++++++++++++++++++ src/hyperion-v4l2/ProtoConnection.h | 89 +++++++++ .../hyperion-v4l2}/V4L2Grabber.cpp | 70 +++++-- .../hyperion-v4l2}/V4L2Grabber.h | 11 +- src/hyperion-v4l2/hyperion-v4l2.cpp | 143 ++++++++++++++ test/CMakeLists.txt | 4 - test/v4l2_to_png/CMakeLists.txt | 17 -- test/v4l2_to_png/v4l2_to_png.cpp | 104 ---------- 12 files changed, 533 insertions(+), 148 deletions(-) create mode 100644 src/hyperion-v4l2/CMakeLists.txt create mode 100644 src/hyperion-v4l2/ProtoConnection.cpp create mode 100644 src/hyperion-v4l2/ProtoConnection.h rename {test/v4l2_to_png => src/hyperion-v4l2}/V4L2Grabber.cpp (91%) rename {test/v4l2_to_png => src/hyperion-v4l2}/V4L2Grabber.h (79%) create mode 100644 src/hyperion-v4l2/hyperion-v4l2.cpp delete mode 100644 test/v4l2_to_png/CMakeLists.txt delete mode 100644 test/v4l2_to_png/v4l2_to_png.cpp diff --git a/dependencies/build/getoptPlusPlus/getoptpp.cc b/dependencies/build/getoptPlusPlus/getoptpp.cc index 4653ebb9..9c385585 100644 --- a/dependencies/build/getoptPlusPlus/getoptpp.cc +++ b/dependencies/build/getoptPlusPlus/getoptpp.cc @@ -120,7 +120,7 @@ void OptionsParser::usage() const { for(i = parameters.parameters.begin(); i != parameters.parameters.end(); i++) { - cerr.width(30); + cerr.width(31); cerr << std::left << " " + (*i)->usageLine(); cerr.width(40); diff --git a/libsrc/protoserver/CMakeLists.txt b/libsrc/protoserver/CMakeLists.txt index 58d67564..a66ef336 100644 --- a/libsrc/protoserver/CMakeLists.txt +++ b/libsrc/protoserver/CMakeLists.txt @@ -3,9 +3,6 @@ set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/protoserver) set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/protoserver) -# add protocol buffers -find_package(Protobuf REQUIRED) - include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${PROTOBUF_INCLUDE_DIRS}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d1f51d7d..b06b4ee7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,2 +1,5 @@ add_subdirectory(hyperiond) add_subdirectory(hyperion-remote) +if (ENABLE_V4L2) + add_subdirectory(hyperion-v4l2) +endif (ENABLE_V4L2) diff --git a/src/hyperion-v4l2/CMakeLists.txt b/src/hyperion-v4l2/CMakeLists.txt new file mode 100644 index 00000000..603ce42e --- /dev/null +++ b/src/hyperion-v4l2/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required(VERSION 2.8) + +project(hyperion-v4l2) + +# add protocol buffers +find_package(Protobuf REQUIRED) + +# find Qt4 +find_package(Qt4 REQUIRED QtCore QtGui QtNetwork) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${PROTOBUF_INCLUDE_DIRS} + ${QT_INCLUDES} +) + +set(Hyperion_V4L2_HEADERS + V4L2Grabber.h + ProtoConnection.h +) + +set(Hyperion_V4L2_SOURCES + hyperion-v4l2.cpp + V4L2Grabber.cpp + ProtoConnection.cpp +) + +set(Hyperion_V4L2_PROTOS + ${CMAKE_CURRENT_SOURCE_DIR}/../../libsrc/protoserver/message.proto +) + +protobuf_generate_cpp(Hyperion_V4L2_PROTO_SRCS Hyperion_V4L2_PROTO_HDRS + ${Hyperion_V4L2_PROTOS} +) + +add_executable(hyperion-v4l2 + ${Hyperion_V4L2_HEADERS} + ${Hyperion_V4L2_SOURCES} + ${Hyperion_V4L2_PROTO_SRCS} + ${Hyperion_V4L2_PROTO_HDRS} +) + +target_link_libraries(hyperion-v4l2 + getoptPlusPlus + jsoncpp + hyperion-utils + ${PROTOBUF_LIBRARIES} + pthread +) + +qt4_use_modules(hyperion-v4l2 + Core + Gui + Network) diff --git a/src/hyperion-v4l2/ProtoConnection.cpp b/src/hyperion-v4l2/ProtoConnection.cpp new file mode 100644 index 00000000..f679bcbc --- /dev/null +++ b/src/hyperion-v4l2/ProtoConnection.cpp @@ -0,0 +1,181 @@ +// stl includes +#include + +// Qt includes +#include + +// hyperion-v4l2 includes +#include "ProtoConnection.h" + +ProtoConnection::ProtoConnection(const std::string & a) : + _socket() +{ + QString address(a.c_str()); + QStringList parts = address.split(":"); + if (parts.size() != 2) + { + throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(address).toStdString()); + } + + bool ok; + uint16_t port = parts[1].toUShort(&ok); + if (!ok) + { + throw std::runtime_error(QString("Wrong address: Unable to parse the port number (%1)").arg(parts[1]).toStdString()); + } + + _socket.connectToHost(parts[0], port); + if (!_socket.waitForConnected()) + { + throw std::runtime_error("Unable to connect to host"); + } + + std::cout << "Connected to " << a << std::endl; +} + +ProtoConnection::~ProtoConnection() +{ + _socket.close(); +} + +void ProtoConnection::setColor(std::vector colors, int priority, int duration) +{ + // create command + Json::Value command; + command["command"] = "color"; + command["priority"] = priority; + Json::Value & rgbValue = command["color"]; + for (const QColor & color : colors) + { + rgbValue.append(color.red()); + rgbValue.append(color.green()); + rgbValue.append(color.blue()); + } + if (duration > 0) + { + command["duration"] = duration; + } + + // send command message + Json::Value reply = sendMessage(command); + + // parse reply message + parseReply(reply); +} + +void ProtoConnection::setImage(const Image &image, int priority, int duration) +{ + // ensure the image has RGB888 format + QByteArray binaryImage = QByteArray::fromRawData(reinterpret_cast(image.memptr()), image.width() * image.height() * 3); + const QByteArray base64Image = binaryImage.toBase64(); + + // create command + Json::Value command; + command["command"] = "image"; + command["priority"] = priority; + command["imagewidth"] = image.width(); + command["imageheight"] = image.height(); + command["imagedata"] = std::string(base64Image.data(), base64Image.size()); + if (duration > 0) + { + command["duration"] = duration; + } + + // send command message + Json::Value reply = sendMessage(command); + + // parse reply message + parseReply(reply); +} + +void ProtoConnection::clear(int priority) +{ + std::cout << "Clear priority channel " << priority << std::endl; + + // create command + Json::Value command; + command["command"] = "clear"; + command["priority"] = priority; + + // send command message + Json::Value reply = sendMessage(command); + + // parse reply message + parseReply(reply); +} + +void ProtoConnection::clearAll() +{ + std::cout << "Clear all priority channels" << std::endl; + + // create command + Json::Value command; + command["command"] = "clearall"; + + // send command message + Json::Value reply = sendMessage(command); + + // parse reply message + parseReply(reply); +} + +Json::Value ProtoConnection::sendMessage(const Json::Value & message) +{ + // serialize message (FastWriter already appends a newline) + std::string serializedMessage = Json::FastWriter().write(message); + + // write message + _socket.write(serializedMessage.c_str()); + if (!_socket.waitForBytesWritten()) + { + throw std::runtime_error("Error while writing data to host"); + } + + // read reply data + QByteArray serializedReply; + while (!serializedReply.contains('\n')) + { + // receive reply + if (!_socket.waitForReadyRead()) + { + throw std::runtime_error("Error while reading data from host"); + } + + serializedReply += _socket.readAll(); + } + int bytes = serializedReply.indexOf('\n') + 1; // Find the end of message + + // parse reply data + Json::Reader jsonReader; + Json::Value reply; + if (!jsonReader.parse(serializedReply.constData(), serializedReply.constData() + bytes, reply)) + { + throw std::runtime_error("Error while parsing reply: invalid json"); + } + + return reply; +} + +bool ProtoConnection::parseReply(const Json::Value &reply) +{ + bool success = false; + std::string reason = "No error info"; + + try + { + success = reply.get("success", false).asBool(); + if (!success) + reason = reply.get("error", reason).asString(); + } + catch (const std::runtime_error &) + { + // Some json parsing error: ignore and set parsing error + } + + if (!success) + { + throw std::runtime_error("Error: " + reason); + } + + return success; +} diff --git a/src/hyperion-v4l2/ProtoConnection.h b/src/hyperion-v4l2/ProtoConnection.h new file mode 100644 index 00000000..cbff597d --- /dev/null +++ b/src/hyperion-v4l2/ProtoConnection.h @@ -0,0 +1,89 @@ +#pragma once + +// stl includes +#include + +// Qt includes +#include +#include +#include +#include + +// hyperion util +#include +#include + +// jsoncpp includes +#include + +/// +/// Connection class to setup an connection to the hyperion server and execute commands +/// +class ProtoConnection +{ +public: + /// + /// Constructor + /// + /// @param address The address of the Hyperion server (for example "192.168.0.32:19444) + /// + ProtoConnection(const std::string & address); + + /// + /// Destructor + /// + ~ProtoConnection(); + + /// + /// Set all leds to the specified color + /// + /// @param color The color + /// @param priority The priority + /// @param duration The duration in milliseconds + /// + void setColor(std::vector color, int priority, int duration); + + /// + /// Set the leds according to the given image (assume the image is stretched to the display size) + /// + /// @param image The image + /// @param priority The priority + /// @param duration The duration in milliseconds + /// + void setImage(const Image & image, int priority, int duration); + + /// + /// Clear the given priority channel + /// + /// @param priority The priority + /// + void clear(int priority); + + /// + /// Clear all priority channels + /// + void clearAll(); + +private: + /// + /// Send a json command message and receive its reply + /// + /// @param message The message to send + /// + /// @return The returned reply + /// + Json::Value sendMessage(const Json::Value & message); + + /// + /// Parse a reply message + /// + /// @param reply The received reply + /// + /// @return true if the reply indicates success + /// + bool parseReply(const Json::Value & reply); + +private: + /// The TCP-Socket with the connection to the server + QTcpSocket _socket; +}; diff --git a/test/v4l2_to_png/V4L2Grabber.cpp b/src/hyperion-v4l2/V4L2Grabber.cpp similarity index 91% rename from test/v4l2_to_png/V4L2Grabber.cpp rename to src/hyperion-v4l2/V4L2Grabber.cpp index af1a3e9f..f971ad25 100644 --- a/test/v4l2_to_png/V4L2Grabber.cpp +++ b/src/hyperion-v4l2/V4L2Grabber.cpp @@ -12,9 +12,6 @@ #include #include -#include -#include - #include "V4L2Grabber.h" #define CLEAR(x) memset(&(x), 0, sizeof(x)) @@ -37,18 +34,20 @@ static void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t & r, uint8_t & g, u } -V4L2Grabber::V4L2Grabber(const std::string &device, int input, VideoStandard videoStandard, int cropHorizontal, int cropVertical, int frameDecimation, int pixelDecimation) : +V4L2Grabber::V4L2Grabber(const std::string &device, int input, VideoStandard videoStandard, int width, int height, int cropHorizontal, int cropVertical, int frameDecimation, int pixelDecimation) : _deviceName(device), _ioMethod(IO_METHOD_MMAP), _fileDescriptor(-1), _buffers(), - _width(0), - _height(0), + _width(width), + _height(height), _cropWidth(cropHorizontal), _cropHeight(cropVertical), _frameDecimation(std::max(1, frameDecimation)), _pixelDecimation(std::max(1, pixelDecimation)), - _currentFrame(0) + _currentFrame(0), + _callback(nullptr), + _callbackArg(nullptr) { open_device(); init_device(videoStandard, input); @@ -60,6 +59,12 @@ V4L2Grabber::~V4L2Grabber() close_device(); } +void V4L2Grabber::setCallback(V4L2Grabber::ImageCallback callback, void *arg) +{ + _callback = callback; + _callbackArg = arg; +} + void V4L2Grabber::start() { start_capturing(); @@ -343,6 +348,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) break; } + // get the current settings struct v4l2_format fmt; CLEAR(fmt); @@ -358,6 +364,32 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) exit(EXIT_FAILURE); } + if (_width > 0 || _height > 0) + { + if (_width > 0) + { + fmt.fmt.pix.width = _width; + } + + if (fmt.fmt.pix.height > 0) + { + fmt.fmt.pix.height = _height; + } + + // set the settings + if (-1 == xioctl(VIDIOC_S_FMT, &fmt)) + { + errno_exit("VIDIOC_S_FMT"); + } + + // get the format settings again + // (the size may not have been accepted without an error) + if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) + { + errno_exit("VIDIOC_G_FMT"); + } + } + // store width & height _width = fmt.fmt.pix.width; _height = fmt.fmt.pix.height; @@ -468,6 +500,8 @@ void V4L2Grabber::stop_capturing() int V4L2Grabber::read_frame() { + bool rc = false; + struct v4l2_buffer buf; switch (_ioMethod) { @@ -490,7 +524,7 @@ int V4L2Grabber::read_frame() } } - process_image(_buffers[0].start, size); + rc = process_image(_buffers[0].start, size); break; case IO_METHOD_MMAP: @@ -518,7 +552,7 @@ int V4L2Grabber::read_frame() assert(buf.index < _buffers.size()); - process_image(_buffers[buf.index].start, buf.bytesused); + rc = process_image(_buffers[buf.index].start, buf.bytesused); if (-1 == xioctl(VIDIOC_QBUF, &buf)) { @@ -558,7 +592,7 @@ int V4L2Grabber::read_frame() } } - process_image((void *)buf.m.userptr, buf.bytesused); + rc = process_image((void *)buf.m.userptr, buf.bytesused); if (-1 == xioctl(VIDIOC_QBUF, &buf)) { @@ -567,10 +601,10 @@ int V4L2Grabber::read_frame() break; } - return 1; + return rc ? 1 : 0; } -void V4L2Grabber::process_image(const void *p, int size) +bool V4L2Grabber::process_image(const void *p, int size) { if (++_currentFrame >= _frameDecimation) { @@ -584,14 +618,15 @@ void V4L2Grabber::process_image(const void *p, int size) { process_image(reinterpret_cast(p)); _currentFrame = 0; // restart counting + return true; } } + + return false; } void V4L2Grabber::process_image(const uint8_t * data) { - std::cout << "process image" << std::endl; - int width = (_width - 2 * _cropWidth + _pixelDecimation/2) / _pixelDecimation; int height = (_height - 2 * _cropHeight + _pixelDecimation/2) / _pixelDecimation; @@ -611,9 +646,10 @@ void V4L2Grabber::process_image(const uint8_t * data) } } - // store as PNG - QImage pngImage((const uint8_t *) image.memptr(), width, height, 3*width, QImage::Format_RGB888); - pngImage.save("screenshot.png"); + if (_callback != nullptr) + { + (*_callback)(_callbackArg, image); + } } int V4L2Grabber::xioctl(int request, void *arg) diff --git a/test/v4l2_to_png/V4L2Grabber.h b/src/hyperion-v4l2/V4L2Grabber.h similarity index 79% rename from test/v4l2_to_png/V4L2Grabber.h rename to src/hyperion-v4l2/V4L2Grabber.h index c028e745..2588e7ce 100644 --- a/test/v4l2_to_png/V4L2Grabber.h +++ b/src/hyperion-v4l2/V4L2Grabber.h @@ -14,14 +14,18 @@ class V4L2Grabber { public: + typedef void (*ImageCallback)(void * arg, const Image & image); + enum VideoStandard { PAL, NTSC, NO_CHANGE }; public: - V4L2Grabber(const std::string & device, int input, VideoStandard videoStandard, int cropHorizontal, int cropVertical, int frameDecimation, int pixelDecimation); + V4L2Grabber(const std::string & device, int input, VideoStandard videoStandard, int width, int height, int cropHorizontal, int cropVertical, int frameDecimation, int pixelDecimation); virtual ~V4L2Grabber(); + void setCallback(ImageCallback callback, void * arg); + void start(); void capture(int frameCount = -1); @@ -49,7 +53,7 @@ private: int read_frame(); - void process_image(const void *p, int size); + bool process_image(const void *p, int size); void process_image(const uint8_t *p); @@ -83,4 +87,7 @@ private: const int _pixelDecimation; int _currentFrame; + + ImageCallback _callback; + void * _callbackArg; }; diff --git a/src/hyperion-v4l2/hyperion-v4l2.cpp b/src/hyperion-v4l2/hyperion-v4l2.cpp new file mode 100644 index 00000000..454832be --- /dev/null +++ b/src/hyperion-v4l2/hyperion-v4l2.cpp @@ -0,0 +1,143 @@ + +// STL includes +#include +#include + +// QT includes +#include + +// getoptPlusPLus includes +#include + +#include "V4L2Grabber.h" +#include "ProtoConnection.h" + +using namespace vlofgren; + +/// Data parameter for the video standard +typedef vlofgren::PODParameter VideoStandardParameter; + +namespace vlofgren { + /// Translates a string (as passed on the commandline) to a color standard + /// + /// @param[in] s The string (as passed on the commandline) + /// @return The color standard + /// @throws Parameter::ParameterRejected If the string did not result in a video standard + template<> + V4L2Grabber::VideoStandard VideoStandardParameter::validate(const std::string& s) throw (Parameter::ParameterRejected) + { + QString input = QString::fromStdString(s).toLower(); + + if (input == "pal") + { + return V4L2Grabber::PAL; + } + else if (input == "ntsc") + { + return V4L2Grabber::NTSC; + } + else if (input == "no-change") + { + return V4L2Grabber::NO_CHANGE; + } + + throw Parameter::ParameterRejected("Invalid value for video standard. Valid values are: PAL, NTSC, and NO-CHANGE"); + return V4L2Grabber::NO_CHANGE; + } +} + +// save the image as screenshot +void saveScreenshot(void *, const Image & image) +{ + // store as PNG + QImage pngImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888); + pngImage.save("screenshot.png"); +} + +// send the image to Hyperion +void sendImage(void * arg, const Image & image) +{ + ProtoConnection * connection = static_cast(arg); + connection->setImage(image, 50, 200); +} + +int main(int argc, char** argv) +{ + try + { + // create the option parser and initialize all parameters + OptionsParser optionParser("Simple application to send a command to hyperion using the Json interface"); + ParameterSet & parameters = optionParser.getParameters(); + + StringParameter & argDevice = parameters.add ('d', "device", "The device to use [default=/dev/video0]"); + VideoStandardParameter & argVideoStandard = parameters.add('v', "video-standard", "The used video standard. Valid values are PAL. NYSC, or NO-CHANGE [default=PAL]"); + IntParameter & argInput = parameters.add (0x0, "input", "Input channel (optional)"); + IntParameter & argWidth = parameters.add (0x0, "width", "Try to set the width of the video input (optional)"); + IntParameter & argHeight = parameters.add (0x0, "height", "Try to set the height of the video input (optional)"); + IntParameter & argCropWidth = parameters.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides in the picture before decimation [default=0]"); + IntParameter & argCropHeight = parameters.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom in the picture before decimation [default=0]"); + IntParameter & argSizeDecimation = parameters.add ('s', "size-decimator", "Decimation factor for the output size [default=1]"); + IntParameter & argFrameDecimation = parameters.add ('f', "frame-decimator","Decimation factor for the video frames [default=1]"); + SwitchParameter<> & argScreenshot = parameters.add> (0x0, "screenshot", "Take a single screenshot, save it to file and quit"); + StringParameter & argAddress = parameters.add ('a', "address", "Set the address of the hyperion server [default: 127.0.0.1:19445]"); + IntParameter & argPriority = parameters.add ('p', "priority", "Use the provided priority channel (the lower the number, the higher the priority) [default: 800]"); + SwitchParameter<> & argHelp = parameters.add> ('h', "help", "Show this help message and exit"); + + // set defaults + argDevice.setDefault("/dev/video0"); + argVideoStandard.setDefault(V4L2Grabber::PAL); + argInput.setDefault(-1); + argWidth.setDefault(-1); + argHeight.setDefault(-1); + argCropWidth.setDefault(0); + argCropHeight.setDefault(0); + argSizeDecimation.setDefault(1); + argFrameDecimation.setDefault(1); + argAddress.setDefault("127.0.0.1:19445"); + argPriority.setDefault(800); + + // parse all options + optionParser.parse(argc, const_cast(argv)); + + // check if we need to display the usage. exit if we do. + if (argHelp.isSet()) + { + optionParser.usage(); + return 0; + } + + V4L2Grabber grabber( + argDevice.getValue(), + argInput.getValue(), + argVideoStandard.getValue(), + argWidth.getValue(), + argHeight.getValue(), + std::max(0, argCropWidth.getValue()), + std::max(0, argCropHeight.getValue()), + std::max(1, argFrameDecimation.getValue()), + std::max(1, argSizeDecimation.getValue())); + + grabber.start(); + if (argScreenshot.isSet()) + { + grabber.setCallback(&saveScreenshot, nullptr); + grabber.capture(1); + } + else + { + ProtoConnection connection(argAddress.getValue()); + + grabber.setCallback(&sendImage, &connection); + grabber.capture(); + } + grabber.stop(); + } + catch (const std::runtime_error & e) + { + // An error occured. Display error and quit + std::cerr << e.what() << std::endl; + return 1; + } + + return 0; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2ac4a4b3..bd46d059 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -35,10 +35,6 @@ if (ENABLE_DISPMANX) add_subdirectory(dispmanx2png) endif (ENABLE_DISPMANX) -if (ENABLE_V4L2) - add_subdirectory(v4l2_to_png) -endif (ENABLE_V4L2) - add_executable(test_blackborderdetector TestBlackBorderDetector.cpp) target_link_libraries(test_blackborderdetector diff --git a/test/v4l2_to_png/CMakeLists.txt b/test/v4l2_to_png/CMakeLists.txt deleted file mode 100644 index ab259048..00000000 --- a/test/v4l2_to_png/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ - -# find Qt4 -find_package(Qt4 REQUIRED QtCore QtGui) - -include_directories(${QT_INCLUDES}) - -add_executable(v4l2_to_png - v4l2_to_png.cpp - V4L2Grabber.h - V4L2Grabber.cpp) - -target_link_libraries(v4l2_to_png - getoptPlusPlus) - -qt4_use_modules(v4l2_to_png - Core - Gui) diff --git a/test/v4l2_to_png/v4l2_to_png.cpp b/test/v4l2_to_png/v4l2_to_png.cpp deleted file mode 100644 index 7f418018..00000000 --- a/test/v4l2_to_png/v4l2_to_png.cpp +++ /dev/null @@ -1,104 +0,0 @@ - -// STL includes -#include -#include - -// QT includes -#include - -// getoptPlusPLus includes -#include - -#include "V4L2Grabber.h" - -using namespace vlofgren; - -/// Data parameter for the video standard -typedef vlofgren::PODParameter VideoStandardParameter; - -namespace vlofgren { - /// Translates a string (as passed on the commandline) to a color standard - /// - /// @param[in] s The string (as passed on the commandline) - /// @return The color standard - /// @throws Parameter::ParameterRejected If the string did not result in a video standard - template<> - V4L2Grabber::VideoStandard VideoStandardParameter::validate(const std::string& s) throw (Parameter::ParameterRejected) - { - QString input = QString::fromStdString(s).toLower(); - - if (input == "pal") - { - return V4L2Grabber::PAL; - } - else if (input == "ntsc") - { - return V4L2Grabber::NTSC; - } - else if (input == "no-change") - { - return V4L2Grabber::NO_CHANGE; - } - - throw Parameter::ParameterRejected("Invalid value for video standard. Valid values are: PAL, NTSC, and NO-CHANGE"); - return V4L2Grabber::NO_CHANGE; - } -} - - -int main(int argc, char** argv) -{ - try - { - // create the option parser and initialize all parameters - OptionsParser optionParser("Simple application to send a command to hyperion using the Json interface"); - ParameterSet & parameters = optionParser.getParameters(); - - StringParameter & argDevice = parameters.add ('d', "device", "The device to use [default=/dev/video0]"); - VideoStandardParameter & argVideoStandard = parameters.add('v', "video-standard", "The used video standard. Valid values are PAL. NYSC, or NO-CHANGE [default=PAL]"); - IntParameter & argInput = parameters.add ('i', "input", "Input channel [default=0]"); - IntParameter & argCropWidth = parameters.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides in the picture before decimation [default=0]"); - IntParameter & argCropHeight = parameters.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom in the picture before decimation [default=0]"); - IntParameter & argSizeDecimation = parameters.add ('s', "size-decimator", "Decimation factor for the output size [default=1]"); - SwitchParameter<> & argHelp = parameters.add > ('h', "help", "Show this help message and exit"); - - // set defaults - argDevice.setDefault("/dev/video0"); - argVideoStandard.setDefault(V4L2Grabber::PAL); - argInput.setDefault(0); - argCropWidth.setDefault(0); - argCropHeight.setDefault(0); - argSizeDecimation.setDefault(1); - - // parse all options - optionParser.parse(argc, const_cast(argv)); - - // check if we need to display the usage. exit if we do. - if (argHelp.isSet()) - { - optionParser.usage(); - return 0; - } - - V4L2Grabber grabber( - argDevice.getValue(), - argInput.getValue(), - argVideoStandard.getValue(), - std::max(0, argCropWidth.getValue()), - std::max(0, argCropHeight.getValue()), - 1, - argSizeDecimation.getValue()); - - grabber.start(); - grabber.capture(1); - grabber.stop(); - } - catch (const std::runtime_error & e) - { - // An error occured. Display error and quit - std::cerr << e.what() << std::endl; - return 1; - } - - return 0; -} From 4f7997bbfe72363db8cb653641eb834922878bdb Mon Sep 17 00:00:00 2001 From: johan Date: Sat, 25 Jan 2014 21:00:48 +0100 Subject: [PATCH 11/21] Switched json to proto Former-commit-id: 1a1c70b93c9b4efe17f1c1d2da246c949affa1f0 --- src/hyperion-v4l2/CMakeLists.txt | 1 - src/hyperion-v4l2/ProtoConnection.cpp | 142 ++++++++++++-------------- src/hyperion-v4l2/ProtoConnection.h | 12 +-- 3 files changed, 73 insertions(+), 82 deletions(-) diff --git a/src/hyperion-v4l2/CMakeLists.txt b/src/hyperion-v4l2/CMakeLists.txt index 603ce42e..58b62ac1 100644 --- a/src/hyperion-v4l2/CMakeLists.txt +++ b/src/hyperion-v4l2/CMakeLists.txt @@ -42,7 +42,6 @@ add_executable(hyperion-v4l2 target_link_libraries(hyperion-v4l2 getoptPlusPlus - jsoncpp hyperion-utils ${PROTOBUF_LIBRARIES} pthread diff --git a/src/hyperion-v4l2/ProtoConnection.cpp b/src/hyperion-v4l2/ProtoConnection.cpp index f679bcbc..07544a8e 100644 --- a/src/hyperion-v4l2/ProtoConnection.cpp +++ b/src/hyperion-v4l2/ProtoConnection.cpp @@ -38,26 +38,17 @@ ProtoConnection::~ProtoConnection() _socket.close(); } -void ProtoConnection::setColor(std::vector colors, int priority, int duration) +void ProtoConnection::setColor(const ColorRgb & color, int priority, int duration) { - // create command - Json::Value command; - command["command"] = "color"; - command["priority"] = priority; - Json::Value & rgbValue = command["color"]; - for (const QColor & color : colors) - { - rgbValue.append(color.red()); - rgbValue.append(color.green()); - rgbValue.append(color.blue()); - } - if (duration > 0) - { - command["duration"] = duration; - } + proto::HyperionRequest request; + request.set_command(proto::HyperionRequest::COLOR); + proto::ColorRequest * colorRequest = request.MutableExtension(proto::ColorRequest::colorRequest); + colorRequest->set_rgbcolor((color.red << 16) | (color.green << 8) | color.blue); + colorRequest->set_priority(priority); + colorRequest->set_duration(duration); // send command message - Json::Value reply = sendMessage(command); + proto::HyperionReply reply = sendMessage(request); // parse reply message parseReply(reply); @@ -65,40 +56,31 @@ void ProtoConnection::setColor(std::vector colors, int priority, int dur void ProtoConnection::setImage(const Image &image, int priority, int duration) { - // ensure the image has RGB888 format - QByteArray binaryImage = QByteArray::fromRawData(reinterpret_cast(image.memptr()), image.width() * image.height() * 3); - const QByteArray base64Image = binaryImage.toBase64(); - - // create command - Json::Value command; - command["command"] = "image"; - command["priority"] = priority; - command["imagewidth"] = image.width(); - command["imageheight"] = image.height(); - command["imagedata"] = std::string(base64Image.data(), base64Image.size()); - if (duration > 0) - { - command["duration"] = duration; - } + proto::HyperionRequest request; + request.set_command(proto::HyperionRequest::IMAGE); + proto::ImageRequest * imageRequest = request.MutableExtension(proto::ImageRequest::imageRequest); + imageRequest->set_imagedata(image.memptr(), image.width() * image.height() * 3); + imageRequest->set_imagewidth(image.width()); + imageRequest->set_imageheight(image.height()); + imageRequest->set_priority(priority); + imageRequest->set_duration(duration); // send command message - Json::Value reply = sendMessage(command); + proto::HyperionReply reply = sendMessage(request); // parse reply message - parseReply(reply); +// parseReply(reply); } void ProtoConnection::clear(int priority) { - std::cout << "Clear priority channel " << priority << std::endl; - - // create command - Json::Value command; - command["command"] = "clear"; - command["priority"] = priority; + proto::HyperionRequest request; + request.set_command(proto::HyperionRequest::CLEAR); + proto::ClearRequest * clearRequest = request.MutableExtension(proto::ClearRequest::clearRequest); + clearRequest->set_priority(priority); // send command message - Json::Value reply = sendMessage(command); + proto::HyperionReply reply = sendMessage(request); // parse reply message parseReply(reply); @@ -106,35 +88,44 @@ void ProtoConnection::clear(int priority) void ProtoConnection::clearAll() { - std::cout << "Clear all priority channels" << std::endl; - - // create command - Json::Value command; - command["command"] = "clearall"; + proto::HyperionRequest request; + request.set_command(proto::HyperionRequest::CLEARALL); // send command message - Json::Value reply = sendMessage(command); + proto::HyperionReply reply = sendMessage(request); // parse reply message parseReply(reply); } -Json::Value ProtoConnection::sendMessage(const Json::Value & message) +proto::HyperionReply ProtoConnection::sendMessage(const proto::HyperionRequest &message) { // serialize message (FastWriter already appends a newline) - std::string serializedMessage = Json::FastWriter().write(message); + std::string serializedMessage = message.SerializeAsString(); + + int length = serializedMessage.size(); + const uint8_t header[] = { + uint8_t((length >> 24) & 0xFF), + uint8_t((length >> 16) & 0xFF), + uint8_t((length >> 8) & 0xFF), + uint8_t((length ) & 0xFF)}; // write message - _socket.write(serializedMessage.c_str()); + int count = 0; + count += _socket.write(reinterpret_cast(header), 4); + count += _socket.write(reinterpret_cast(serializedMessage.data()), length); if (!_socket.waitForBytesWritten()) { throw std::runtime_error("Error while writing data to host"); } + /* // read reply data QByteArray serializedReply; - while (!serializedReply.contains('\n')) + length = -1; + while (serializedReply.size() != length) { + std::cout << length << std::endl; // receive reply if (!_socket.waitForReadyRead()) { @@ -142,39 +133,40 @@ Json::Value ProtoConnection::sendMessage(const Json::Value & message) } serializedReply += _socket.readAll(); - } - int bytes = serializedReply.indexOf('\n') + 1; // Find the end of message + if (length < 0 && serializedReply.size() >= 4) + { + std::cout << (int) serializedReply[3] << std::endl; + std::cout << (int) serializedReply[2] << std::endl; + std::cout << (int) serializedReply[1] << std::endl; + std::cout << (int) serializedReply[0] << std::endl; + + length = (uint8_t(serializedReply[0]) << 24) | (uint8_t(serializedReply[1]) << 16) | (uint8_t(serializedReply[2]) << 8) | uint8_t(serializedReply[3]) ; + } + } + + std::cout << length << std::endl; +*/ // parse reply data - Json::Reader jsonReader; - Json::Value reply; - if (!jsonReader.parse(serializedReply.constData(), serializedReply.constData() + bytes, reply)) - { - throw std::runtime_error("Error while parsing reply: invalid json"); - } - + proto::HyperionReply reply; +// reply.ParseFromArray(serializedReply.constData()+4, length); return reply; } -bool ProtoConnection::parseReply(const Json::Value &reply) +bool ProtoConnection::parseReply(const proto::HyperionReply &reply) { bool success = false; - std::string reason = "No error info"; - try + if (!reply.success()) { - success = reply.get("success", false).asBool(); - if (!success) - reason = reply.get("error", reason).asString(); - } - catch (const std::runtime_error &) - { - // Some json parsing error: ignore and set parsing error - } - - if (!success) - { - throw std::runtime_error("Error: " + reason); + if (reply.has_error()) + { + throw std::runtime_error("Error: " + reply.error()); + } + else + { + throw std::runtime_error("Error: No error info"); + } } return success; diff --git a/src/hyperion-v4l2/ProtoConnection.h b/src/hyperion-v4l2/ProtoConnection.h index cbff597d..b20faf34 100644 --- a/src/hyperion-v4l2/ProtoConnection.h +++ b/src/hyperion-v4l2/ProtoConnection.h @@ -14,7 +14,7 @@ #include // jsoncpp includes -#include +#include /// /// Connection class to setup an connection to the hyperion server and execute commands @@ -41,7 +41,7 @@ public: /// @param priority The priority /// @param duration The duration in milliseconds /// - void setColor(std::vector color, int priority, int duration); + void setColor(const ColorRgb & color, int priority, int duration = 1); /// /// Set the leds according to the given image (assume the image is stretched to the display size) @@ -50,7 +50,7 @@ public: /// @param priority The priority /// @param duration The duration in milliseconds /// - void setImage(const Image & image, int priority, int duration); + void setImage(const Image & image, int priority, int duration = -1); /// /// Clear the given priority channel @@ -66,13 +66,13 @@ public: private: /// - /// Send a json command message and receive its reply + /// Send a command message and receive its reply /// /// @param message The message to send /// /// @return The returned reply /// - Json::Value sendMessage(const Json::Value & message); + proto::HyperionReply sendMessage(const proto::HyperionRequest & message); /// /// Parse a reply message @@ -81,7 +81,7 @@ private: /// /// @return true if the reply indicates success /// - bool parseReply(const Json::Value & reply); + bool parseReply(const proto::HyperionReply & reply); private: /// The TCP-Socket with the connection to the server From df9b33f560e3808d34282838b56b3e2ae560d55d Mon Sep 17 00:00:00 2001 From: johan Date: Sun, 26 Jan 2014 12:35:31 +0100 Subject: [PATCH 12/21] Replaced exit statements for exceptions Former-commit-id: 1eb180620fb59613eaa02da84026ffdcb28803a4 --- src/hyperion-v4l2/V4L2Grabber.cpp | 677 +++++++++++++++--------------- src/hyperion-v4l2/V4L2Grabber.h | 4 +- 2 files changed, 351 insertions(+), 330 deletions(-) diff --git a/src/hyperion-v4l2/V4L2Grabber.cpp b/src/hyperion-v4l2/V4L2Grabber.cpp index f971ad25..dd5cee31 100644 --- a/src/hyperion-v4l2/V4L2Grabber.cpp +++ b/src/hyperion-v4l2/V4L2Grabber.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -92,14 +94,13 @@ void V4L2Grabber::capture(int frameCount) if (-1 == r) { if (EINTR == errno) - continue; - errno_exit("select"); + continue; + throw_errno_exception("select"); } if (0 == r) { - fprintf(stderr, "select timeout\n"); - exit(EXIT_FAILURE); + throw_exception("select timeout"); } if (read_frame()) @@ -119,317 +120,327 @@ void V4L2Grabber::stop() void V4L2Grabber::open_device() { - struct stat st; + struct stat st; - if (-1 == stat(_deviceName.c_str(), &st)) - { - fprintf(stderr, "Cannot identify '%s': %d, %s\n", _deviceName.c_str(), errno, strerror(errno)); - exit(EXIT_FAILURE); - } + if (-1 == stat(_deviceName.c_str(), &st)) + { + std::ostringstream oss; + oss << "Cannot identify '" << _deviceName << "'"; + throw_errno_exception(oss.str()); + } - if (!S_ISCHR(st.st_mode)) - { - fprintf(stderr, "%s is no device\n", _deviceName.c_str()); - exit(EXIT_FAILURE); - } + if (!S_ISCHR(st.st_mode)) + { + std::ostringstream oss; + oss << "'" << _deviceName << "' is no device"; + throw_exception(oss.str()); + } - _fileDescriptor = open(_deviceName.c_str(), O_RDWR /* required */ | O_NONBLOCK, 0); + _fileDescriptor = open(_deviceName.c_str(), O_RDWR /* required */ | O_NONBLOCK, 0); - if (-1 == _fileDescriptor) - { - fprintf(stderr, "Cannot open '%s': %d, %s\n", _deviceName.c_str(), errno, strerror(errno)); - exit(EXIT_FAILURE); - } + if (-1 == _fileDescriptor) + { + std::ostringstream oss; + oss << "Cannot open '" << _deviceName << "'"; + throw_errno_exception(oss.str()); + } } void V4L2Grabber::close_device() { - if (-1 == close(_fileDescriptor)) - errno_exit("close"); + if (-1 == close(_fileDescriptor)) + throw_errno_exception("close"); - _fileDescriptor = -1; + _fileDescriptor = -1; } void V4L2Grabber::init_read(unsigned int buffer_size) { - _buffers.resize(1); + _buffers.resize(1); - _buffers[0].length = buffer_size; - _buffers[0].start = malloc(buffer_size); + _buffers[0].length = buffer_size; + _buffers[0].start = malloc(buffer_size); - if (!_buffers[0].start) { - fprintf(stderr, "Out of memory\n"); - exit(EXIT_FAILURE); - } + if (!_buffers[0].start) { + throw_exception("Out of memory"); + } } void V4L2Grabber::init_mmap() { - struct v4l2_requestbuffers req; + struct v4l2_requestbuffers req; - CLEAR(req); + CLEAR(req); - req.count = 4; - req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - req.memory = V4L2_MEMORY_MMAP; + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; - if (-1 == xioctl(VIDIOC_REQBUFS, &req)) { - if (EINVAL == errno) { - fprintf(stderr, "%s does not support memory mapping\n", _deviceName.c_str()); - exit(EXIT_FAILURE); - } else { - errno_exit("VIDIOC_REQBUFS"); - } + if (-1 == xioctl(VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) { + std::ostringstream oss; + oss << "'" << _deviceName << "' does not support memory mapping"; + throw_exception(oss.str()); + } else { + throw_errno_exception("VIDIOC_REQBUFS"); } + } - if (req.count < 2) { - fprintf(stderr, "Insufficient buffer memory on %s\n", _deviceName.c_str()); - exit(EXIT_FAILURE); - } + if (req.count < 2) { + std::ostringstream oss; + oss << "Insufficient buffer memory on " << _deviceName; + throw_exception(oss.str()); + } - _buffers.resize(req.count); + _buffers.resize(req.count); - for (size_t n_buffers = 0; n_buffers < req.count; ++n_buffers) { - struct v4l2_buffer buf; + for (size_t n_buffers = 0; n_buffers < req.count; ++n_buffers) { + struct v4l2_buffer buf; - CLEAR(buf); + CLEAR(buf); - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_MMAP; - buf.index = n_buffers; + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = n_buffers; - if (-1 == xioctl(VIDIOC_QUERYBUF, &buf)) - errno_exit("VIDIOC_QUERYBUF"); + if (-1 == xioctl(VIDIOC_QUERYBUF, &buf)) + throw_errno_exception("VIDIOC_QUERYBUF"); - _buffers[n_buffers].length = buf.length; - _buffers[n_buffers].start = - mmap(NULL /* start anywhere */, - buf.length, - PROT_READ | PROT_WRITE /* required */, - MAP_SHARED /* recommended */, - _fileDescriptor, buf.m.offset); + _buffers[n_buffers].length = buf.length; + _buffers[n_buffers].start = + mmap(NULL /* start anywhere */, + buf.length, + PROT_READ | PROT_WRITE /* required */, + MAP_SHARED /* recommended */, + _fileDescriptor, buf.m.offset); - if (MAP_FAILED == _buffers[n_buffers].start) - errno_exit("mmap"); - } + if (MAP_FAILED == _buffers[n_buffers].start) + throw_errno_exception("mmap"); + } } void V4L2Grabber::init_userp(unsigned int buffer_size) { - struct v4l2_requestbuffers req; + struct v4l2_requestbuffers req; - CLEAR(req); + CLEAR(req); - req.count = 4; - req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - req.memory = V4L2_MEMORY_USERPTR; + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_USERPTR; - if (-1 == xioctl(VIDIOC_REQBUFS, &req)) { - if (EINVAL == errno) - { - fprintf(stderr, "%s does not support user pointer i/o\n", _deviceName.c_str()); - exit(EXIT_FAILURE); - } else { - errno_exit("VIDIOC_REQBUFS"); - } + if (-1 == xioctl(VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) + { + std::ostringstream oss; + oss << "'" << _deviceName << "' does not support user pointer"; + throw_exception(oss.str()); + } else { + throw_errno_exception("VIDIOC_REQBUFS"); } + } - _buffers.resize(4); + _buffers.resize(4); - for (size_t n_buffers = 0; n_buffers < 4; ++n_buffers) { - _buffers[n_buffers].length = buffer_size; - _buffers[n_buffers].start = malloc(buffer_size); + for (size_t n_buffers = 0; n_buffers < 4; ++n_buffers) { + _buffers[n_buffers].length = buffer_size; + _buffers[n_buffers].start = malloc(buffer_size); - if (!_buffers[n_buffers].start) { - fprintf(stderr, "Out of memory\n"); - exit(EXIT_FAILURE); - } + if (!_buffers[n_buffers].start) { + throw_exception("Out of memory"); } + } } void V4L2Grabber::init_device(VideoStandard videoStandard, int input) { - struct v4l2_capability cap; - if (-1 == xioctl(VIDIOC_QUERYCAP, &cap)) - { - if (EINVAL == errno) { - fprintf(stderr, "%s is no V4L2 device\n", _deviceName.c_str()); - exit(EXIT_FAILURE); - } else { - errno_exit("VIDIOC_QUERYCAP"); - } - } - - if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) - { - fprintf(stderr, "%s is no video capture device\n", _deviceName.c_str()); - exit(EXIT_FAILURE); - } - - switch (_ioMethod) { - case IO_METHOD_READ: - if (!(cap.capabilities & V4L2_CAP_READWRITE)) - { - fprintf(stderr, "%s does not support read i/o\n", _deviceName.c_str()); - exit(EXIT_FAILURE); - } - break; - - case IO_METHOD_MMAP: - case IO_METHOD_USERPTR: - if (!(cap.capabilities & V4L2_CAP_STREAMING)) - { - fprintf(stderr, "%s does not support streaming i/o\n", _deviceName.c_str()); - exit(EXIT_FAILURE); - } - break; - } - - - /* Select video input, video standard and tune here. */ - - struct v4l2_cropcap cropcap; - CLEAR(cropcap); - - cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - - if (0 == xioctl(VIDIOC_CROPCAP, &cropcap)) { - struct v4l2_crop crop; - crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - crop.c = cropcap.defrect; /* reset to default */ - - if (-1 == xioctl(VIDIOC_S_CROP, &crop)) { - switch (errno) { - case EINVAL: - /* Cropping not supported. */ - break; - default: - /* Errors ignored. */ - break; - } - } + struct v4l2_capability cap; + if (-1 == xioctl(VIDIOC_QUERYCAP, &cap)) + { + if (EINVAL == errno) { + std::ostringstream oss; + oss << "'" << _deviceName << "' is no V4L2 device"; + throw_exception(oss.str()); } else { + throw_errno_exception("VIDIOC_QUERYCAP"); + } + } + + if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) + { + std::ostringstream oss; + oss << "'" << _deviceName << "' is no video capture device"; + throw_exception(oss.str()); + } + + switch (_ioMethod) { + case IO_METHOD_READ: + if (!(cap.capabilities & V4L2_CAP_READWRITE)) + { + std::ostringstream oss; + oss << "'" << _deviceName << "' does not support read i/o"; + throw_exception(oss.str()); + } + break; + + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + if (!(cap.capabilities & V4L2_CAP_STREAMING)) + { + std::ostringstream oss; + oss << "'" << _deviceName << "' does not support streaming i/o"; + throw_exception(oss.str()); + } + break; + } + + + /* Select video input, video standard and tune here. */ + + struct v4l2_cropcap cropcap; + CLEAR(cropcap); + + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (0 == xioctl(VIDIOC_CROPCAP, &cropcap)) { + struct v4l2_crop crop; + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop.c = cropcap.defrect; /* reset to default */ + + if (-1 == xioctl(VIDIOC_S_CROP, &crop)) { + switch (errno) { + case EINVAL: + /* Cropping not supported. */ + break; + default: /* Errors ignored. */ + break; + } } + } else { + /* Errors ignored. */ + } - // set input if needed - if (input >= 0) + // set input if needed + if (input >= 0) + { + if (-1 == xioctl(VIDIOC_S_INPUT, &input)) { - if (-1 == xioctl(VIDIOC_S_INPUT, &input)) - { - errno_exit("VIDIOC_S_INPUT"); - } + throw_errno_exception("VIDIOC_S_INPUT"); } + } - // set the video standard if needed - switch (videoStandard) + // set the video standard if needed + switch (videoStandard) + { + case PAL: + { + v4l2_std_id std_id = V4L2_STD_PAL; + if (-1 == xioctl(VIDIOC_S_STD, &std_id)) { - case PAL: - { - v4l2_std_id std_id = V4L2_STD_PAL; - if (-1 == xioctl(VIDIOC_S_STD, &std_id)) - { - errno_exit("VIDIOC_S_STD"); - } - } - break; - case NTSC: - { - v4l2_std_id std_id = V4L2_STD_NTSC; - if (-1 == xioctl(VIDIOC_S_STD, &std_id)) - { - errno_exit("VIDIOC_S_STD"); - } - } - break; - case NO_CHANGE: - default: - // No change to device settings - break; + throw_errno_exception("VIDIOC_S_STD"); + } + } + break; + case NTSC: + { + v4l2_std_id std_id = V4L2_STD_NTSC; + if (-1 == xioctl(VIDIOC_S_STD, &std_id)) + { + throw_errno_exception("VIDIOC_S_STD"); + } + } + break; + case NO_CHANGE: + default: + // No change to device settings + break; + } + + + // get the current settings + struct v4l2_format fmt; + CLEAR(fmt); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) + { + throw_errno_exception("VIDIOC_G_FMT"); + } + + // check pixel format + if (fmt.fmt.pix.pixelformat != V4L2_PIX_FMT_UYVY) + { + throw_exception("Only pixel format UYVY is supported"); + } + + if (_width > 0 || _height > 0) + { + if (_width > 0) + { + fmt.fmt.pix.width = _width; } + if (fmt.fmt.pix.height > 0) + { + fmt.fmt.pix.height = _height; + } - // get the current settings - struct v4l2_format fmt; - CLEAR(fmt); - fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + // set the settings + if (-1 == xioctl(VIDIOC_S_FMT, &fmt)) + { + throw_errno_exception("VIDIOC_S_FMT"); + } + + // get the format settings again + // (the size may not have been accepted without an error) if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) { - errno_exit("VIDIOC_G_FMT"); + throw_errno_exception("VIDIOC_G_FMT"); } + } - // check pixel format - if (fmt.fmt.pix.pixelformat != V4L2_PIX_FMT_UYVY) - { - exit(EXIT_FAILURE); - } + // store width & height + _width = fmt.fmt.pix.width; + _height = fmt.fmt.pix.height; - if (_width > 0 || _height > 0) - { - if (_width > 0) - { - fmt.fmt.pix.width = _width; - } + // print the eventually used width and height + std::cout << "V4L2 width=" << _width << " height=" << _height << std::endl; - if (fmt.fmt.pix.height > 0) - { - fmt.fmt.pix.height = _height; - } + switch (_ioMethod) { + case IO_METHOD_READ: + init_read(fmt.fmt.pix.sizeimage); + break; - // set the settings - if (-1 == xioctl(VIDIOC_S_FMT, &fmt)) - { - errno_exit("VIDIOC_S_FMT"); - } + case IO_METHOD_MMAP: + init_mmap(); + break; - // get the format settings again - // (the size may not have been accepted without an error) - if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) - { - errno_exit("VIDIOC_G_FMT"); - } - } - - // store width & height - _width = fmt.fmt.pix.width; - _height = fmt.fmt.pix.height; - std::cout << "V4L2 width=" << _width << " height=" << _height << std::endl; - - switch (_ioMethod) { - case IO_METHOD_READ: - init_read(fmt.fmt.pix.sizeimage); - break; - - case IO_METHOD_MMAP: - init_mmap(); - break; - - case IO_METHOD_USERPTR: - init_userp(fmt.fmt.pix.sizeimage); - break; - } + case IO_METHOD_USERPTR: + init_userp(fmt.fmt.pix.sizeimage); + break; + } } void V4L2Grabber::uninit_device() { - switch (_ioMethod) { - case IO_METHOD_READ: - free(_buffers[0].start); - break; + switch (_ioMethod) { + case IO_METHOD_READ: + free(_buffers[0].start); + break; - case IO_METHOD_MMAP: - for (size_t i = 0; i < _buffers.size(); ++i) - if (-1 == munmap(_buffers[i].start, _buffers[i].length)) - errno_exit("munmap"); - break; + case IO_METHOD_MMAP: + for (size_t i = 0; i < _buffers.size(); ++i) + if (-1 == munmap(_buffers[i].start, _buffers[i].length)) + throw_errno_exception("munmap"); + break; - case IO_METHOD_USERPTR: - for (size_t i = 0; i < _buffers.size(); ++i) - free(_buffers[i].start); - break; - } + case IO_METHOD_USERPTR: + for (size_t i = 0; i < _buffers.size(); ++i) + free(_buffers[i].start); + break; + } - _buffers.resize(0); + _buffers.resize(0); } void V4L2Grabber::start_capturing() @@ -442,39 +453,39 @@ void V4L2Grabber::start_capturing() case IO_METHOD_MMAP: { for (size_t i = 0; i < _buffers.size(); ++i) { - struct v4l2_buffer buf; + struct v4l2_buffer buf; - CLEAR(buf); - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_MMAP; - buf.index = i; + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; - if (-1 == xioctl(VIDIOC_QBUF, &buf)) - errno_exit("VIDIOC_QBUF"); + if (-1 == xioctl(VIDIOC_QBUF, &buf)) + throw_errno_exception("VIDIOC_QBUF"); } v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == xioctl(VIDIOC_STREAMON, &type)) - errno_exit("VIDIOC_STREAMON"); + throw_errno_exception("VIDIOC_STREAMON"); break; } case IO_METHOD_USERPTR: { for (size_t i = 0; i < _buffers.size(); ++i) { - struct v4l2_buffer buf; + struct v4l2_buffer buf; - CLEAR(buf); - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_USERPTR; - buf.index = i; - buf.m.userptr = (unsigned long)_buffers[i].start; - buf.length = _buffers[i].length; + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + buf.index = i; + buf.m.userptr = (unsigned long)_buffers[i].start; + buf.length = _buffers[i].length; - if (-1 == xioctl(VIDIOC_QBUF, &buf)) - errno_exit("VIDIOC_QBUF"); + if (-1 == xioctl(VIDIOC_QBUF, &buf)) + throw_errno_exception("VIDIOC_QBUF"); } v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == xioctl(VIDIOC_STREAMON, &type)) - errno_exit("VIDIOC_STREAMON"); + throw_errno_exception("VIDIOC_STREAMON"); break; } } @@ -486,15 +497,15 @@ void V4L2Grabber::stop_capturing() switch (_ioMethod) { case IO_METHOD_READ: - /* Nothing to do. */ - break; + /* Nothing to do. */ + break; case IO_METHOD_MMAP: case IO_METHOD_USERPTR: - type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (-1 == xioctl(VIDIOC_STREAMOFF, &type)) - errno_exit("VIDIOC_STREAMOFF"); - break; + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(VIDIOC_STREAMOFF, &type)) + throw_errno_exception("VIDIOC_STREAMOFF"); + break; } } @@ -506,99 +517,99 @@ int V4L2Grabber::read_frame() switch (_ioMethod) { case IO_METHOD_READ: - int size; - if ((size = read(_fileDescriptor, _buffers[0].start, _buffers[0].length)) == -1) + int size; + if ((size = read(_fileDescriptor, _buffers[0].start, _buffers[0].length)) == -1) + { + switch (errno) { - switch (errno) - { - case EAGAIN: - return 0; + case EAGAIN: + return 0; - case EIO: - /* Could ignore EIO, see spec. */ + case EIO: + /* Could ignore EIO, see spec. */ - /* fall through */ + /* fall through */ - default: - errno_exit("read"); - } + default: + throw_errno_exception("read"); } + } - rc = process_image(_buffers[0].start, size); - break; + rc = process_image(_buffers[0].start, size); + break; case IO_METHOD_MMAP: - CLEAR(buf); + CLEAR(buf); - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_MMAP; + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; - if (-1 == xioctl(VIDIOC_DQBUF, &buf)) + if (-1 == xioctl(VIDIOC_DQBUF, &buf)) + { + switch (errno) { - switch (errno) - { - case EAGAIN: - return 0; + case EAGAIN: + return 0; - case EIO: - /* Could ignore EIO, see spec. */ + case EIO: + /* Could ignore EIO, see spec. */ - /* fall through */ + /* fall through */ - default: - errno_exit("VIDIOC_DQBUF"); - } + default: + throw_errno_exception("VIDIOC_DQBUF"); } + } - assert(buf.index < _buffers.size()); + assert(buf.index < _buffers.size()); - rc = process_image(_buffers[buf.index].start, buf.bytesused); + rc = process_image(_buffers[buf.index].start, buf.bytesused); - if (-1 == xioctl(VIDIOC_QBUF, &buf)) - { - errno_exit("VIDIOC_QBUF"); - } + if (-1 == xioctl(VIDIOC_QBUF, &buf)) + { + throw_errno_exception("VIDIOC_QBUF"); + } - break; + break; case IO_METHOD_USERPTR: - CLEAR(buf); + CLEAR(buf); - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_USERPTR; + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; - if (-1 == xioctl(VIDIOC_DQBUF, &buf)) + if (-1 == xioctl(VIDIOC_DQBUF, &buf)) + { + switch (errno) { - switch (errno) - { - case EAGAIN: - return 0; + case EAGAIN: + return 0; - case EIO: - /* Could ignore EIO, see spec. */ + case EIO: + /* Could ignore EIO, see spec. */ - /* fall through */ + /* fall through */ - default: - errno_exit("VIDIOC_DQBUF"); - } + default: + throw_errno_exception("VIDIOC_DQBUF"); } + } - for (size_t i = 0; i < _buffers.size(); ++i) + for (size_t i = 0; i < _buffers.size(); ++i) + { + if (buf.m.userptr == (unsigned long)_buffers[i].start && buf.length == _buffers[i].length) { - if (buf.m.userptr == (unsigned long)_buffers[i].start && buf.length == _buffers[i].length) - { - break; - } + break; } + } - rc = process_image((void *)buf.m.userptr, buf.bytesused); + rc = process_image((void *)buf.m.userptr, buf.bytesused); - if (-1 == xioctl(VIDIOC_QBUF, &buf)) - { - errno_exit("VIDIOC_QBUF"); - } - break; + if (-1 == xioctl(VIDIOC_QBUF, &buf)) + { + throw_errno_exception("VIDIOC_QBUF"); + } + break; } return rc ? 1 : 0; @@ -665,8 +676,16 @@ int V4L2Grabber::xioctl(int request, void *arg) return r; } -void V4L2Grabber::errno_exit(const char *s) +void V4L2Grabber::throw_exception(const std::string & error) { - fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno)); - exit(EXIT_FAILURE); + std::ostringstream oss; + oss << error << " error"; + throw std::runtime_error(oss.str()); +} + +void V4L2Grabber::throw_errno_exception(const std::string & error) +{ + std::ostringstream oss; + oss << error << " error " << errno << ", " << strerror(errno); + throw std::runtime_error(oss.str()); } diff --git a/src/hyperion-v4l2/V4L2Grabber.h b/src/hyperion-v4l2/V4L2Grabber.h index 2588e7ce..a8033dad 100644 --- a/src/hyperion-v4l2/V4L2Grabber.h +++ b/src/hyperion-v4l2/V4L2Grabber.h @@ -59,7 +59,9 @@ private: int xioctl(int request, void *arg); - void errno_exit(const char *s); + void throw_exception(const std::string &error); + + void throw_errno_exception(const std::string &error); private: enum io_method { From c96be1cf388173358e42dac073fe9ad3b784500d Mon Sep 17 00:00:00 2001 From: johan Date: Sun, 26 Jan 2014 13:00:23 +0100 Subject: [PATCH 13/21] Added option to stop receiving the proto replies Former-commit-id: b15c31b8aa500b8d7253d0c2b2368eab6a11ed50 --- src/hyperion-v4l2/ProtoConnection.cpp | 32 ++++++++++++++++----------- src/hyperion-v4l2/ProtoConnection.h | 6 +++++ src/hyperion-v4l2/hyperion-v4l2.cpp | 2 ++ 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/hyperion-v4l2/ProtoConnection.cpp b/src/hyperion-v4l2/ProtoConnection.cpp index 07544a8e..e78b2285 100644 --- a/src/hyperion-v4l2/ProtoConnection.cpp +++ b/src/hyperion-v4l2/ProtoConnection.cpp @@ -8,7 +8,8 @@ #include "ProtoConnection.h" ProtoConnection::ProtoConnection(const std::string & a) : - _socket() + _socket(), + _skipReply(false) { QString address(a.c_str()); QStringList parts = address.split(":"); @@ -38,6 +39,11 @@ ProtoConnection::~ProtoConnection() _socket.close(); } +void ProtoConnection::setSkipReply(bool skip) +{ + _skipReply = skip; +} + void ProtoConnection::setColor(const ColorRgb & color, int priority, int duration) { proto::HyperionRequest request; @@ -119,13 +125,11 @@ proto::HyperionReply ProtoConnection::sendMessage(const proto::HyperionRequest & throw std::runtime_error("Error while writing data to host"); } - /* // read reply data QByteArray serializedReply; length = -1; - while (serializedReply.size() != length) + while (length < 0 && serializedReply.size() < length+4) { - std::cout << length << std::endl; // receive reply if (!_socket.waitForReadyRead()) { @@ -136,20 +140,22 @@ proto::HyperionReply ProtoConnection::sendMessage(const proto::HyperionRequest & if (length < 0 && serializedReply.size() >= 4) { - std::cout << (int) serializedReply[3] << std::endl; - std::cout << (int) serializedReply[2] << std::endl; - std::cout << (int) serializedReply[1] << std::endl; - std::cout << (int) serializedReply[0] << std::endl; - - length = (uint8_t(serializedReply[0]) << 24) | (uint8_t(serializedReply[1]) << 16) | (uint8_t(serializedReply[2]) << 8) | uint8_t(serializedReply[3]) ; + // read the message size + length = + ((serializedReply[0]<<24) & 0xFF000000) | + ((serializedReply[1]<<16) & 0x00FF0000) | + ((serializedReply[2]<< 8) & 0x0000FF00) | + ((serializedReply[3] ) & 0x000000FF); } } - std::cout << length << std::endl; -*/ // parse reply data proto::HyperionReply reply; -// reply.ParseFromArray(serializedReply.constData()+4, length); + reply.ParseFromArray(serializedReply.constData()+4, length); + + // remove data from receive buffer + serializedReply = serializedReply.mid(length+4); + return reply; } diff --git a/src/hyperion-v4l2/ProtoConnection.h b/src/hyperion-v4l2/ProtoConnection.h index b20faf34..f8a9387e 100644 --- a/src/hyperion-v4l2/ProtoConnection.h +++ b/src/hyperion-v4l2/ProtoConnection.h @@ -34,6 +34,9 @@ public: /// ~ProtoConnection(); + /// Do not read reply messages from Hyperion if set to true + void setSkipReply(bool skip); + /// /// Set all leds to the specified color /// @@ -86,4 +89,7 @@ private: private: /// The TCP-Socket with the connection to the server QTcpSocket _socket; + + /// Skip receiving reply messages from Hyperion if set + bool _skipReply; }; diff --git a/src/hyperion-v4l2/hyperion-v4l2.cpp b/src/hyperion-v4l2/hyperion-v4l2.cpp index 454832be..0df6ca95 100644 --- a/src/hyperion-v4l2/hyperion-v4l2.cpp +++ b/src/hyperion-v4l2/hyperion-v4l2.cpp @@ -81,6 +81,7 @@ int main(int argc, char** argv) SwitchParameter<> & argScreenshot = parameters.add> (0x0, "screenshot", "Take a single screenshot, save it to file and quit"); StringParameter & argAddress = parameters.add ('a', "address", "Set the address of the hyperion server [default: 127.0.0.1:19445]"); IntParameter & argPriority = parameters.add ('p', "priority", "Use the provided priority channel (the lower the number, the higher the priority) [default: 800]"); + SwitchParameter<> & argSkipReply = parameters.add> (0x0, "skip-reply", "Do not receive and check reply messages from Hyperion"); SwitchParameter<> & argHelp = parameters.add> ('h', "help", "Show this help message and exit"); // set defaults @@ -126,6 +127,7 @@ int main(int argc, char** argv) else { ProtoConnection connection(argAddress.getValue()); + connection.setSkipReply(argSkipReply.isSet()); grabber.setCallback(&sendImage, &connection); grabber.capture(); From 8cf39a9f6f1642772e66ce9f13a016e2969b44a7 Mon Sep 17 00:00:00 2001 From: johan Date: Sun, 26 Jan 2014 13:16:58 +0100 Subject: [PATCH 14/21] Added logic to reconnect after connection failure; Implemented option to stop receiving the proto replies; Former-commit-id: d8dfc3f36e81d727c4b7d449888f781b7b57c99e --- src/hyperion-v4l2/ProtoConnection.cpp | 113 ++++++++++++++------------ src/hyperion-v4l2/ProtoConnection.h | 13 ++- 2 files changed, 71 insertions(+), 55 deletions(-) diff --git a/src/hyperion-v4l2/ProtoConnection.cpp b/src/hyperion-v4l2/ProtoConnection.cpp index e78b2285..9f59eddd 100644 --- a/src/hyperion-v4l2/ProtoConnection.cpp +++ b/src/hyperion-v4l2/ProtoConnection.cpp @@ -17,21 +17,18 @@ ProtoConnection::ProtoConnection(const std::string & a) : { throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(address).toStdString()); } + _host = parts[0]; bool ok; - uint16_t port = parts[1].toUShort(&ok); + _port = parts[1].toUShort(&ok); if (!ok) { throw std::runtime_error(QString("Wrong address: Unable to parse the port number (%1)").arg(parts[1]).toStdString()); } - _socket.connectToHost(parts[0], port); - if (!_socket.waitForConnected()) - { - throw std::runtime_error("Unable to connect to host"); - } - - std::cout << "Connected to " << a << std::endl; + // try to connect to host + std::cout << "Connecting to Hyperion: " << _host.toStdString() << ":" << _port << std::endl; + connectToHost(); } ProtoConnection::~ProtoConnection() @@ -54,10 +51,7 @@ void ProtoConnection::setColor(const ColorRgb & color, int priority, int duratio colorRequest->set_duration(duration); // send command message - proto::HyperionReply reply = sendMessage(request); - - // parse reply message - parseReply(reply); + sendMessage(request); } void ProtoConnection::setImage(const Image &image, int priority, int duration) @@ -72,10 +66,7 @@ void ProtoConnection::setImage(const Image &image, int priority, int d imageRequest->set_duration(duration); // send command message - proto::HyperionReply reply = sendMessage(request); - - // parse reply message -// parseReply(reply); + sendMessage(request); } void ProtoConnection::clear(int priority) @@ -86,10 +77,7 @@ void ProtoConnection::clear(int priority) clearRequest->set_priority(priority); // send command message - proto::HyperionReply reply = sendMessage(request); - - // parse reply message - parseReply(reply); + sendMessage(request); } void ProtoConnection::clearAll() @@ -98,14 +86,32 @@ void ProtoConnection::clearAll() request.set_command(proto::HyperionRequest::CLEARALL); // send command message - proto::HyperionReply reply = sendMessage(request); - - // parse reply message - parseReply(reply); + sendMessage(request); } -proto::HyperionReply ProtoConnection::sendMessage(const proto::HyperionRequest &message) +void ProtoConnection::connectToHost() { + _socket.connectToHost(_host, _port); + if (_socket.waitForConnected()) { + std::cout << "Connected to Hyperion host" << std::endl; + } +} + +void ProtoConnection::sendMessage(const proto::HyperionRequest &message) +{ + if (_socket.state() == QAbstractSocket::UnconnectedState) + { + std::cout << "Currently disconnected: trying to connect to host" << std::endl; + connectToHost(); + } + + if (_socket.state() != QAbstractSocket::ConnectedState) + { + return; + } + + // We only get here if we are connected + // serialize message (FastWriter already appends a newline) std::string serializedMessage = message.SerializeAsString(); @@ -122,41 +128,44 @@ proto::HyperionReply ProtoConnection::sendMessage(const proto::HyperionRequest & count += _socket.write(reinterpret_cast(serializedMessage.data()), length); if (!_socket.waitForBytesWritten()) { - throw std::runtime_error("Error while writing data to host"); + std::cerr << "Error while writing data to host" << std::endl; + return; } - // read reply data - QByteArray serializedReply; - length = -1; - while (length < 0 && serializedReply.size() < length+4) + if (!_skipReply) { - // receive reply - if (!_socket.waitForReadyRead()) + // read reply data + QByteArray serializedReply; + length = -1; + while (length < 0 && serializedReply.size() < length+4) { - throw std::runtime_error("Error while reading data from host"); + // receive reply + if (!_socket.waitForReadyRead()) + { + std::cerr << "Error while reading data from host" << std::endl; + return; + } + + serializedReply += _socket.readAll(); + + if (length < 0 && serializedReply.size() >= 4) + { + // read the message size + length = + ((serializedReply[0]<<24) & 0xFF000000) | + ((serializedReply[1]<<16) & 0x00FF0000) | + ((serializedReply[2]<< 8) & 0x0000FF00) | + ((serializedReply[3] ) & 0x000000FF); + } } - serializedReply += _socket.readAll(); + // parse reply data + proto::HyperionReply reply; + reply.ParseFromArray(serializedReply.constData()+4, length); - if (length < 0 && serializedReply.size() >= 4) - { - // read the message size - length = - ((serializedReply[0]<<24) & 0xFF000000) | - ((serializedReply[1]<<16) & 0x00FF0000) | - ((serializedReply[2]<< 8) & 0x0000FF00) | - ((serializedReply[3] ) & 0x000000FF); - } + // parse reply message + parseReply(reply); } - - // parse reply data - proto::HyperionReply reply; - reply.ParseFromArray(serializedReply.constData()+4, length); - - // remove data from receive buffer - serializedReply = serializedReply.mid(length+4); - - return reply; } bool ProtoConnection::parseReply(const proto::HyperionReply &reply) diff --git a/src/hyperion-v4l2/ProtoConnection.h b/src/hyperion-v4l2/ProtoConnection.h index f8a9387e..a220c794 100644 --- a/src/hyperion-v4l2/ProtoConnection.h +++ b/src/hyperion-v4l2/ProtoConnection.h @@ -68,14 +68,15 @@ public: void clearAll(); private: + /// Try to connect to the Hyperion host + void connectToHost(); + /// /// Send a command message and receive its reply /// /// @param message The message to send /// - /// @return The returned reply - /// - proto::HyperionReply sendMessage(const proto::HyperionRequest & message); + void sendMessage(const proto::HyperionRequest & message); /// /// Parse a reply message @@ -90,6 +91,12 @@ private: /// The TCP-Socket with the connection to the server QTcpSocket _socket; + /// Host address + QString _host; + + /// Host port + uint16_t _port; + /// Skip receiving reply messages from Hyperion if set bool _skipReply; }; From f5317bc2d969ce8ea3317279d56b2a5640c7aafd Mon Sep 17 00:00:00 2001 From: johan Date: Sun, 26 Jan 2014 14:23:08 +0100 Subject: [PATCH 15/21] Moved black border detection code to seperate library Former-commit-id: 590029949b79689ea05409149677e51b748cb64f --- .../BlackBorderDetector.h | 0 .../BlackBorderProcessor.h | 0 include/hyperion/ImageProcessor.h | 4 +++- libsrc/CMakeLists.txt | 1 + .../BlackBorderDetector.cpp | 4 ++-- .../BlackBorderProcessor.cpp | 4 ++-- libsrc/blackborder/CMakeLists.txt | 23 +++++++++++++++++++ libsrc/hyperion/CMakeLists.txt | 6 +---- libsrc/hyperion/ImageProcessor.cpp | 4 +++- test/TestBlackBorderDetector.cpp | 4 +++- test/TestBlackBorderProcessor.cpp | 4 ++-- 11 files changed, 40 insertions(+), 14 deletions(-) rename include/{hyperion => blackborder}/BlackBorderDetector.h (100%) rename include/{hyperion => blackborder}/BlackBorderProcessor.h (100%) rename libsrc/{hyperion => blackborder}/BlackBorderDetector.cpp (69%) rename libsrc/{hyperion => blackborder}/BlackBorderProcessor.cpp (96%) create mode 100644 libsrc/blackborder/CMakeLists.txt diff --git a/include/hyperion/BlackBorderDetector.h b/include/blackborder/BlackBorderDetector.h similarity index 100% rename from include/hyperion/BlackBorderDetector.h rename to include/blackborder/BlackBorderDetector.h diff --git a/include/hyperion/BlackBorderProcessor.h b/include/blackborder/BlackBorderProcessor.h similarity index 100% rename from include/hyperion/BlackBorderProcessor.h rename to include/blackborder/BlackBorderProcessor.h diff --git a/include/hyperion/ImageProcessor.h b/include/hyperion/ImageProcessor.h index 990b6593..deb40df1 100644 --- a/include/hyperion/ImageProcessor.h +++ b/include/hyperion/ImageProcessor.h @@ -8,7 +8,9 @@ #include #include #include -#include + +// Black border includes +#include /// /// The ImageProcessor translates an RGB-image to RGB-values for the leds. The processing is diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index 8eab4b33..8db13505 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -4,6 +4,7 @@ SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include) SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc) add_subdirectory(hyperion) +add_subdirectory(blackborder) add_subdirectory(jsonserver) add_subdirectory(protoserver) add_subdirectory(boblightserver) diff --git a/libsrc/hyperion/BlackBorderDetector.cpp b/libsrc/blackborder/BlackBorderDetector.cpp similarity index 69% rename from libsrc/hyperion/BlackBorderDetector.cpp rename to libsrc/blackborder/BlackBorderDetector.cpp index ed97bebe..6164f920 100644 --- a/libsrc/hyperion/BlackBorderDetector.cpp +++ b/libsrc/blackborder/BlackBorderDetector.cpp @@ -1,6 +1,6 @@ -// Local-Hyperion includes -#include +// BlackBorders includes +#include using namespace hyperion; diff --git a/libsrc/hyperion/BlackBorderProcessor.cpp b/libsrc/blackborder/BlackBorderProcessor.cpp similarity index 96% rename from libsrc/hyperion/BlackBorderProcessor.cpp rename to libsrc/blackborder/BlackBorderProcessor.cpp index efc97724..ee78efb9 100644 --- a/libsrc/hyperion/BlackBorderProcessor.cpp +++ b/libsrc/blackborder/BlackBorderProcessor.cpp @@ -1,6 +1,6 @@ -// Local-Hyperion includes -#include +// Blackborder includes +#include using namespace hyperion; diff --git a/libsrc/blackborder/CMakeLists.txt b/libsrc/blackborder/CMakeLists.txt new file mode 100644 index 00000000..0d693c89 --- /dev/null +++ b/libsrc/blackborder/CMakeLists.txt @@ -0,0 +1,23 @@ + +# Define the current source locations +SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/blackborder) +SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/blackborder) + +SET(Blackborder_HEADERS + ${CURRENT_HEADER_DIR}/BlackBorderDetector.h + ${CURRENT_HEADER_DIR}/BlackBorderProcessor.h +) + +SET(Blackborder_SOURCES + ${CURRENT_SOURCE_DIR}/BlackBorderDetector.cpp + ${CURRENT_SOURCE_DIR}/BlackBorderProcessor.cpp +) + +add_library(blackborder + ${Blackborder_HEADERS} + ${Blackborder_SOURCES} +) + +target_link_libraries(blackborder + hyperion-utils +) diff --git a/libsrc/hyperion/CMakeLists.txt b/libsrc/hyperion/CMakeLists.txt index 738af8c0..8f6a227d 100644 --- a/libsrc/hyperion/CMakeLists.txt +++ b/libsrc/hyperion/CMakeLists.txt @@ -17,9 +17,6 @@ SET(Hyperion_HEADERS ${CURRENT_HEADER_DIR}/LedString.h ${CURRENT_HEADER_DIR}/PriorityMuxer.h - ${CURRENT_HEADER_DIR}/BlackBorderDetector.h - ${CURRENT_HEADER_DIR}/BlackBorderProcessor.h - ${CURRENT_SOURCE_DIR}/MultiColorTransform.h ) @@ -30,8 +27,6 @@ SET(Hyperion_SOURCES ${CURRENT_SOURCE_DIR}/LedString.cpp ${CURRENT_SOURCE_DIR}/PriorityMuxer.cpp - ${CURRENT_SOURCE_DIR}/BlackBorderDetector.cpp - ${CURRENT_SOURCE_DIR}/BlackBorderProcessor.cpp ${CURRENT_SOURCE_DIR}/ImageToLedsMap.cpp ${CURRENT_SOURCE_DIR}/MultiColorTransform.cpp ${CURRENT_SOURCE_DIR}/LinearColorSmoothing.cpp @@ -54,6 +49,7 @@ add_library(hyperion ) target_link_libraries(hyperion + blackborder hyperion-utils leddevice effectengine diff --git a/libsrc/hyperion/ImageProcessor.cpp b/libsrc/hyperion/ImageProcessor.cpp index cd39dd1e..66b02e74 100644 --- a/libsrc/hyperion/ImageProcessor.cpp +++ b/libsrc/hyperion/ImageProcessor.cpp @@ -2,7 +2,9 @@ // Hyperion includes #include #include -#include + +// Blacborder includes +#include using namespace hyperion; diff --git a/test/TestBlackBorderDetector.cpp b/test/TestBlackBorderDetector.cpp index 1d91079f..018cebf0 100644 --- a/test/TestBlackBorderDetector.cpp +++ b/test/TestBlackBorderDetector.cpp @@ -3,9 +3,11 @@ #include // Hyperion includes -#include #include +// Blackborder includes +#include + using namespace hyperion; ColorRgb randomColor() diff --git a/test/TestBlackBorderProcessor.cpp b/test/TestBlackBorderProcessor.cpp index e0eef0d2..4aceaa81 100644 --- a/test/TestBlackBorderProcessor.cpp +++ b/test/TestBlackBorderProcessor.cpp @@ -8,8 +8,8 @@ #include #include -// Local-Hyperion includes -#include "hyperion/BlackBorderProcessor.h" +// Blackborder includes +#include "blackborder/BlackBorderProcessor.h" using namespace hyperion; From 305a2e856a152afa7729319f32ed353304a485cb Mon Sep 17 00:00:00 2001 From: johan Date: Sun, 26 Jan 2014 15:51:02 +0100 Subject: [PATCH 16/21] Added signal detection option Former-commit-id: 1bd70d02814d6238c488db765d9d25af655c1872 --- dependencies/build/getoptPlusPlus/getoptpp.cc | 2 +- src/hyperion-v4l2/CMakeLists.txt | 4 + src/hyperion-v4l2/ImageHandler.cpp | 41 ++++++++++ src/hyperion-v4l2/ImageHandler.h | 34 ++++++++ src/hyperion-v4l2/VideoStandardParameter.h | 36 +++++++++ src/hyperion-v4l2/hyperion-v4l2.cpp | 81 ++++++------------- 6 files changed, 140 insertions(+), 58 deletions(-) create mode 100644 src/hyperion-v4l2/ImageHandler.cpp create mode 100644 src/hyperion-v4l2/ImageHandler.h create mode 100644 src/hyperion-v4l2/VideoStandardParameter.h diff --git a/dependencies/build/getoptPlusPlus/getoptpp.cc b/dependencies/build/getoptPlusPlus/getoptpp.cc index 9c385585..e7b8b420 100644 --- a/dependencies/build/getoptPlusPlus/getoptpp.cc +++ b/dependencies/build/getoptPlusPlus/getoptpp.cc @@ -120,7 +120,7 @@ void OptionsParser::usage() const { for(i = parameters.parameters.begin(); i != parameters.parameters.end(); i++) { - cerr.width(31); + cerr.width(33); cerr << std::left << " " + (*i)->usageLine(); cerr.width(40); diff --git a/src/hyperion-v4l2/CMakeLists.txt b/src/hyperion-v4l2/CMakeLists.txt index 58b62ac1..5e5de616 100644 --- a/src/hyperion-v4l2/CMakeLists.txt +++ b/src/hyperion-v4l2/CMakeLists.txt @@ -17,12 +17,15 @@ include_directories( set(Hyperion_V4L2_HEADERS V4L2Grabber.h ProtoConnection.h + ImageHandler.h + VideoStandardParameter.h ) set(Hyperion_V4L2_SOURCES hyperion-v4l2.cpp V4L2Grabber.cpp ProtoConnection.cpp + ImageHandler.cpp ) set(Hyperion_V4L2_PROTOS @@ -42,6 +45,7 @@ add_executable(hyperion-v4l2 target_link_libraries(hyperion-v4l2 getoptPlusPlus + blackborder hyperion-utils ${PROTOBUF_LIBRARIES} pthread diff --git a/src/hyperion-v4l2/ImageHandler.cpp b/src/hyperion-v4l2/ImageHandler.cpp new file mode 100644 index 00000000..a100fd5d --- /dev/null +++ b/src/hyperion-v4l2/ImageHandler.cpp @@ -0,0 +1,41 @@ +// hyperion-v4l2 includes +#include "ImageHandler.h" + +ImageHandler::ImageHandler(const std::string &address, int priority, double signalThreshold, bool skipProtoReply) : + _priority(priority), + _connection(address), + _signalThreshold(signalThreshold), + _signalProcessor(100, 50, 0, uint8_t(std::min(255, std::max(0, int(255*signalThreshold))))) +{ + _connection.setSkipReply(skipProtoReply); +} + +void ImageHandler::receiveImage(const Image &image) +{ + // check if we should do signal detection + if (_signalThreshold < 0) + { + _connection.setImage(image, _priority, 200); + } + else + { + if (_signalProcessor.process(image)) + { + std::cout << "Signal state = " << (_signalProcessor.getCurrentBorder().unknown ? "off" : "on") << std::endl; + } + + // consider an unknown border as no signal + // send the image to Hyperion if we have a signal + if (!_signalProcessor.getCurrentBorder().unknown) + { + _connection.setImage(image, _priority, 200); + } + } +} + +void ImageHandler::imageCallback(void *arg, const Image &image) +{ + ImageHandler * handler = static_cast(arg); + handler->receiveImage(image); +} + diff --git a/src/hyperion-v4l2/ImageHandler.h b/src/hyperion-v4l2/ImageHandler.h new file mode 100644 index 00000000..e0e0adbf --- /dev/null +++ b/src/hyperion-v4l2/ImageHandler.h @@ -0,0 +1,34 @@ +// blackborder includes +#include + +// hyperion-v4l includes +#include "ProtoConnection.h" + +/// This class handles callbacks from the V4L2 grabber +class ImageHandler +{ +public: + ImageHandler(const std::string & address, int priority, double signalThreshold, bool skipProtoReply); + + /// Handle a single image + /// @param image The image to process + void receiveImage(const Image & image); + + /// static function used to direct callbacks to a ImageHandler object + /// @param arg This should be an ImageHandler instance + /// @param image The image to process + static void imageCallback(void * arg, const Image & image); + +private: + /// Priority for calls to Hyperion + const int _priority; + + /// Hyperion proto connection object + ProtoConnection _connection; + + /// Threshold used for signal detection + double _signalThreshold; + + /// Blackborder detector which is used as a signal detector (unknown border = no signal) + hyperion::BlackBorderProcessor _signalProcessor; +}; diff --git a/src/hyperion-v4l2/VideoStandardParameter.h b/src/hyperion-v4l2/VideoStandardParameter.h new file mode 100644 index 00000000..7d711ccf --- /dev/null +++ b/src/hyperion-v4l2/VideoStandardParameter.h @@ -0,0 +1,36 @@ +// getoptPlusPLus includes +#include + +using namespace vlofgren; + +/// Data parameter for the video standard +typedef vlofgren::PODParameter VideoStandardParameter; + +namespace vlofgren { + /// Translates a string (as passed on the commandline) to a color standard + /// + /// @param[in] s The string (as passed on the commandline) + /// @return The color standard + /// @throws Parameter::ParameterRejected If the string did not result in a video standard + template<> + V4L2Grabber::VideoStandard VideoStandardParameter::validate(const std::string& s) throw (Parameter::ParameterRejected) + { + QString input = QString::fromStdString(s).toLower(); + + if (input == "pal") + { + return V4L2Grabber::PAL; + } + else if (input == "ntsc") + { + return V4L2Grabber::NTSC; + } + else if (input == "no-change") + { + return V4L2Grabber::NO_CHANGE; + } + + throw Parameter::ParameterRejected("Invalid value for video standard. Valid values are: PAL, NTSC, and NO-CHANGE"); + return V4L2Grabber::NO_CHANGE; + } +} diff --git a/src/hyperion-v4l2/hyperion-v4l2.cpp b/src/hyperion-v4l2/hyperion-v4l2.cpp index 0df6ca95..cf82ba35 100644 --- a/src/hyperion-v4l2/hyperion-v4l2.cpp +++ b/src/hyperion-v4l2/hyperion-v4l2.cpp @@ -9,43 +9,17 @@ // getoptPlusPLus includes #include +// blackborder includes +#include + +// hyperion-v4l2 includes #include "V4L2Grabber.h" #include "ProtoConnection.h" +#include "VideoStandardParameter.h" +#include "ImageHandler.h" using namespace vlofgren; -/// Data parameter for the video standard -typedef vlofgren::PODParameter VideoStandardParameter; - -namespace vlofgren { - /// Translates a string (as passed on the commandline) to a color standard - /// - /// @param[in] s The string (as passed on the commandline) - /// @return The color standard - /// @throws Parameter::ParameterRejected If the string did not result in a video standard - template<> - V4L2Grabber::VideoStandard VideoStandardParameter::validate(const std::string& s) throw (Parameter::ParameterRejected) - { - QString input = QString::fromStdString(s).toLower(); - - if (input == "pal") - { - return V4L2Grabber::PAL; - } - else if (input == "ntsc") - { - return V4L2Grabber::NTSC; - } - else if (input == "no-change") - { - return V4L2Grabber::NO_CHANGE; - } - - throw Parameter::ParameterRejected("Invalid value for video standard. Valid values are: PAL, NTSC, and NO-CHANGE"); - return V4L2Grabber::NO_CHANGE; - } -} - // save the image as screenshot void saveScreenshot(void *, const Image & image) { @@ -54,13 +28,6 @@ void saveScreenshot(void *, const Image & image) pngImage.save("screenshot.png"); } -// send the image to Hyperion -void sendImage(void * arg, const Image & image) -{ - ProtoConnection * connection = static_cast(arg); - connection->setImage(image, 50, 200); -} - int main(int argc, char** argv) { try @@ -69,20 +36,21 @@ int main(int argc, char** argv) OptionsParser optionParser("Simple application to send a command to hyperion using the Json interface"); ParameterSet & parameters = optionParser.getParameters(); - StringParameter & argDevice = parameters.add ('d', "device", "The device to use [default=/dev/video0]"); - VideoStandardParameter & argVideoStandard = parameters.add('v', "video-standard", "The used video standard. Valid values are PAL. NYSC, or NO-CHANGE [default=PAL]"); - IntParameter & argInput = parameters.add (0x0, "input", "Input channel (optional)"); - IntParameter & argWidth = parameters.add (0x0, "width", "Try to set the width of the video input (optional)"); - IntParameter & argHeight = parameters.add (0x0, "height", "Try to set the height of the video input (optional)"); - IntParameter & argCropWidth = parameters.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides in the picture before decimation [default=0]"); - IntParameter & argCropHeight = parameters.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom in the picture before decimation [default=0]"); - IntParameter & argSizeDecimation = parameters.add ('s', "size-decimator", "Decimation factor for the output size [default=1]"); - IntParameter & argFrameDecimation = parameters.add ('f', "frame-decimator","Decimation factor for the video frames [default=1]"); - SwitchParameter<> & argScreenshot = parameters.add> (0x0, "screenshot", "Take a single screenshot, save it to file and quit"); - StringParameter & argAddress = parameters.add ('a', "address", "Set the address of the hyperion server [default: 127.0.0.1:19445]"); - IntParameter & argPriority = parameters.add ('p', "priority", "Use the provided priority channel (the lower the number, the higher the priority) [default: 800]"); - SwitchParameter<> & argSkipReply = parameters.add> (0x0, "skip-reply", "Do not receive and check reply messages from Hyperion"); - SwitchParameter<> & argHelp = parameters.add> ('h', "help", "Show this help message and exit"); + StringParameter & argDevice = parameters.add ('d', "device", "The device to use [default=/dev/video0]"); + VideoStandardParameter & argVideoStandard = parameters.add('v', "video-standard", "The used video standard. Valid values are PAL. NYSC, or NO-CHANGE [default=PAL]"); + IntParameter & argInput = parameters.add (0x0, "input", "Input channel (optional)"); + IntParameter & argWidth = parameters.add (0x0, "width", "Try to set the width of the video input (optional)"); + IntParameter & argHeight = parameters.add (0x0, "height", "Try to set the height of the video input (optional)"); + IntParameter & argCropWidth = parameters.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides in the picture before decimation [default=0]"); + IntParameter & argCropHeight = parameters.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom in the picture before decimation [default=0]"); + IntParameter & argSizeDecimation = parameters.add ('s', "size-decimator", "Decimation factor for the output size [default=1]"); + IntParameter & argFrameDecimation = parameters.add ('f', "frame-decimator", "Decimation factor for the video frames [default=1]"); + SwitchParameter<> & argScreenshot = parameters.add> (0x0, "screenshot", "Take a single screenshot, save it to file and quit"); + DoubleParameter & argSignalThreshold = parameters.add ('t', "signal-threshold", "The signal threshold for detecting the presence of a signal. Value should be between 0.0 and 1.0."); + StringParameter & argAddress = parameters.add ('a', "address", "Set the address of the hyperion server [default: 127.0.0.1:19445]"); + IntParameter & argPriority = parameters.add ('p', "priority", "Use the provided priority channel (the lower the number, the higher the priority) [default: 800]"); + SwitchParameter<> & argSkipReply = parameters.add> (0x0, "skip-reply", "Do not receive and check reply messages from Hyperion"); + SwitchParameter<> & argHelp = parameters.add> ('h', "help", "Show this help message and exit"); // set defaults argDevice.setDefault("/dev/video0"); @@ -96,6 +64,7 @@ int main(int argc, char** argv) argFrameDecimation.setDefault(1); argAddress.setDefault("127.0.0.1:19445"); argPriority.setDefault(800); + argSignalThreshold.setDefault(-1); // parse all options optionParser.parse(argc, const_cast(argv)); @@ -126,10 +95,8 @@ int main(int argc, char** argv) } else { - ProtoConnection connection(argAddress.getValue()); - connection.setSkipReply(argSkipReply.isSet()); - - grabber.setCallback(&sendImage, &connection); + ImageHandler handler(argAddress.getValue(), argPriority.getValue(), argSignalThreshold.getValue(), argSkipReply.isSet()); + grabber.setCallback(&ImageHandler::imageCallback, &handler); grabber.capture(); } grabber.stop(); From 6a1ba0bcffc484914b4d662b5e3af7a37ff28f08 Mon Sep 17 00:00:00 2001 From: johan Date: Sun, 26 Jan 2014 16:12:07 +0100 Subject: [PATCH 17/21] New release created Former-commit-id: 32cea464297936ab978575203be70b8ba5371b42 --- bin/copy_binaries_to_deploy.sh | 1 + deploy/hyperion.tar.gz.REMOVED.git-id | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/copy_binaries_to_deploy.sh b/bin/copy_binaries_to_deploy.sh index c2783c0c..5ce8be74 100755 --- a/bin/copy_binaries_to_deploy.sh +++ b/bin/copy_binaries_to_deploy.sh @@ -22,6 +22,7 @@ tar --create --verbose --gzip --absolute-names --show-transformed-names \ --transform "s://:/:g" \ "$builddir/bin/hyperiond" \ "$builddir/bin/hyperion-remote" \ + "$builddir/bin/hyperion-v4l2" \ "$builddir/bin/gpio2spi" \ "$builddir/bin/dispmanx2png" \ "$repodir/effects/"* \ diff --git a/deploy/hyperion.tar.gz.REMOVED.git-id b/deploy/hyperion.tar.gz.REMOVED.git-id index e41e5906..c6e981f4 100644 --- a/deploy/hyperion.tar.gz.REMOVED.git-id +++ b/deploy/hyperion.tar.gz.REMOVED.git-id @@ -1 +1 @@ -2ad39031528c40c14e352cbc84784f48564cc59b \ No newline at end of file +70a3a8fe7858c21afe8593ff4a5f5bb44f5701aa \ No newline at end of file From 6b609fe9d79171ea94b8b5131946feb5399eaf50 Mon Sep 17 00:00:00 2001 From: johan Date: Sun, 26 Jan 2014 16:34:13 +0100 Subject: [PATCH 18/21] Update program description Former-commit-id: 6c6e7972e25c7eb7b3e575273cbeb91f27578f79 --- deploy/hyperion.tar.gz.REMOVED.git-id | 2 +- src/hyperion-v4l2/hyperion-v4l2.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/hyperion.tar.gz.REMOVED.git-id b/deploy/hyperion.tar.gz.REMOVED.git-id index c6e981f4..39782de1 100644 --- a/deploy/hyperion.tar.gz.REMOVED.git-id +++ b/deploy/hyperion.tar.gz.REMOVED.git-id @@ -1 +1 @@ -70a3a8fe7858c21afe8593ff4a5f5bb44f5701aa \ No newline at end of file +174682186110e5b067b5bf9be70f6683c97d4fbf \ No newline at end of file diff --git a/src/hyperion-v4l2/hyperion-v4l2.cpp b/src/hyperion-v4l2/hyperion-v4l2.cpp index cf82ba35..51314f5f 100644 --- a/src/hyperion-v4l2/hyperion-v4l2.cpp +++ b/src/hyperion-v4l2/hyperion-v4l2.cpp @@ -33,7 +33,7 @@ int main(int argc, char** argv) try { // create the option parser and initialize all parameters - OptionsParser optionParser("Simple application to send a command to hyperion using the Json interface"); + OptionsParser optionParser("V4L capture application for Hyperion"); ParameterSet & parameters = optionParser.getParameters(); StringParameter & argDevice = parameters.add ('d', "device", "The device to use [default=/dev/video0]"); From beddd7ccc4fc80e2ed8667005e3ac5375ac3ac05 Mon Sep 17 00:00:00 2001 From: johan Date: Sun, 26 Jan 2014 18:02:33 +0100 Subject: [PATCH 19/21] Changed default video standard Former-commit-id: fb1452ee49b188082bdbd01c8241d451f6edc704 --- deploy/hyperion.tar.gz.REMOVED.git-id | 2 +- src/hyperion-v4l2/hyperion-v4l2.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/hyperion.tar.gz.REMOVED.git-id b/deploy/hyperion.tar.gz.REMOVED.git-id index 39782de1..d10d00a7 100644 --- a/deploy/hyperion.tar.gz.REMOVED.git-id +++ b/deploy/hyperion.tar.gz.REMOVED.git-id @@ -1 +1 @@ -174682186110e5b067b5bf9be70f6683c97d4fbf \ No newline at end of file +a485d3d4675427fdfdb206cf108d945e77064d9e \ No newline at end of file diff --git a/src/hyperion-v4l2/hyperion-v4l2.cpp b/src/hyperion-v4l2/hyperion-v4l2.cpp index 51314f5f..a4464eb9 100644 --- a/src/hyperion-v4l2/hyperion-v4l2.cpp +++ b/src/hyperion-v4l2/hyperion-v4l2.cpp @@ -37,7 +37,7 @@ int main(int argc, char** argv) ParameterSet & parameters = optionParser.getParameters(); StringParameter & argDevice = parameters.add ('d', "device", "The device to use [default=/dev/video0]"); - VideoStandardParameter & argVideoStandard = parameters.add('v', "video-standard", "The used video standard. Valid values are PAL. NYSC, or NO-CHANGE [default=PAL]"); + VideoStandardParameter & argVideoStandard = parameters.add('v', "video-standard", "The used video standard. Valid values are PAL or NTSC (optional)"); IntParameter & argInput = parameters.add (0x0, "input", "Input channel (optional)"); IntParameter & argWidth = parameters.add (0x0, "width", "Try to set the width of the video input (optional)"); IntParameter & argHeight = parameters.add (0x0, "height", "Try to set the height of the video input (optional)"); @@ -54,7 +54,7 @@ int main(int argc, char** argv) // set defaults argDevice.setDefault("/dev/video0"); - argVideoStandard.setDefault(V4L2Grabber::PAL); + argVideoStandard.setDefault(V4L2Grabber::NO_CHANGE); argInput.setDefault(-1); argWidth.setDefault(-1); argHeight.setDefault(-1); From ad8afb0f12c37d6ff09008ec7ac41e0d9ad25dcd Mon Sep 17 00:00:00 2001 From: johan Date: Sun, 26 Jan 2014 20:20:55 +0100 Subject: [PATCH 20/21] Added support for YUYV Former-commit-id: 2e867b53b129af5c5f6d443a197721a3c8852a1b --- deploy/hyperion.tar.gz.REMOVED.git-id | 2 +- src/hyperion-v4l2/V4L2Grabber.cpp | 31 ++++++++++++++++++++++----- src/hyperion-v4l2/V4L2Grabber.h | 1 + 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/deploy/hyperion.tar.gz.REMOVED.git-id b/deploy/hyperion.tar.gz.REMOVED.git-id index d10d00a7..d163bc69 100644 --- a/deploy/hyperion.tar.gz.REMOVED.git-id +++ b/deploy/hyperion.tar.gz.REMOVED.git-id @@ -1 +1 @@ -a485d3d4675427fdfdb206cf108d945e77064d9e \ No newline at end of file +3ffb1a5d95dabb4c6a7b1ac40403ce5d393fe646 \ No newline at end of file diff --git a/src/hyperion-v4l2/V4L2Grabber.cpp b/src/hyperion-v4l2/V4L2Grabber.cpp index dd5cee31..a305ca9e 100644 --- a/src/hyperion-v4l2/V4L2Grabber.cpp +++ b/src/hyperion-v4l2/V4L2Grabber.cpp @@ -41,6 +41,7 @@ V4L2Grabber::V4L2Grabber(const std::string &device, int input, VideoStandard vid _ioMethod(IO_METHOD_MMAP), _fileDescriptor(-1), _buffers(), + _pixelFormat(0), _width(width), _height(height), _cropWidth(cropHorizontal), @@ -368,9 +369,15 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) } // check pixel format - if (fmt.fmt.pix.pixelformat != V4L2_PIX_FMT_UYVY) + + switch (fmt.fmt.pix.pixelformat) { - throw_exception("Only pixel format UYVY is supported"); + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YUYV: + _pixelFormat = fmt.fmt.pix.pixelformat; + break; + default: + throw_exception("Only pixel formats UYVY and YUYV are supported"); } if (_width > 0 || _height > 0) @@ -648,9 +655,23 @@ void V4L2Grabber::process_image(const uint8_t * data) for (int xSource = _cropWidth + _pixelDecimation/2, xDest = 0; xSource < _width - _cropWidth; xSource += _pixelDecimation, ++xDest) { int index = (_width * ySource + xSource) * 2; - uint8_t y = data[index+1]; - uint8_t u = (xSource%2 == 0) ? data[index] : data[index-2]; - uint8_t v = (xSource%2 == 0) ? data[index+2] : data[index]; + uint8_t y = 0; + uint8_t u = 0; + uint8_t v = 0; + + switch (_pixelFormat) + { + case V4L2_PIX_FMT_UYVY: + y = data[index+1]; + u = (xSource%2 == 0) ? data[index ] : data[index-2]; + v = (xSource%2 == 0) ? data[index+2] : data[index ]; + break; + case V4L2_PIX_FMT_YUYV: + y = data[index]; + u = (xSource%2 == 0) ? data[index+1] : data[index-1]; + v = (xSource%2 == 0) ? data[index+3] : data[index+1]; + break; + } ColorRgb & rgb = image(xDest, yDest); yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); diff --git a/src/hyperion-v4l2/V4L2Grabber.h b/src/hyperion-v4l2/V4L2Grabber.h index a8033dad..ca05204f 100644 --- a/src/hyperion-v4l2/V4L2Grabber.h +++ b/src/hyperion-v4l2/V4L2Grabber.h @@ -81,6 +81,7 @@ private: int _fileDescriptor; std::vector _buffers; + uint32_t _pixelFormat; int _width; int _height; const int _cropWidth; From 2085e3909f58f647f1f1790da6070bf1d8ae9efb Mon Sep 17 00:00:00 2001 From: johan Date: Sun, 26 Jan 2014 22:55:17 +0100 Subject: [PATCH 21/21] Added creation of symbolic link /usr/bin/hyperion-v4l2 in install script Former-commit-id: f45b8424968aa7ef5f0ca96cd716b6e3cf3b622a --- bin/install_hyperion.sh | 4 ++-- deploy/hyperion.tar.gz.REMOVED.git-id | 2 +- src/hyperion-v4l2/V4L2Grabber.cpp | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bin/install_hyperion.sh b/bin/install_hyperion.sh index 5ec9a2a0..53dd28e6 100755 --- a/bin/install_hyperion.sh +++ b/bin/install_hyperion.sh @@ -36,7 +36,7 @@ if [ $IS_OPENELEC -eq 1 ]; then # OpenELEC has a readonly file system. Use alternative location curl --get https://raw.github.com/tvdzwan/hyperion/master/deploy/hyperion.tar.gz | tar -C /storage -xz curl --get https://raw.github.com/tvdzwan/hyperion/master/deploy/hyperion.deps.openelec-rpi.tar.gz | tar -C /storage/hyperion/bin -xz - + # modify the default config to have a correct effect path sed -i 's:/opt:/storage:g' /storage/hyperion/config/hyperion.config.json else @@ -47,6 +47,7 @@ fi if [ $IS_OPENELEC -ne 1 ]; then ln -fs /opt/hyperion/bin/hyperiond /usr/bin/hyperiond ln -fs /opt/hyperion/bin/hyperion-remote /usr/bin/hyperion-remote + ln -fs /opt/hyperion/bin/hyperion-v4l2 /usr/bin/hyperion-v4l2 fi # create link to the gpio changer (gpio->spi) @@ -62,7 +63,6 @@ if [ $IS_OPENELEC -eq 1 ]; then else ln -s /opt/hyperion/config/hyperion.config.json /etc/hyperion.config.json fi - # Copy the service control configuration to /etc/int if [ $USE_INITCTL -eq 1 ]; then diff --git a/deploy/hyperion.tar.gz.REMOVED.git-id b/deploy/hyperion.tar.gz.REMOVED.git-id index d163bc69..362f60df 100644 --- a/deploy/hyperion.tar.gz.REMOVED.git-id +++ b/deploy/hyperion.tar.gz.REMOVED.git-id @@ -1 +1 @@ -3ffb1a5d95dabb4c6a7b1ac40403ce5d393fe646 \ No newline at end of file +08d42deff1de4c4296e4c6e22c783a0096ed3396 \ No newline at end of file diff --git a/src/hyperion-v4l2/V4L2Grabber.cpp b/src/hyperion-v4l2/V4L2Grabber.cpp index a305ca9e..3c8f5a91 100644 --- a/src/hyperion-v4l2/V4L2Grabber.cpp +++ b/src/hyperion-v4l2/V4L2Grabber.cpp @@ -369,7 +369,6 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) } // check pixel format - switch (fmt.fmt.pix.pixelformat) { case V4L2_PIX_FMT_UYVY: