uvccamerasession.cpp 8.64 KB
Newer Older
Thiago Santini's avatar
Thiago Santini committed
1
#include "uvccamerasession.h"
2 3
#include <QThread>
#include <QtConcurrent/QtConcurrent>
Thiago Santini's avatar
Thiago Santini committed
4

5 6
QMutex UVCCameraSession::sessionMutex;

Thiago Santini's avatar
Thiago Santini committed
7
UVCCameraSession::UVCCameraSession(QObject *parent)
8 9
    : QObject(parent),
      m_surface(Q_NULLPTR),
Thiago Santini's avatar
Thiago Santini committed
10
      streaming(false),
11
      bandwidthFactor(1.3f),
12
      interval(0),
13
      dev(NULL),
Thiago Santini's avatar
Thiago Santini committed
14 15 16 17 18 19
      devh(NULL)
{
}

UVCCameraSession::~UVCCameraSession()
{
Thiago Santini's avatar
Thiago Santini committed
20
    unload();
Thiago Santini's avatar
Thiago Santini committed
21 22 23 24 25 26 27 28 29
}

void UVCCameraSession::setDevice(uvc_device_t *device)
{
    dev = device;
}

void UVCCameraSession::setSurface(QAbstractVideoSurface* surface)
{
30
    QMutexLocker locker(&surfaceMutex);
Thiago Santini's avatar
Thiago Santini committed
31 32 33
    m_surface = surface;
}

34 35
// If the following is defined, frame grabbing is performed by the callback thread
//#define BLOCKING_FRAME_GRABBING
36
void cb(uvc_frame_t *frame, void *ptr) { if (ptr && frame) static_cast<UVCCameraSession*>(ptr)->callback(frame); }
Thiago Santini's avatar
Thiago Santini committed
37 38 39 40 41 42 43
void UVCCameraSession::callback(uvc_frame_t *frame)
{
    qreal t = frameReference.elapsed();

    if (!streaming)
        return;

44
    QMutexLocker locker(&surfaceMutex);
Thiago Santini's avatar
Thiago Santini committed
45 46 47 48
    if (m_surface) {
        QVideoFrame qFrame;
        switch(frame->frame_format) {
            case UVC_FRAME_FORMAT_MJPEG:
49
                qFrame = QVideoFrame( (int) frame->data_bytes, QSize(frame->width, frame->height), 0, QVideoFrame::Format_Jpeg);
Thiago Santini's avatar
Thiago Santini committed
50 51 52 53 54 55 56
                qFrame.map(QAbstractVideoBuffer::WriteOnly);
                memcpy( qFrame.bits(), frame->data, frame->data_bytes); // copied; safe to retun from callback now
                qFrame.unmap();
                break;
            default:
                return;
        }
57 58 59
#ifdef BLOCKING_FRAME_GRABBING
        m_surface->present( qFrame );
#else
60
        QMetaObject::invokeMethod(this, "presentFrame", Qt::QueuedConnection, Q_ARG(const QVideoFrame&, qFrame), Q_ARG(const qreal, t) );
61
#endif
Thiago Santini's avatar
Thiago Santini committed
62 63 64
    }
}

65
void UVCCameraSession::presentFrame(QVideoFrame frame, const qreal t)
Thiago Santini's avatar
Thiago Santini committed
66 67
{
    qreal latency = frameReference.elapsed() - t;
68
    frame.setMetaData("timestamp", t);
69
    if (latency <= MAX_LATENCY_MS && m_surface)
70
        m_surface->present(frame);
71
    //else
72
    //    qWarning() << "Dropping frame (" << latency << "ms old )";
Thiago Santini's avatar
Thiago Santini committed
73 74 75 76
}

bool UVCCameraSession::load()
{
77
    QMutexLocker sessionLocker(&sessionMutex);
Thiago Santini's avatar
Thiago Santini committed
78 79
    uvc_error_t res;

80 81 82
    if (!dev)
        return false;

Thiago Santini's avatar
Thiago Santini committed
83 84
    res = uvc_open(dev, &devh);
    if (res != UVC_SUCCESS) {
85
        qWarning() << "uvc_open" << uvc_strerror(res);
Thiago Santini's avatar
Thiago Santini committed
86 87 88 89 90 91
        devh = NULL;
        return false;
    }

    // TODO: parametrize this
    uvc_set_focus_auto(devh, 0);
92 93 94 95 96 97 98 99 100 101 102
    if (isPupilFieldCamera()) {
        bandwidthFactor = 2.0f;
        uvc_set_ae_priority(devh, 1);
    } else if (isPupilEyeCamera()) {
        uvc_set_ae_priority(devh, 0);
        uvc_set_ae_mode(devh, 1);
        uvc_set_saturation(devh, 127); // do not set saturation to zero as to not lose color information
        uvc_set_exposure_abs(devh, 63);
        uvc_set_backlight_compensation(devh, 2);
        uvc_set_gamma(devh, 100);
    }
Thiago Santini's avatar
Thiago Santini committed
103 104 105 106 107 108 109 110

    updateSourceCapabilities();

    return true;
}

bool UVCCameraSession::unload()
{
111
    QMutexLocker sessionLocker(&sessionMutex);
Thiago Santini's avatar
Thiago Santini committed
112

Thiago Santini's avatar
Thiago Santini committed
113 114
    if (streaming)
        stopPreview();
115
#ifdef _WIN32
116 117
    // TODO: closing here seems to mess up the device
    //if (devh)
118
    //   uvc_close(devh);
119
    //devh = NULL;
120 121 122 123 124 125
#else
    // For linux it seems to work as expected
    if (devh)
        uvc_close(devh);
    devh = NULL;
#endif
Thiago Santini's avatar
Thiago Santini committed
126 127 128 129 130

    m_supportedViewfinderSettings.clear();
    return true;
}

131
void concurrentCustomCallback(UVCCameraSession *session)
132
{
133
    QMutexLocker getFrameLocker( &session->getFrameMutex );
134 135 136 137 138 139 140 141 142
    while (session->streaming) {
        if (session->strmh == NULL)
            continue;
        uvc_frame_t *frame = NULL;
        uvc_error_t res;
        res = uvc_stream_get_frame(session->strmh, &frame, 0);
        if ( res == UVC_SUCCESS && frame != NULL)
            session->callback(frame);
        else
143 144
            if (res != UVC_ERROR_TIMEOUT)
                qWarning() << session->streaming << uvc_strerror(res) << frame;
145 146 147
    }
}

148 149
// Custom callback is experimental; avoid it for the moment.
// Just here for debugging a libusb bug
150
//#define USE_CUSTOM_CALLBACK
Thiago Santini's avatar
Thiago Santini committed
151 152 153 154 155 156 157
bool UVCCameraSession::startPreview()
{
    uvc_error_t res;

    if (streaming)
        stopPreview();

158
    QMutexLocker getFrameLocker( &getFrameMutex );
Thiago Santini's avatar
Thiago Santini committed
159 160 161
    uvc_frame_format format;

    if (!qPixelFormat2UVCFrameFormat(settings.pixelFormat(), format)) {
162
        qWarning() << "Unknown pixel format";
Thiago Santini's avatar
Thiago Santini committed
163 164 165 166
        return false;
    }

    res = uvc_get_stream_ctrl_format_size(devh, &ctrl, format, settings.resolution().width(), settings.resolution().height(), settings.maximumFrameRate());
167

Thiago Santini's avatar
Thiago Santini committed
168
    if (res != UVC_SUCCESS) {
169
        qWarning() << "uvc_get_stream_ctrl" << uvc_strerror(res);
Thiago Santini's avatar
Thiago Santini committed
170 171 172 173 174
        return false;
    }

    res = uvc_stream_open_ctrl(devh, &strmh, &ctrl);
    if (res != UVC_SUCCESS) {
175
        qWarning() << "uvc_stream_open_ctrl" << uvc_strerror(res);
Thiago Santini's avatar
Thiago Santini committed
176 177 178
        return false;
    }

179 180 181
#ifdef USE_CUSTOM_CALLBACK
    res = uvc_stream_start(strmh, NULL, (void*) this, bandwidthFactor, 0);
#else
182
    res = uvc_stream_start(strmh, cb, (void*) this, bandwidthFactor, 0);
183
#endif
Thiago Santini's avatar
Thiago Santini committed
184
    if (res != UVC_SUCCESS) {
185
        qWarning() << "uvc_stream_start" << uvc_strerror(res);
Thiago Santini's avatar
Thiago Santini committed
186 187 188 189 190
        return false;
    }

    streaming = true;
    frameReference.restart();
191 192

#ifdef USE_CUSTOM_CALLBACK
193
    QtConcurrent::run(concurrentCustomCallback, this);
194
#endif
Thiago Santini's avatar
Thiago Santini committed
195 196 197 198 199 200 201 202 203
    return true;
}

bool UVCCameraSession::qPixelFormat2UVCFrameFormat(const QVideoFrame::PixelFormat &qFormat, uvc_frame_format &uvcFormat)
{
    switch(qFormat) {
        case QVideoFrame::Format_Jpeg:
            uvcFormat = UVC_FRAME_FORMAT_MJPEG;
            return true;
204 205 206
        // TODO: support for YUYV. See:
        // 		bDescriptorSubtype (UVC_VS_FORMAT_UNCOMPRESSED)
        //		guidFormat
Thiago Santini's avatar
Thiago Santini committed
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
        default:
            return false;
    }
}

void UVCCameraSession::updateSourceCapabilities()
{
    m_supportedViewfinderSettings.clear();

    if (!devh)
        return;

    const uvc_format_desc_t* formatDesc = uvc_get_format_descs(devh);
    while (formatDesc != NULL) {

        QVideoFrame::PixelFormat qFormat;
        switch (formatDesc->bDescriptorSubtype) {
            case UVC_VS_FORMAT_MJPEG:
                qFormat = QVideoFrame::Format_Jpeg;
                break;
            default: // format not supported, next!
                formatDesc = formatDesc->next;
                continue;
        }

        const uvc_frame_desc *frameDesc = formatDesc->frame_descs;
        while (frameDesc!=NULL) {
            QCameraViewfinderSettings settings;
            settings.setResolution(frameDesc->wWidth, frameDesc->wHeight);
            settings.setPixelAspectRatio(frameDesc->wWidth, frameDesc->wHeight);
237
            settings.setPixelAspectRatio(formatDesc->bAspectRatioX,formatDesc->bAspectRatioY);
Thiago Santini's avatar
Thiago Santini committed
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
            settings.setPixelFormat(qFormat);

            uint32_t *intervals = frameDesc->intervals;
            while (*intervals) {
                // libuvc uses 100ns units
                double fps = 1.0e7 / (*intervals);
                settings.setMinimumFrameRate( fps );
                settings.setMaximumFrameRate( fps );
                m_supportedViewfinderSettings.push_back(settings);
                intervals++;
            }

            frameDesc = frameDesc->next;
        }

        formatDesc = formatDesc->next;
    }
}

bool UVCCameraSession::stopPreview()
{
    uvc_error_t res;
260
    streaming = false;
Thiago Santini's avatar
Thiago Santini committed
261 262 263 264 265 266

    // for some reason libuvc seems to timeout on uvc_stream_stop
    for (unsigned int i=0; i<100;i++)
        if ( (res = uvc_stream_stop(strmh)) != UVC_ERROR_TIMEOUT)
            break;
    if (res != UVC_SUCCESS && res != UVC_ERROR_INVALID_PARAM)
267
        qWarning() << "uvc_stream_stop" << uvc_strerror(res);
268
    uvc_stream_close(strmh);
Thiago Santini's avatar
Thiago Santini committed
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286

    frameReference.invalidate();
    return true;
}

QList<QCameraViewfinderSettings> UVCCameraSession::supportedViewfinderSettings()
{
    return m_supportedViewfinderSettings;
}

QCameraViewfinderSettings UVCCameraSession::viewfinderSettings()
{
    return QCameraViewfinderSettings();
}

void UVCCameraSession::setViewfinderSettings(const QCameraViewfinderSettings &settings)
{
    this->settings = settings;
287
    interval = 1 / settings.maximumFrameRate();
Thiago Santini's avatar
Thiago Santini committed
288
}
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312

bool UVCCameraSession::isPupilFieldCamera()
{
    bool ret = false;
    uvc_device_descriptor *desc;
    uvc_get_device_descriptor(dev, &desc);
    QString name(desc->product);
    if ( name.contains("Pupil Cam1 ID2") )
        ret = true;
    uvc_free_device_descriptor(desc);
    return ret;
}

bool UVCCameraSession::isPupilEyeCamera()
{
    bool ret = false;
    uvc_device_descriptor *desc;
    uvc_get_device_descriptor(dev, &desc);
    QString name(desc->product);
    if ( name.contains("Pupil Cam1 ID0") || name.contains("Pupil Cam1 ID1") )
        ret = true;
    uvc_free_device_descriptor(desc);
    return ret;
}