Commit aef0428d authored by Thiago Santini's avatar Thiago Santini

Creates a usb context per camera.

Previously, we had a single usb context for everyone.
This was causing the usb context thread handler to be overloaded
and signficantly reduced performance (and possibly caused frame
corruption?).

As of this commit, CPU consumption for uvc devices is greatly reduced
(although still not on pair with devices using direct show!).

Tested on Windows 8.1 and Windows 10.

One issue was observed during tests with this new context setup.
Sometimes when reinitializing (e.g., changing the framerate or resolution),
the uvc stream will be initialized without errors, but we only receive a
single (sometimes corrupted) frame back; afterwards, no new frames are
received.
Reopening the camera seems to solve the issue, so this is low priority
atm.
parent 882fb18b
......@@ -11,9 +11,9 @@ UVCCameraControl::UVCCameraControl(QObject *parent)
, m_state(QCamera::UnloadedState)
, m_captureMode(QCamera::CaptureStillImage)
{
m_session = qobject_cast<UVCCameraSession*>(parent);
m_session = qobject_cast<UVCCameraSession*>(parent);
//connect(m_session, SIGNAL(statusChanged(QCamera::Status)),
// this, SIGNAL(statusChanged(QCamera::Status)));
// this, SIGNAL(statusChanged(QCamera::Status)));
}
UVCCameraControl::~UVCCameraControl()
......
......@@ -20,7 +20,7 @@ public:
QCamera::State state() const { return m_state; }
QCamera::CaptureModes captureMode() const { return m_captureMode; }
void setCaptureMode(QCamera::CaptureModes mode);
void setCaptureMode(QCamera::CaptureModes mode);
void setState(QCamera::State state);
......
......@@ -3,13 +3,16 @@
#include <QtConcurrent/QtConcurrent>
QMutex UVCCameraSession::sessionMutex;
QMap<QString,bool> UVCCameraSession::devices;
QMutex UVCCameraSession::devicesMutex;
UVCCameraSession::UVCCameraSession(QObject *parent)
: QObject(parent),
m_surface(Q_NULLPTR),
streaming(false),
bandwidthFactor(1.3f),
interval(0),
interval(0),
ctx(NULL),
dev(NULL),
devh(NULL)
{
......@@ -17,12 +20,60 @@ UVCCameraSession::UVCCameraSession(QObject *parent)
UVCCameraSession::~UVCCameraSession()
{
unload();
unload();
release(deviceName);
if (ctx)
uvc_exit(ctx);
}
void UVCCameraSession::setDevice(uvc_device_t *device)
bool UVCCameraSession::setDevice(const QString &device)
{
dev = device;
if ( ! acquire(device) ) {
qWarning() << device << "already in use.";
return false;
}
uvc_error_t res;
if (!ctx) {
res = uvc_init(&ctx, NULL);
if (res < 0) {
qWarning() << "uvc_init" << uvc_strerror(res);
ctx = NULL;
return false;
}
}
uvc_device_t **devList;
res = uvc_get_device_list(ctx, &devList);
if (res < 0) {
qWarning() << "uvc_get_device_list" << uvc_strerror(res);
return false;
}
bool ret = false;
int idx = 0;
while (1) {
dev = devList[idx];
idx++;
if (dev == NULL)
break;
uvc_device_descriptor_t *desc;
res = uvc_get_device_descriptor(dev, &desc);
if (res < 0) {
qWarning() << "uvc_get_device_descriptor" << uvc_strerror(res);
return false;
}
QString product(desc->product);
uvc_free_device_descriptor(desc);
if (product.compare(device) == 0) {
deviceName = device;
ret = true;
break;
}
}
uvc_free_device_list(devList, 0);
return ret;
}
void UVCCameraSession::setSurface(QAbstractVideoSurface* surface)
......@@ -67,14 +118,14 @@ void UVCCameraSession::presentFrame(QVideoFrame frame, const qreal t)
qreal latency = frameReference.elapsed() - t;
frame.setMetaData("timestamp", t);
if (latency <= MAX_LATENCY_MS && m_surface)
m_surface->present(frame);
m_surface->present(frame);
//else
// qWarning() << "Dropping frame (" << latency << "ms old )";
}
bool UVCCameraSession::load()
{
QMutexLocker sessionLocker(&sessionMutex);
QMutexLocker sessionLocker(&sessionMutex);
uvc_error_t res;
if (!dev)
......@@ -111,12 +162,13 @@ bool UVCCameraSession::unload()
QMutexLocker sessionLocker(&sessionMutex);
if (streaming)
stopPreview();
stopPreview();
#ifdef _WIN32
// TODO: closing here seems to mess up the device
//if (devh)
// uvc_close(devh);
//devh = NULL;
//if (devh)
// uvc_close(devh);
devh = NULL;
#else
// For linux it seems to work as expected
if (devh)
......@@ -136,7 +188,7 @@ void concurrentCustomCallback(UVCCameraSession *session)
continue;
uvc_frame_t *frame = NULL;
uvc_error_t res;
res = uvc_stream_get_frame(session->strmh, &frame, 0);
res = uvc_stream_get_frame(session->strmh, &frame, 0);
if ( res == UVC_SUCCESS && frame != NULL)
session->callback(frame);
else
......@@ -265,7 +317,8 @@ bool UVCCameraSession::stopPreview()
break;
if (res != UVC_SUCCESS && res != UVC_ERROR_INVALID_PARAM)
qWarning() << "uvc_stream_stop" << uvc_strerror(res);
uvc_stream_close(strmh);
uvc_stream_close(strmh);
strmh = NULL;
frameReference.invalidate();
return true;
......@@ -310,3 +363,19 @@ bool UVCCameraSession::isPupilEyeCamera()
uvc_free_device_descriptor(desc);
return ret;
}
bool UVCCameraSession::acquire(const QString &device)
{
QMutexLocker locker(&devicesMutex);
if (devices.contains(device))
return false;
devices[device] = true;
return true;
}
void UVCCameraSession::release(const QString &device)
{
QMutexLocker locker(&devicesMutex);
if (devices.contains(device))
devices.remove(device);
}
......@@ -41,7 +41,7 @@ public:
QCameraViewfinderSettings viewfinderSettings();
void setViewfinderSettings(const QCameraViewfinderSettings &settings);
void setDevice(uvc_device_t *device);
bool setDevice(const QString &device);
bool load();
bool unload();
......@@ -52,7 +52,7 @@ public:
bool streaming;
uvc_stream_handle_t *strmh;
QMutex getFrameMutex;
double interval;
double interval;
private Q_SLOTS:
void presentFrame(QVideoFrame frame, const qreal t);
......@@ -61,10 +61,12 @@ private:
float bandwidthFactor;
QCamera::Status m_status;
QAbstractVideoSurface *m_surface;
QMutex surfaceMutex;
QMutex surfaceMutex;
QString deviceName;
// Source (camera)
uvc_device_t *dev;
uvc_context_t* ctx;
uvc_device_t *dev;
uvc_device_handle_t *devh;
uvc_stream_ctrl_t ctrl;
......@@ -83,7 +85,13 @@ private:
static QMutex sessionMutex;
bool isPupilFieldCamera();
bool isPupilEyeCamera();
bool isPupilEyeCamera();
// device availability
static QMap<QString,bool> UVCCameraSession::devices;
static QMutex UVCCameraSession::devicesMutex;
bool acquire(const QString &device);
void release(const QString &device);
};
......
......@@ -39,9 +39,9 @@ unix {
# Dependencies
win32:{
LIBUSBPATH = "C:/Users/santini/repo/libusb/"
LIBUVCPATH = "C:/Users/santini/repo/libuvc/"
LIBPTHREADSPATH = "C:/Users/santini/repo/libuvc/pthreads-2.9.1/"
LIBUSBPATH = "C:/Users/santini/repo/libusb/"
LIBUVCPATH = "C:/Users/santini/repo/libuvc/"
LIBPTHREADSPATH = "C:/Users/santini/repo/libuvc/pthreads-2.9.1/"
# Libraries required to access pupil lab's cameras
# LIBUSB-1.0
......@@ -56,8 +56,9 @@ win32:{
LIBS += -llibusb-1.0
# LIBUVC
INCLUDEPATH += "$${LIBUVCPATH}/install/include/"
contains(QMAKE_HOST.arch, x86_64) {
INCLUDEPATH += "$${LIBUVCPATH}/include/"
INCLUDEPATH += "$${LIBUVCPATH}/build/include/"
contains(QMAKE_HOST.arch, x86_64) {
Release:LIBS += "-L$${LIBUVCPATH}/build/Release/"
Debug:LIBS += "-L$${LIBUVCPATH}/build/Debug"
} else {
......
......@@ -4,23 +4,24 @@ QT_BEGIN_NAMESPACE
Q_GLOBAL_STATIC(QList<UVCVideoDeviceInfo>, deviceList)
// this context may never get deleted. Is that a problem?
uvc_context_t* UVCVideoDeviceControl::ctx = NULL;
QMap<QString,bool> UVCVideoDeviceControl::busyDevices;
bool UVCVideoDeviceControl::loaded = false;
UVCVideoDeviceControl::UVCVideoDeviceControl(QObject *parent)
: QVideoDeviceSelectorControl(parent),
currentDevice(""),
: QVideoDeviceSelectorControl(parent),
ctx(NULL),
selected(0)
{
m_session = qobject_cast<UVCCameraSession*>(parent);
updateDevices();
m_session = qobject_cast<UVCCameraSession*>(parent);
updateDevices();
if (!loaded) {
loaded = true;
qInfo() << "Loaded uvcengine!";
}
}
UVCVideoDeviceControl::~UVCVideoDeviceControl()
{
busyDevices.remove(currentDevice);
currentDevice.clear();
}
int UVCVideoDeviceControl::deviceCount() const
......@@ -66,44 +67,9 @@ void UVCVideoDeviceControl::setSelectedDevice(int index)
if (index >= 0 && index < deviceList->count()) {
if (m_session) {
QString device = deviceList->at(index).first;
uvc_device_t **devList;
uvc_error_t res;
res = uvc_get_device_list(ctx, &devList);
if (res < 0) {
qWarning() << "uvc_get_device_list" << uvc_strerror(res);
return;
}
int idx = 0;
while (1) {
uvc_device_t *dev = devList[idx];
idx++;
if (dev == NULL)
break;
uvc_device_descriptor_t *desc;
res = uvc_get_device_descriptor(dev, &desc);
if (res < 0) {
qWarning() << "uvc_get_device_descriptor" << uvc_strerror(res);
return;
}
bool selectedDevice = QString(desc->product).compare(device) == 0;
uvc_free_device_descriptor(desc);
if (selectedDevice) {
if (!busyDevices.contains(device)) {
busyDevices[device] = true;
currentDevice = device;
m_session->setDevice(dev);
} else {
qWarning() << "Device is busy.";
}
break;
}
}
uvc_free_device_list(devList, 0);
if ( m_session->setDevice(device) )
selected = index;
}
selected = index;
}
}
......@@ -117,23 +83,18 @@ void UVCVideoDeviceControl::updateDevices()
{
static QElapsedTimer timer;
if (timer.isValid() && timer.elapsed() < 500) // ms
return;
return;
deviceList->clear();
uvc_error_t res;
if (!ctx) {
// Init libuvc
qInfo() << "Initializing libuvc context...";
res = uvc_init(&ctx, NULL);
if (res < 0) {
qWarning() << "uvc_init" << uvc_strerror(res);
return;
}
}
uvc_device_t **devList;
uvc_error_t res;
uvc_context_t *ctx;
res = uvc_init(&ctx, NULL);
if (res < 0) {
qWarning() << "uvc_init" << uvc_strerror(res);
return ;
}
uvc_device_t **devList;
res = uvc_get_device_list(ctx, &devList);
if (res < 0) {
qWarning() << "uvc_get_device_list" << uvc_strerror(res);
......@@ -166,7 +127,8 @@ void UVCVideoDeviceControl::updateDevices()
uvc_free_device_descriptor(desc);
}
uvc_free_device_list(devList, 0);
uvc_free_device_list(devList, 0);
uvc_exit(ctx);
timer.restart();
}
......@@ -27,19 +27,18 @@ public:
int defaultDevice() const;
int selectedDevice() const;
static const QList<UVCVideoDeviceInfo> &availableDevices();
static const QList<UVCVideoDeviceInfo> &availableDevices();
public Q_SLOTS:
void setSelectedDevice(int index);
private:
static void updateDevices();
static void updateDevices();
UVCCameraSession* m_session;
int selected;
static uvc_context_t* ctx;
static QMap<QString,bool> busyDevices;
QString currentDevice;
uvc_context_t* ctx;
static bool loaded;
};
QT_END_NAMESPACE
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment