mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2025-03-01 10:33:28 +00:00
V4L2 enhanced (#766)
* fix v4l2 standard * ignore v4l2 meta devices * added resolution, framerate and device dropdown list to WebUI (thx to @Lord-Grey & @b1rdhous3) * Fix for kernels prior to v4.16 * Device names added & WebUI adapted
This commit is contained in:
@@ -26,6 +26,10 @@
|
||||
|
||||
#define CLEAR(x) memset(&(x), 0, sizeof(x))
|
||||
|
||||
#ifndef V4L2_CAP_META_CAPTURE
|
||||
#define V4L2_CAP_META_CAPTURE 0x00800000 // Specified in kernel header v4.16. Required for backward compatibility.
|
||||
#endif
|
||||
|
||||
V4L2Grabber::V4L2Grabber(const QString & device
|
||||
, const unsigned width
|
||||
, const unsigned height
|
||||
@@ -84,13 +88,13 @@ void V4L2Grabber::uninit()
|
||||
|
||||
bool V4L2Grabber::init()
|
||||
{
|
||||
if (! _initialized)
|
||||
if (!_initialized)
|
||||
{
|
||||
getV4Ldevices();
|
||||
QString v4lDevices_str;
|
||||
|
||||
// show list only once
|
||||
if ( ! QString(QSTRING_CSTR(_deviceName)).startsWith("/dev/") )
|
||||
if (!QString(QSTRING_CSTR(_deviceName)).startsWith("/dev/"))
|
||||
{
|
||||
for (auto& dev: _v4lDevices)
|
||||
{
|
||||
@@ -100,7 +104,7 @@ bool V4L2Grabber::init()
|
||||
Info(_log, "available V4L2 devices:\n%s", QSTRING_CSTR(v4lDevices_str));
|
||||
}
|
||||
|
||||
if ( _deviceName == "auto" )
|
||||
if (_deviceName == "auto")
|
||||
{
|
||||
_deviceAutoDiscoverEnabled = true;
|
||||
_deviceName = "unknown";
|
||||
@@ -108,20 +112,20 @@ bool V4L2Grabber::init()
|
||||
for (auto& dev: _v4lDevices)
|
||||
{
|
||||
_deviceName = dev.first;
|
||||
if ( init() )
|
||||
if (init())
|
||||
{
|
||||
Info(_log, "found usable v4l2 device: %s (%s)",QSTRING_CSTR(dev.first), QSTRING_CSTR(dev.second));
|
||||
_deviceAutoDiscoverEnabled = false;
|
||||
return _initialized;
|
||||
}
|
||||
}
|
||||
Info( _log, "no usable device found" );
|
||||
Info(_log, "no usable device found");
|
||||
}
|
||||
else if ( ! _deviceName.startsWith("/dev/") )
|
||||
else if (!_deviceName.startsWith("/dev/"))
|
||||
{
|
||||
for (auto& dev: _v4lDevices)
|
||||
{
|
||||
if ( _deviceName.toLower() == dev.second.toLower() )
|
||||
if (_deviceName.toLower() == dev.second.toLower())
|
||||
{
|
||||
_deviceName = dev.first;
|
||||
Info(_log, "found v4l2 device with configured name: %s (%s)", QSTRING_CSTR(dev.second), QSTRING_CSTR(dev.first) );
|
||||
@@ -138,7 +142,7 @@ bool V4L2Grabber::init()
|
||||
try
|
||||
{
|
||||
// do not init with unknown device
|
||||
if(_deviceName != "unknown")
|
||||
if (_deviceName != "unknown")
|
||||
{
|
||||
if (open_device())
|
||||
{
|
||||
@@ -165,22 +169,96 @@ bool V4L2Grabber::init()
|
||||
void V4L2Grabber::getV4Ldevices()
|
||||
{
|
||||
QDirIterator it("/sys/class/video4linux/", QDirIterator::NoIteratorFlags);
|
||||
_deviceProperties.clear();
|
||||
while(it.hasNext())
|
||||
{
|
||||
//_v4lDevices
|
||||
QString dev = it.next();
|
||||
if (it.fileName().startsWith("video"))
|
||||
{
|
||||
QString devName = "/dev/" + it.fileName();
|
||||
int fd = open(QSTRING_CSTR(devName), O_RDWR | O_NONBLOCK, 0);
|
||||
|
||||
if (fd < 0)
|
||||
{
|
||||
throw_errno_exception("Cannot open '" + devName + "'");
|
||||
continue;
|
||||
}
|
||||
|
||||
struct v4l2_capability cap;
|
||||
CLEAR(cap);
|
||||
|
||||
if (xioctl(fd, VIDIOC_QUERYCAP, &cap) < 0)
|
||||
{
|
||||
throw_errno_exception("'" + devName + "' is no V4L2 device");
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cap.device_caps & V4L2_CAP_META_CAPTURE) // this device has bit 23 set (and bit 1 reset), so it doesn't have capture.
|
||||
{
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the current settings
|
||||
struct v4l2_format fmt;
|
||||
CLEAR(fmt);
|
||||
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (xioctl(fd, VIDIOC_G_FMT, &fmt) < 0)
|
||||
{
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
V4L2Grabber::DeviceProperties properties;
|
||||
|
||||
// collect available device resolutions & frame rates
|
||||
struct v4l2_frmsizeenum frmsizeenum;
|
||||
CLEAR(frmsizeenum);
|
||||
|
||||
frmsizeenum.index = 0;
|
||||
frmsizeenum.pixel_format = fmt.fmt.pix.pixelformat;
|
||||
while (xioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) >= 0)
|
||||
{
|
||||
switch (frmsizeenum.type)
|
||||
{
|
||||
case V4L2_FRMSIZE_TYPE_DISCRETE:
|
||||
{
|
||||
properties.resolutions << QString::number(frmsizeenum.discrete.width) + "x" + QString::number(frmsizeenum.discrete.height);
|
||||
enumFrameIntervals(properties.framerates, fd, fmt.fmt.pix.pixelformat, frmsizeenum.discrete.width, frmsizeenum.discrete.height);
|
||||
}
|
||||
break;
|
||||
case V4L2_FRMSIZE_TYPE_CONTINUOUS:
|
||||
case V4L2_FRMSIZE_TYPE_STEPWISE:
|
||||
{
|
||||
for(unsigned int y = frmsizeenum.stepwise.min_height; y <= frmsizeenum.stepwise.max_height; y += frmsizeenum.stepwise.step_height)
|
||||
{
|
||||
for(unsigned int x = frmsizeenum.stepwise.min_width; x <= frmsizeenum.stepwise.max_width; x += frmsizeenum.stepwise.step_width)
|
||||
{
|
||||
properties.resolutions << QString::number(x) + "x" + QString::number(y);
|
||||
enumFrameIntervals(properties.framerates, fd, fmt.fmt.pix.pixelformat, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
frmsizeenum.index++;
|
||||
}
|
||||
|
||||
if (close(fd) < 0) continue;
|
||||
|
||||
QFile devNameFile(dev+"/name");
|
||||
QString devName;
|
||||
if ( devNameFile.exists())
|
||||
if (devNameFile.exists())
|
||||
{
|
||||
devNameFile.open(QFile::ReadOnly);
|
||||
devName = devNameFile.readLine();
|
||||
devName = devName.trimmed();
|
||||
properties.name = devName;
|
||||
devNameFile.close();
|
||||
}
|
||||
_v4lDevices.emplace("/dev/"+it.fileName(), devName);
|
||||
_deviceProperties.insert("/dev/"+it.fileName(), properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,6 +315,7 @@ void V4L2Grabber::stop()
|
||||
uninit_device();
|
||||
close_device();
|
||||
_initialized = false;
|
||||
_deviceProperties.clear();
|
||||
Info(_log, "Stopped");
|
||||
}
|
||||
}
|
||||
@@ -409,6 +488,8 @@ void V4L2Grabber::init_userp(unsigned int buffer_size)
|
||||
void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
|
||||
{
|
||||
struct v4l2_capability cap;
|
||||
CLEAR(cap);
|
||||
|
||||
if (-1 == xioctl(VIDIOC_QUERYCAP, &cap))
|
||||
{
|
||||
if (EINVAL == errno)
|
||||
@@ -484,6 +565,8 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
|
||||
|
||||
// set input if needed and supported
|
||||
struct v4l2_input v4l2Input;
|
||||
CLEAR(v4l2Input);
|
||||
|
||||
v4l2Input.index = input;
|
||||
|
||||
if (input >= 0 && 0 == xioctl(VIDIOC_ENUMINPUT,&v4l2Input))
|
||||
@@ -496,41 +579,46 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
|
||||
}
|
||||
|
||||
// set the video standard if needed and supported
|
||||
v4l2_std_id std_id;
|
||||
if (-1 != xioctl(VIDIOC_ENUMSTD, &std_id))
|
||||
struct v4l2_standard standard;
|
||||
CLEAR(standard);
|
||||
|
||||
if (-1 != xioctl(VIDIOC_ENUMSTD, &standard))
|
||||
{
|
||||
switch (videoStandard)
|
||||
{
|
||||
case VIDEOSTANDARD_PAL:
|
||||
{
|
||||
std_id = V4L2_STD_PAL;
|
||||
if (-1 == xioctl(VIDIOC_S_STD, &std_id))
|
||||
standard.id = V4L2_STD_PAL;
|
||||
if (-1 == xioctl(VIDIOC_S_STD, &standard.id))
|
||||
{
|
||||
throw_errno_exception("VIDIOC_S_STD");
|
||||
break;
|
||||
}
|
||||
Debug(_log, "Video standard=PAL");
|
||||
}
|
||||
break;
|
||||
|
||||
case VIDEOSTANDARD_NTSC:
|
||||
{
|
||||
std_id = V4L2_STD_NTSC;
|
||||
if (-1 == xioctl(VIDIOC_S_STD, &std_id))
|
||||
standard.id = V4L2_STD_NTSC;
|
||||
if (-1 == xioctl(VIDIOC_S_STD, &standard.id))
|
||||
{
|
||||
throw_errno_exception("VIDIOC_S_STD");
|
||||
break;
|
||||
}
|
||||
Debug(_log, "Video standard=NTSC");
|
||||
}
|
||||
break;
|
||||
|
||||
case VIDEOSTANDARD_SECAM:
|
||||
{
|
||||
std_id = V4L2_STD_SECAM;
|
||||
if (-1 == xioctl(VIDIOC_S_STD, &std_id))
|
||||
standard.id = V4L2_STD_SECAM;
|
||||
if (-1 == xioctl(VIDIOC_S_STD, &standard.id))
|
||||
{
|
||||
throw_errno_exception("VIDIOC_S_STD");
|
||||
break;
|
||||
}
|
||||
Debug(_log, "Video standard=SECAM");
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -544,6 +632,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
|
||||
// get the current settings
|
||||
struct v4l2_format fmt;
|
||||
CLEAR(fmt);
|
||||
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (-1 == xioctl(VIDIOC_G_FMT, &fmt))
|
||||
{
|
||||
@@ -581,42 +670,14 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
|
||||
break;
|
||||
}
|
||||
|
||||
// collect available device resolutions
|
||||
QString v4lDevice_res;
|
||||
v4l2_frmsizeenum frmsizeenum;
|
||||
CLEAR(frmsizeenum);
|
||||
frmsizeenum.index = 0;
|
||||
frmsizeenum.pixel_format = fmt.fmt.pix.pixelformat;
|
||||
while (xioctl(VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) >= 0)
|
||||
// set custom resolution for width and height if they are not zero
|
||||
if(_width && _height)
|
||||
{
|
||||
switch (frmsizeenum.type)
|
||||
{
|
||||
case V4L2_FRMSIZE_TYPE_DISCRETE:
|
||||
v4lDevice_res += "\t"+ QString::number(frmsizeenum.discrete.width) + "x" + QString::number(frmsizeenum.discrete.height) + "\n";
|
||||
break;
|
||||
case V4L2_FRMSIZE_TYPE_CONTINUOUS:
|
||||
case V4L2_FRMSIZE_TYPE_STEPWISE:
|
||||
{
|
||||
for(unsigned int y = frmsizeenum.stepwise.min_height; y <= frmsizeenum.stepwise.max_height; y += frmsizeenum.stepwise.step_height)
|
||||
{
|
||||
for(unsigned int x = frmsizeenum.stepwise.min_width; x <= frmsizeenum.stepwise.max_width; x += frmsizeenum.stepwise.step_width)
|
||||
{
|
||||
v4lDevice_res += "\t"+ QString::number(x) + "x" + QString::number(y) + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
frmsizeenum.index++;
|
||||
fmt.fmt.pix.width = _width;
|
||||
fmt.fmt.pix.height = _height;
|
||||
}
|
||||
|
||||
// print available device resolutions in debug mode
|
||||
if (!v4lDevice_res.isEmpty())
|
||||
Debug(_log, "available V4L2 resolutions:\n%s", QSTRING_CSTR(v4lDevice_res));
|
||||
|
||||
// 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");
|
||||
@@ -633,30 +694,19 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
|
||||
// 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))
|
||||
// Check that the driver knows about framerate get/set
|
||||
if (xioctl(VIDIOC_G_PARM, &streamparms) >= 0)
|
||||
{
|
||||
Debug(_log, "Frame rate settings not supported");
|
||||
// continue
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check the capability flag is set to V4L2_CAP_TIMEPERFRAME
|
||||
// Check if the device is able to accept a capture framerate set.
|
||||
if (streamparms.parm.capture.capability == V4L2_CAP_TIMEPERFRAME)
|
||||
{
|
||||
// Driver supports the feature. Set required framerate
|
||||
CLEAR(streamparms);
|
||||
streamparms.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
streamparms.parm.capture.timeperframe.numerator = 1;
|
||||
streamparms.parm.capture.timeperframe.denominator = _fps;
|
||||
if(-1 == xioctl(VIDIOC_S_PARM, &streamparms))
|
||||
{
|
||||
throw_errno_exception("VIDIOC_S_PARM");
|
||||
// continue
|
||||
}
|
||||
else
|
||||
// display the used framerate
|
||||
Debug(_log, "Set framerate to %d fps", _fps);
|
||||
(-1 == xioctl(VIDIOC_S_PARM, &streamparms))
|
||||
? Debug(_log, "Frame rate settings not supported.")
|
||||
: Debug(_log, "Set framerate to %d fps", streamparms.parm.capture.timeperframe.denominator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -883,7 +933,11 @@ int V4L2Grabber::read_frame()
|
||||
|
||||
case EIO: /* Could ignore EIO, see spec. */
|
||||
default:
|
||||
{
|
||||
throw_errno_exception("VIDIOC_DQBUF");
|
||||
stop();
|
||||
getV4Ldevices();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -916,7 +970,11 @@ int V4L2Grabber::read_frame()
|
||||
|
||||
case EIO: /* Could ignore EIO, see spec. */
|
||||
default:
|
||||
{
|
||||
throw_errno_exception("VIDIOC_DQBUF");
|
||||
stop();
|
||||
getV4Ldevices();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -953,9 +1011,9 @@ bool V4L2Grabber::process_image(const void *p, int size)
|
||||
{
|
||||
// We do want a new frame...
|
||||
#ifdef HAVE_JPEG_DECODER
|
||||
if (size != _frameByteSize && _pixelFormat != PIXELFORMAT_MJPEG)
|
||||
if (size < _frameByteSize && _pixelFormat != PIXELFORMAT_MJPEG)
|
||||
#else
|
||||
if (size != _frameByteSize)
|
||||
if (size < _frameByteSize)
|
||||
#endif
|
||||
{
|
||||
Error(_log, "Frame too small: %d != %d", size, _frameByteSize);
|
||||
@@ -1100,7 +1158,7 @@ void V4L2Grabber::process_image(const uint8_t * data, int size)
|
||||
* ------------ END of JPEG decoder related code ------------
|
||||
* --------------------------------------------------------*/
|
||||
|
||||
_imageResampler.processImage(data, _width, _height, _lineLength, _pixelFormat, image);
|
||||
_imageResampler.processImage(data, _width, _height, _lineLength, _pixelFormat, image);
|
||||
|
||||
if (_signalDetectionEnabled)
|
||||
{
|
||||
@@ -1168,6 +1226,60 @@ int V4L2Grabber::xioctl(int request, void *arg)
|
||||
return r;
|
||||
}
|
||||
|
||||
int V4L2Grabber::xioctl(int fileDescriptor, int request, void *arg)
|
||||
{
|
||||
int r;
|
||||
|
||||
do
|
||||
{
|
||||
r = ioctl(fileDescriptor, request, arg);
|
||||
}
|
||||
while (r < 0 && errno == EINTR );
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void V4L2Grabber::enumFrameIntervals(QStringList &framerates, int fileDescriptor, int pixelformat, int width, int height)
|
||||
{
|
||||
// collect available frame rates
|
||||
struct v4l2_frmivalenum frmivalenum;
|
||||
CLEAR(frmivalenum);
|
||||
|
||||
frmivalenum.index = 0;
|
||||
frmivalenum.pixel_format = pixelformat;
|
||||
frmivalenum.width = width;
|
||||
frmivalenum.height = height;
|
||||
|
||||
while (xioctl(fileDescriptor, VIDIOC_ENUM_FRAMEINTERVALS, &frmivalenum) >= 0)
|
||||
{
|
||||
int rate;
|
||||
switch (frmivalenum.type)
|
||||
{
|
||||
case V4L2_FRMSIZE_TYPE_DISCRETE:
|
||||
{
|
||||
if (frmivalenum.discrete.numerator != 0)
|
||||
{
|
||||
rate = frmivalenum.discrete.denominator / frmivalenum.discrete.numerator;
|
||||
if (!framerates.contains(QString::number(rate)))
|
||||
framerates.append(QString::number(rate));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case V4L2_FRMSIZE_TYPE_CONTINUOUS:
|
||||
case V4L2_FRMSIZE_TYPE_STEPWISE:
|
||||
{
|
||||
if (frmivalenum.stepwise.min.denominator != 0)
|
||||
{
|
||||
rate = frmivalenum.stepwise.min.denominator / frmivalenum.stepwise.min.numerator;
|
||||
if (!framerates.contains(QString::number(rate)))
|
||||
framerates.append(QString::number(rate));
|
||||
}
|
||||
}
|
||||
}
|
||||
frmivalenum.index++;
|
||||
}
|
||||
}
|
||||
|
||||
void V4L2Grabber::setSignalDetectionEnable(bool enable)
|
||||
{
|
||||
if (_signalDetectionEnabled != enable)
|
||||
@@ -1227,3 +1339,28 @@ bool V4L2Grabber::setWidthHeight(int width, int height)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList V4L2Grabber::getV4L2devices()
|
||||
{
|
||||
QStringList result = QStringList();
|
||||
for (auto it = _deviceProperties.begin(); it != _deviceProperties.end(); ++it)
|
||||
{
|
||||
result << it.key();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString V4L2Grabber::getV4L2deviceName(QString devicePath)
|
||||
{
|
||||
return _deviceProperties.value(devicePath).name;
|
||||
}
|
||||
|
||||
QStringList V4L2Grabber::getResolutions(QString devicePath)
|
||||
{
|
||||
return _deviceProperties.value(devicePath).resolutions;
|
||||
}
|
||||
|
||||
QStringList V4L2Grabber::getFramerates(QString devicePath)
|
||||
{
|
||||
return _deviceProperties.value(devicePath).framerates;
|
||||
}
|
||||
|
Reference in New Issue
Block a user