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

5 6
QMutex UVCCameraSession::sessionMutex;

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

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

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

void UVCCameraSession::setSurface(QAbstractVideoSurface* surface)
{
    m_surface = surface;
}

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

    if (!streaming)
        return;

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

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

bool UVCCameraSession::load()
{
75
    QMutexLocker sessionLocker(&sessionMutex);
Thiago C. Santini's avatar
Thiago C. Santini committed
76 77
    uvc_error_t res;

78 79 80
    if (!dev)
        return false;

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

    // TODO: parametrize this
    uvc_set_focus_auto(devh, 0);
90 91 92 93 94 95 96 97 98 99 100
    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 C. Santini's avatar
Thiago C. Santini committed
101 102 103 104 105 106 107 108

    updateSourceCapabilities();

    return true;
}

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

Thiago C. Santini's avatar
Thiago C. Santini committed
111 112 113
    if (streaming)
        stopPreview();

114 115 116 117
    // TODO: closing here seems to mess up the device
    //if (devh)
    //    uvc_close(devh);
    //devh = NULL;
Thiago C. Santini's avatar
Thiago C. Santini committed
118 119 120 121 122

    m_supportedViewfinderSettings.clear();
    return true;
}

123
void concurrentCustomCallback(UVCCameraSession *session)
124
{
125
    QMutexLocker getFrameLocker( &session->getFrameMutex );
126 127 128 129 130 131 132 133 134
    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
135 136
            if (res != UVC_ERROR_TIMEOUT)
                qWarning() << session->streaming << uvc_strerror(res) << frame;
137 138 139
    }
}

140 141
// Custom callback is experimental; avoid it for the moment.
// Just here for debugging a libusb bug
142
//#define USE_CUSTOM_CALLBACK
Thiago C. Santini's avatar
Thiago C. Santini committed
143 144 145 146 147 148 149
bool UVCCameraSession::startPreview()
{
    uvc_error_t res;

    if (streaming)
        stopPreview();

150
    QMutexLocker getFrameLocker( &getFrameMutex );
Thiago C. Santini's avatar
Thiago C. Santini committed
151 152 153
    uvc_frame_format format;

    if (!qPixelFormat2UVCFrameFormat(settings.pixelFormat(), format)) {
154
        qWarning() << "Unknown pixel format";
Thiago C. Santini's avatar
Thiago C. Santini committed
155 156 157 158
        return false;
    }

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

Thiago C. Santini's avatar
Thiago C. Santini committed
160
    if (res != UVC_SUCCESS) {
161
        qWarning() << "uvc_get_stream_ctrl" << uvc_strerror(res);
Thiago C. Santini's avatar
Thiago C. Santini committed
162 163 164 165 166
        return false;
    }

    res = uvc_stream_open_ctrl(devh, &strmh, &ctrl);
    if (res != UVC_SUCCESS) {
167
        qWarning() << "uvc_stream_open_ctrl" << uvc_strerror(res);
Thiago C. Santini's avatar
Thiago C. Santini committed
168 169 170
        return false;
    }

171 172 173
#ifdef USE_CUSTOM_CALLBACK
    res = uvc_stream_start(strmh, NULL, (void*) this, bandwidthFactor, 0);
#else
174
    res = uvc_stream_start(strmh, cb, (void*) this, bandwidthFactor, 0);
175
#endif
Thiago C. Santini's avatar
Thiago C. Santini committed
176
    if (res != UVC_SUCCESS) {
177
        qWarning() << "uvc_stream_start" << uvc_strerror(res);
Thiago C. Santini's avatar
Thiago C. Santini committed
178 179 180 181 182
        return false;
    }

    streaming = true;
    frameReference.restart();
183 184

#ifdef USE_CUSTOM_CALLBACK
185
    QtConcurrent::run(concurrentCustomCallback, this);
186
#endif
Thiago C. Santini's avatar
Thiago C. Santini committed
187 188 189 190 191 192 193 194 195
    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;
196 197 198
        // TODO: support for YUYV. See:
        // 		bDescriptorSubtype (UVC_VS_FORMAT_UNCOMPRESSED)
        //		guidFormat
Thiago C. Santini's avatar
Thiago C. Santini committed
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
        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);
229
            settings.setPixelAspectRatio(formatDesc->bAspectRatioX,formatDesc->bAspectRatioY);
Thiago C. Santini's avatar
Thiago C. Santini committed
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
            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;
252
    streaming = false;
Thiago C. Santini's avatar
Thiago C. Santini committed
253 254 255 256 257 258

    // 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)
259
        qWarning() << "uvc_stream_stop" << uvc_strerror(res);
260
    uvc_stream_close(strmh);
Thiago C. Santini's avatar
Thiago C. Santini committed
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278

    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;
279
    interval = 1 / settings.maximumFrameRate();
Thiago C. Santini's avatar
Thiago C. Santini committed
280
}
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304

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