uvccamerasession.cpp 8.11 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)
Thiago Santini's avatar
Thiago Santini committed
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
      dev(NULL),
Thiago C. Santini's avatar
Thiago C. Santini committed
13 14 15 16 17 18
      devh(NULL)
{
}

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

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

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

32 33
// 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
34 35 36 37 38 39 40 41 42 43 44 45
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:
46
                qFrame = QVideoFrame( (int) frame->data_bytes, QSize(frame->width, frame->height), 0, QVideoFrame::Format_Jpeg);
Thiago C. Santini's avatar
Thiago C. Santini committed
47 48 49 50 51 52 53
                qFrame.map(QAbstractVideoBuffer::WriteOnly);
                memcpy( qFrame.bits(), frame->data, frame->data_bytes); // copied; safe to retun from callback now
                qFrame.unmap();
                break;
            default:
                return;
        }
54 55 56 57 58
#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
59 60 61
    }
}

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

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

79 80 81
    if (!dev)
        return false;

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

    // TODO: parametrize this
    uvc_set_focus_auto(devh, 0);
91 92 93 94 95 96 97 98 99 100 101
    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
102 103 104 105 106 107 108 109

    updateSourceCapabilities();

    return true;
}

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

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

Thiago Santini's avatar
Thiago Santini committed
115 116 117 118
    // 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
119 120 121 122 123

    m_supportedViewfinderSettings.clear();
    return true;
}

124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
void customCallback(UVCCameraSession *session)
{
    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
            qWarning() << uvc_strerror(res) << frame;
    }
}

//#define USE_CUSTOM_CALLBACK
Thiago C. Santini's avatar
Thiago C. Santini committed
140 141 142 143 144 145 146 147 148 149
bool UVCCameraSession::startPreview()
{
    uvc_error_t res;

    if (streaming)
        stopPreview();

    uvc_frame_format format;

    if (!qPixelFormat2UVCFrameFormat(settings.pixelFormat(), format)) {
150
        qWarning() << "Unknown pixel format";
Thiago C. Santini's avatar
Thiago C. Santini committed
151 152 153 154
        return false;
    }

    res = uvc_get_stream_ctrl_format_size(devh, &ctrl, format, settings.resolution().width(), settings.resolution().height(), settings.maximumFrameRate());
Thiago Santini's avatar
Thiago Santini committed
155

Thiago C. Santini's avatar
Thiago C. Santini committed
156
    if (res != UVC_SUCCESS) {
157
        qWarning() << "uvc_get_stream_ctrl" << uvc_strerror(res);
Thiago C. Santini's avatar
Thiago C. Santini committed
158 159 160 161 162
        return false;
    }

    res = uvc_stream_open_ctrl(devh, &strmh, &ctrl);
    if (res != UVC_SUCCESS) {
163
        qWarning() << "uvc_stream_open_ctrl" << uvc_strerror(res);
Thiago C. Santini's avatar
Thiago C. Santini committed
164 165 166
        return false;
    }

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

    streaming = true;
    frameReference.restart();
179 180 181 182

#ifdef USE_CUSTOM_CALLBACK
    QtConcurrent::run(customCallback, this);
#endif
Thiago C. Santini's avatar
Thiago C. Santini committed
183 184 185 186 187 188 189 190 191
    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;
192 193 194
        // TODO: support for YUYV. See:
        // 		bDescriptorSubtype (UVC_VS_FORMAT_UNCOMPRESSED)
        //		guidFormat
Thiago C. Santini's avatar
Thiago C. Santini committed
195 196 197 198 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
        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);
225
            settings.setPixelAspectRatio(formatDesc->bAspectRatioX,formatDesc->bAspectRatioY);
Thiago C. Santini's avatar
Thiago C. Santini committed
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
            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;
248
    streaming = false;
Thiago C. Santini's avatar
Thiago C. Santini committed
249 250 251 252 253 254

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

    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;
}
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299

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