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:
Paulchen Panther
2020-04-17 16:59:20 +02:00
committed by GitHub
parent b92af63cef
commit 10f11c2d89
20 changed files with 717 additions and 175 deletions

View File

@@ -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;
}