Bug fixes and new implementations

- Video format MJPEG implemented (libjpeg/qimage)
- Inactive priorities are now skipped correctly (PriorityMuxer.cpp line 297)
- v4l configuration section replaced with an object (preparation for #542)
This commit is contained in:
Paulchen-Panther 2019-04-28 19:53:45 +02:00
parent 4aab0ad55c
commit 0a8af60726
No known key found for this signature in database
GPG Key ID: 84E3B692456B6840
15 changed files with 587 additions and 415 deletions

View File

@ -268,6 +268,18 @@ find_package(libusb-1.0 REQUIRED)
find_package(Threads REQUIRED)
add_definitions(${QT_DEFINITIONS})
# Add jpeg library
if (ENABLE_V4L2)
find_package(JPEG)
if (JPEG_FOUND)
add_definitions(-DHAVE_JPEG)
message( STATUS "Using JPEG library: ${JPEG_LIBRARIES}")
include_directories(${JPEG_INCLUDE_DIR})
else()
message( STATUS "JPEG library not found, MJPEG camera format won't work in V4L2 grabber.")
endif()
endif()
# TODO[TvdZ]: This linking directory should only be added if we are cross compiling
#if(NOT APPLE)
# link_directories(${CMAKE_FIND_ROOT_PATH}/lib/arm-linux-gnueabihf)

View File

@ -26,7 +26,7 @@ Note: call the script with `./docker-compile.sh -h` for more options
```
sudo apt-get update
sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev
sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev
```
**on RPI you need the videocore IV headers**

View File

@ -27,7 +27,7 @@ $(document).ready( function() {
//v4l
$('#conf_cont').append(createRow('conf_cont_v4l'))
$('#conf_cont_v4l').append(createOptPanel('fa-camera', $.i18n("edt_conf_v4l2_heading_title"), 'editor_container_v4l2', 'btn_submit_v4l2'));
$('#conf_cont_v4l').append(createHelpTable(schema.grabberV4L2.items.properties, $.i18n("edt_conf_v4l2_heading_title")));
$('#conf_cont_v4l').append(createHelpTable(schema.grabberV4L2.properties, $.i18n("edt_conf_v4l2_heading_title")));
}
else
{

View File

@ -114,7 +114,6 @@
/// * sDHOffsetMax : area for signal detection - horizontal maximum offset value. Values between 0.0 and 1.0
/// * sDVOffsetMax : area for signal detection - vertical maximum offset value. Values between 0.0 and 1.0
"grabberV4L2" :
[
{
"device" : "auto",
"standard" : "NO_CHANGE",
@ -132,8 +131,7 @@
"sDHOffsetMin" : 0.25,
"sDVOffsetMax" : 0.75,
"sDHOffsetMax" : 0.75
}
],
},
/// The configuration for the frame-grabber, contains the following items:
/// * type : type of grabber. (auto|osx|dispmanx|amlogic|x11|framebuffer|qt) [auto]

View File

@ -57,7 +57,6 @@
},
"grabberV4L2" :
[
{
"device" : "auto",
"standard" : "NO_CHANGE",
@ -74,8 +73,7 @@
"sDHOffsetMin" : 0.25,
"sDVOffsetMax" : 0.75,
"sDHOffsetMax" : 0.75
}
],
},
"framegrabber" :
{

View File

@ -13,6 +13,13 @@
#include <utils/PixelFormat.h>
#include <hyperion/Grabber.h>
#include <grabber/VideoStandard.h>
#include <utils/Components.h>
#ifdef HAVE_JPEG
#include <QImage>
#include <jpeglib.h>
#include <csetjmp>
#endif
/// Capture class for V4L2 devices
///
@ -78,6 +85,8 @@ public slots:
void stop();
void componentStateChanged(const hyperion::Components component, bool enable);
signals:
void newFrame(const Image<ColorRgb> & image);
void readError(const char* err);
@ -111,7 +120,7 @@ private:
bool process_image(const void *p, int size);
void process_image(const uint8_t *p);
void process_image(const uint8_t *p, int size);
int xioctl(int request, void *arg);
@ -120,17 +129,41 @@ private:
void throw_errno_exception(const QString &error);
private:
enum io_method {
enum io_method
{
IO_METHOD_READ,
IO_METHOD_MMAP,
IO_METHOD_USERPTR
};
struct buffer {
struct buffer
{
void *start;
size_t length;
};
#ifdef HAVE_JPEG
struct errorManager
{
jpeg_error_mgr pub;
jmp_buf setjmp_buffer;
};
static void errorHandler(j_common_ptr cInfo)
{
errorManager* mgr = reinterpret_cast<errorManager*>(cInfo->err);
longjmp(mgr->setjmp_buffer, 1);
}
static void outputHandler(j_common_ptr cInfo)
{
// Suppress fprintf warnings.
}
jpeg_decompress_struct* _decompress;
errorManager* _error;
#endif
private:
QString _deviceName;
std::map<QString,QString> _v4lDevices;
@ -156,7 +189,7 @@ private:
double _x_frac_max;
double _y_frac_max;
QSocketNotifier * _streamNotifier;
QSocketNotifier *_streamNotifier;
bool _initialized;
bool _deviceAutoDiscoverEnabled;

View File

@ -12,6 +12,9 @@ enum PixelFormat {
PIXELFORMAT_BGR24,
PIXELFORMAT_RGB32,
PIXELFORMAT_BGR32,
#ifdef HAVE_JPEG
PIXELFORMAT_MJPEG,
#endif
PIXELFORMAT_NO_CHANGE
};
@ -44,6 +47,12 @@ inline PixelFormat parsePixelFormat(QString pixelFormat)
{
return PIXELFORMAT_BGR32;
}
#ifdef HAVE_JPEG
else if (pixelFormat == "mjpeg")
{
return PIXELFORMAT_MJPEG;
}
#endif
// return the default NO_CHANGE
return PIXELFORMAT_NO_CHANGE;

View File

@ -10,3 +10,7 @@ target_link_libraries(v4l2-grabber
hyperion
${QT_LIBRARIES}
)
if (JPEG_FOUND)
target_link_libraries(v4l2-grabber ${JPEG_LIBRARY})
endif()

View File

@ -16,11 +16,15 @@
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <hyperion/Hyperion.h>
#include <QDirIterator>
#include <QFileInfo>
#include "grabber/V4L2Grabber.h"
using namespace hyperion;
#define CLEAR(x) memset(&(x), 0, sizeof(x))
V4L2Grabber::V4L2Grabber(const QString & device
@ -55,6 +59,10 @@ V4L2Grabber::V4L2Grabber(const QString & device
setPixelDecimation(pixelDecimation);
getV4Ldevices();
// listen for component change for build-in grabber only
if (Hyperion::_hyperion)
connect(Hyperion::getInstance(), &Hyperion::componentStateChanged, this, &V4L2Grabber::componentStateChanged);
// init
setDeviceVideoStandard(device, videoStandard);
}
@ -70,11 +78,7 @@ void V4L2Grabber::uninit()
if (_initialized)
{
Debug(_log,"uninit grabber: %s", QSTRING_CSTR(_deviceName));
stop();
uninit_device();
close_device();
_initialized = false;
}
}
@ -233,6 +237,9 @@ void V4L2Grabber::stop()
{
stop_capturing();
_streamNotifier->setEnabled(false);
uninit_device();
close_device();
_initialized = false;
Info(_log, "Stopped");
}
}
@ -527,63 +534,98 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
case PIXELFORMAT_RGB32:
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32;
break;
#ifdef HAVE_JPEG
case PIXELFORMAT_MJPEG:
{
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
}
break;
#endif
case PIXELFORMAT_NO_CHANGE:
default:
// No change to device settings
break;
}
// TODO Does never accept own sizes? use always _imageResampler instead
/*
// calc the size based on pixelDecimation
fmt.fmt.pix.width = fmt.fmt.pix.width / _pixelDecimation;
fmt.fmt.pix.height = fmt.fmt.pix.height / _pixelDecimation;
// set the settings
fmt.fmt.pix.width = _width;
fmt.fmt.pix.height = _height;
if (-1 == xioctl(VIDIOC_S_FMT, &fmt))
{
throw_errno_exception("VIDIOC_S_FMT");
return;
}
// get the format settings again
// (the size may not have been accepted without an error)
if (-1 == xioctl(VIDIOC_G_FMT, &fmt))
{
throw_errno_exception("VIDIOC_G_FMT");
return;
}
*/
// set the line length
_lineLength = fmt.fmt.pix.bytesperline;
// store width & height
// initialize current width and height
_width = fmt.fmt.pix.width;
_height = fmt.fmt.pix.height;
// display the used width and height
Debug(_log, "width=%d height=%d", _width, _height );
// Trying to set frame rate
struct v4l2_streamparm streamparms;
CLEAR(streamparms);
streamparms.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == xioctl(VIDIOC_G_PARM, &streamparms))
{
throw_errno_exception("VIDIOC_G_PARM");
// continue
}
else
{
// Check the capability flag is set to V4L2_CAP_TIMEPERFRAME
if (streamparms.parm.capture.capability == V4L2_CAP_TIMEPERFRAME)
{
// Driver supports the feature. Set required framerate
streamparms.parm.capture.capturemode = V4L2_MODE_HIGHQUALITY;
streamparms.parm.capture.timeperframe.numerator = 1;
streamparms.parm.capture.timeperframe.denominator = 30;
if(-1 == xioctl(VIDIOC_S_PARM, &streamparms))
{
throw_errno_exception("VIDIOC_S_PARM");
return;
}
}
}
// set the line length
_lineLength = fmt.fmt.pix.bytesperline;
// check pixel format and frame size
switch (fmt.fmt.pix.pixelformat)
{
case V4L2_PIX_FMT_UYVY:
{
_pixelFormat = PIXELFORMAT_UYVY;
_frameByteSize = _width * _height * 2;
Debug(_log, "Pixel format=UYVY");
}
break;
case V4L2_PIX_FMT_YUYV:
{
_pixelFormat = PIXELFORMAT_YUYV;
_frameByteSize = _width * _height * 2;
Debug(_log, "Pixel format=YUYV");
}
break;
case V4L2_PIX_FMT_RGB32:
{
_pixelFormat = PIXELFORMAT_RGB32;
_frameByteSize = _width * _height * 4;
Debug(_log, "Pixel format=RGB32");
}
break;
#ifdef HAVE_JPEG
case V4L2_PIX_FMT_MJPEG:
{
_pixelFormat = PIXELFORMAT_MJPEG;
Debug(_log, "Pixel format=MJPEG");
}
break;
#endif
default:
throw_exception("Only pixel formats UYVY, YUYV, and RGB32 are supported");
return;
@ -714,7 +756,8 @@ int V4L2Grabber::read_frame()
{
struct v4l2_buffer buf;
switch (_ioMethod) {
switch (_ioMethod)
{
case IO_METHOD_READ:
int size;
if ((size = read(_fileDescriptor, _buffers[0].start, _buffers[0].length)) == -1)
@ -828,22 +871,100 @@ int V4L2Grabber::read_frame()
bool V4L2Grabber::process_image(const void *p, int size)
{
// We do want a new frame...
#ifdef HAVE_JPEG
if (size != _frameByteSize && _pixelFormat != PIXELFORMAT_MJPEG)
#else
if (size != _frameByteSize)
#endif
{
Error(_log, "Frame too small: %d != %d", size, _frameByteSize);
}
else
{
process_image(reinterpret_cast<const uint8_t *>(p));
process_image(reinterpret_cast<const uint8_t *>(p), size);
return true;
}
return false;
}
void V4L2Grabber::process_image(const uint8_t * data)
void V4L2Grabber::process_image(const uint8_t * data, int size)
{
Image<ColorRgb> image(0, 0);
Image<ColorRgb> image(_width, _height);
#ifdef HAVE_JPEG
if (_pixelFormat == PIXELFORMAT_MJPEG)
{
_decompress = new jpeg_decompress_struct;
_error = new errorManager;
_decompress->err = jpeg_std_error(&_error->pub);
_error->pub.error_exit = &errorHandler;
_error->pub.output_message = &outputHandler;
jpeg_create_decompress(_decompress);
if (setjmp(_error->setjmp_buffer))
{
jpeg_abort_decompress(_decompress);
jpeg_destroy_decompress(_decompress);
delete _decompress;
delete _error;
return;
}
jpeg_mem_src(_decompress, const_cast<uint8_t*>(data), size);
if (jpeg_read_header(_decompress, (bool) TRUE) != JPEG_HEADER_OK)
{
jpeg_abort_decompress(_decompress);
jpeg_destroy_decompress(_decompress);
delete _decompress;
delete _error;
return;
}
jpeg_start_decompress(_decompress);
QImage imageFrame = QImage(_decompress->output_width, _decompress->output_height, QImage::Format_RGB888);
int y = 0;
while (_decompress->output_scanline < _decompress->output_height)
{
uchar *row = imageFrame.scanLine(_decompress->output_scanline);
jpeg_read_scanlines(_decompress, &row, 1);
y++;
}
jpeg_finish_decompress(_decompress);
jpeg_destroy_decompress(_decompress);
delete _decompress;
delete _error;
if (imageFrame.isNull())
return;
QRect rect(_cropLeft, _cropTop, imageFrame.width() - _cropLeft - _cropRight, imageFrame.height() - _cropTop - _cropBottom);
imageFrame = imageFrame.copy(rect);
imageFrame = imageFrame.scaled(imageFrame.width() / _pixelDecimation, imageFrame.height() / _pixelDecimation,Qt::KeepAspectRatio);
if ((image.width() != unsigned(imageFrame.width())) && (image.height() != unsigned(imageFrame.height())))
image.resize(imageFrame.width(), imageFrame.height());
for (int y=0; y<imageFrame.height(); ++y)
{
for (int x=0; x<imageFrame.width(); ++x)
{
const QRgb inPixel = imageFrame.pixel(x,y);
ColorRgb & outPixel = image(x,y);
outPixel.red = (inPixel & 0xff0000) >> 16;
outPixel.green = (inPixel & 0xff00) >> 8;
outPixel.blue = (inPixel & 0xff);
}
}
}
else
#endif
_imageResampler.processImage(data, _width, _height, _lineLength, _pixelFormat, image);
if (_signalDetectionEnabled)
@ -963,3 +1084,19 @@ void V4L2Grabber::setDeviceVideoStandard(QString device, VideoStandard videoStan
start();
}
}
void V4L2Grabber::componentStateChanged(const hyperion::Components component, bool enable)
{
if (component == COMP_V4L)
{
if (_initialized != enable)
{
if (enable)
{
if(init()) start();
}
else
uninit();
}
}
}

View File

@ -294,7 +294,7 @@ void PriorityMuxer::setCurrentTime(void)
else
{
// timeoutTime of -100 is awaiting data (inactive); skip
if(infoIt->timeoutTime_ms >= -100)
if(infoIt->timeoutTime_ms > -100)
newPriority = qMin(newPriority, infoIt->priority);
// call timeTrigger when effect or color is running with timeout > 0, blacklist prio 255

View File

@ -1,11 +1,4 @@
{
"type":"array",
"required" : true,
"title" : "edt_conf_v4l2_heading_title",
"minItems": 1,
"maxItems": 1,
"items":
{
"type" : "object",
"required" : true,
"title" : "edt_conf_v4l2_heading_title",
@ -204,5 +197,4 @@
}
},
"additionalProperties" : false
}
}

View File

@ -121,6 +121,10 @@ void ImageResampler::processImage(const uint8_t * data, int width, int height, i
rgb.red = data[index+2];
}
break;
#ifdef HAVE_JPEG
case PIXELFORMAT_MJPEG:
break;
#endif
case PIXELFORMAT_NO_CHANGE:
Error(Logger::getInstance("ImageResampler"), "Invalid pixel format given");
break;

View File

@ -60,7 +60,7 @@ int main(int argc, char** argv)
Option & argDevice = parser.add<Option> ('d', "device", "The device to use, can be /dev/video0 [default: %1 (auto detected)]", "auto");
SwitchOption<VideoStandard> & argVideoStandard= parser.add<SwitchOption<VideoStandard>>('v', "video-standard", "The used video standard. Valid values are PAL, NTSC, SECAM or no-change. [default: %1]", "no-change");
SwitchOption<PixelFormat> & argPixelFormat = parser.add<SwitchOption<PixelFormat>> (0x0, "pixel-format", "The use pixel format. Valid values are YUYV, UYVY, RGB32 or no-change. [default: %1]", "no-change");
SwitchOption<PixelFormat> & argPixelFormat = parser.add<SwitchOption<PixelFormat>> (0x0, "pixel-format", "The use pixel format. Valid values are YUYV, UYVY, RGB32, MJPEG or no-change. [default: %1]", "no-change");
IntOption & argCropWidth = parser.add<IntOption> (0x0, "crop-width", "Number of pixels to crop from the left and right sides of the picture before decimation [default: %1]", "0");
IntOption & argCropHeight = parser.add<IntOption> (0x0, "crop-height", "Number of pixels to crop from the top and the bottom of the picture before decimation [default: %1]", "0");
IntOption & argCropLeft = parser.add<IntOption> (0x0, "crop-left", "Number of pixels to crop from the left of the picture before decimation (overrides --crop-width)");
@ -96,6 +96,9 @@ int main(int argc, char** argv)
argPixelFormat.addSwitch("yuyv", PIXELFORMAT_YUYV);
argPixelFormat.addSwitch("uyvy", PIXELFORMAT_UYVY);
argPixelFormat.addSwitch("rgb32", PIXELFORMAT_RGB32);
#ifdef HAVE_JPEG
argPixelFormat.addSwitch("mjpeg", PIXELFORMAT_MJPEG);
#endif
argPixelFormat.addSwitch("no-change", PIXELFORMAT_NO_CHANGE);
// parse all options
@ -212,9 +215,11 @@ int main(int argc, char** argv)
// Connect the screen capturing to flatbuf connection processing
QObject::connect(&grabber, SIGNAL(newFrame(const Image<ColorRgb> &)), &flatbuf, SLOT(setImage(Image<ColorRgb>)));
if (grabber.start())
QCoreApplication::exec();
grabber.stop();
// Start the capturing
grabber.start();
// Start the application
app.exec();
}
}
catch (const std::runtime_error & e)

View File

@ -57,7 +57,7 @@ HyperionDaemon::HyperionDaemon(QString configFile, const QString rootPath, QObje
, _webserver(nullptr)
, _jsonServer(nullptr)
, _udpListener(nullptr)
, _v4l2Grabbers()
, _v4l2Grabbers(nullptr)
, _dispmanx(nullptr)
, _x11Grabber(nullptr)
, _amlGrabber(nullptr)
@ -162,14 +162,10 @@ void HyperionDaemon::freeObjects()
delete _fbGrabber;
delete _osxGrabber;
delete _qtGrabber;
for(V4L2Wrapper* grabber : _v4l2Grabbers)
{
delete grabber;
}
delete _v4l2Grabbers;
delete _stats;
_v4l2Grabbers.clear();
_v4l2Grabbers = nullptr;
_bonjourBrowserWrapper = nullptr;
_amlGrabber = nullptr;
_dispmanx = nullptr;
@ -386,19 +382,9 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type& type, const QJso
}
else if(type == settings::V4L2)
{
// stop
if(_v4l2Grabbers.size()>0)
return;
unsigned v4lEnableCount = 0;
const QJsonArray & v4lArray = config.array();
for ( signed idx=0; idx<v4lArray.size(); idx++)
{
#ifdef ENABLE_V4L2
const QJsonObject & grabberConfig = v4lArray.at(idx).toObject();
bool enableV4l = grabberConfig["enable"].toBool(true);
#ifdef ENABLE_V4L2
const QJsonObject & grabberConfig = config.object();
V4L2Wrapper* grabber = new V4L2Wrapper(
grabberConfig["device"].toString("auto"),
@ -425,15 +411,9 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type& type, const QJso
// connect to HyperionDaemon signal
connect(this, &HyperionDaemon::videoMode, grabber, &V4L2Wrapper::setVideoMode);
connect(this, &HyperionDaemon::settingsChanged, grabber, &V4L2Wrapper::handleSettingsUpdate);
if (enableV4l)
v4lEnableCount++;
_v4l2Grabbers.push_back(grabber);
#endif
}
ErrorIf( (v4lEnableCount>0 && _v4l2Grabbers.size()==0), _log, "The v4l2 grabber can not be instantiated, because it has been left out from the build");
#else
Error(_log, "The v4l2 grabber can not be instantiated, because it has been left out from the build");
#endif
}
}
@ -449,7 +429,7 @@ void HyperionDaemon::createGrabberDispmanx()
Info(_log, "DISPMANX frame grabber created");
#else
Error( _log, "The dispmanx framegrabber can not be instantiated, because it has been left out from the build");
Error(_log, "The dispmanx framegrabber can not be instantiated, because it has been left out from the build");
#endif
}
@ -466,7 +446,7 @@ void HyperionDaemon::createGrabberAmlogic()
Info(_log, "AMLOGIC grabber created");
#else
Error( _log, "The AMLOGIC grabber can not be instantiated, because it has been left out from the build");
Error(_log, "The AMLOGIC grabber can not be instantiated, because it has been left out from the build");
#endif
}

View File

@ -137,7 +137,7 @@ private:
WebServer* _webserver;
JsonServer* _jsonServer;
UDPListener* _udpListener;
std::vector<V4L2Wrapper*> _v4l2Grabbers;
V4L2Wrapper* _v4l2Grabbers;
DispmanxWrapper* _dispmanx;
X11Wrapper* _x11Grabber;
AmlogicWrapper* _amlGrabber;