# Capturando secuencias de vídeo con Qt

[Qt](/proyecto-qt-framework-de-desarrollo-de-aplicaciones/) incluye el módulo [Qt Multimedia](https://doc.qt.io//qt-5/qtmultimedia-index.html) para facilitar la manipulación de contenidos multimedia. Entre otras cosas permite reproducir audio y vídeo y capturar desde dispositivos de adquisición soportados por el sistema operativo.

Como por defecto no viene activado, es necesario abrir el archivo `.pro` del proyecto y añadir la línea

```plaintext
QT += multimedia multimediawidgets
```

# Primeros pasos con la webcam

Capturar de una webcam es tan sencillo como crear un objeto [QCamera](https://doc.qt.io/qt-5/qcamera.html) e iniciar la captura mediante el método [QCamera](https://doc.qt.io/qt-5/qcamera.html)::[start](https://doc.qt.io/qt-5/qcamera.html#start)():

```cpp
QCamera* camera = new QCamera;
camera->start();
```

Por lo general suele ser interesante incorporar un visor en el que mostrar al usuario lo que la cámara está capturando. Para simplificar esta tarea, [Qt](/proyecto-qt-framework-de-desarrollo-de-aplicaciones/) nos ofrece el control [QCameraViewfinder](https://doc.qt.io/qt-5/qcameraviewfinder.html) que hereda del más genérico [QVideoWidget](https://doc.qt.io/qt-5/qvideowidget.html):

```cpp
QCamera* camera = new QCamera;
QCameraViewfinder* viewfinder = new QCameraViewfinder;
camera->setViewfinder(viewfinder);
```

Como [QCameraViewfinder](https://doc.qt.io/qt-5/qcameraviewfinder.html) —al igual que otros controles de [Qt Multimedia](https://doc.qt.io/qt-5/qtmultimedia-index.html)— no está disponible en Qt Designer, tendremos que colocarlo en nuestra ventana desde el propio código. Por ejemplo como el control central de la misma:

```cpp
viewfinder->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
setCentralWidget(viewfinder);
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1635808447217/v3CuQQTGO.png align="left")

Hecho esto, finalmente, podemos iniciar la cámara:

```cpp
// camera->setCaptureMode(QCamera::CaptureVideo);
camera->setCaptureMode(QCamera::CaptureViewfinder);
camera->start();
```

# Acceder a un dispositivo específico

En un mismo sistema pueden haber varios dispositivos de captura, de tal forma que puede ser necesario crear un objeto [QCamera](https://doc.qt.io/qt-5/qcamera.html) para un dispositivo concreto. En ese caso simplemente tenemos que indicarlo en el constructor:

```cpp
QCamera* camera = new QCamera("/dev/video0");
```

Obviamente para hacerlo necesitamos conocer la lista de los dispositivos disponibles en el sistema. Esto se puede hacer a través del método [QCamera](https://doc.qt.io/qt-5/qcamera.html)::[availableDevices](https://doc.qt.io/qt-5/qcamera.html#availableDevices)(). Al igual que se puede obtener para cada uno de ellos un texto más descriptivo, principalmente de cara a los usuarios, usando [QCamera](https://doc.qt.io/qt-5/qcamera.html)::[deviceDescription](https://doc.qt.io/qt-5/qcamera.html#deviceDescription)():

```cpp
QList devices = QCamera::availableDevices();
qDebug() << "Capturando de... "
         << QCamera::deviceDescription(devices[0]);
QCamera* camera = new QCamera(devices[0]);
```

# Accediendo a los frames individualmente

En el módulo [Qt Multimedia](https://doc.qt.io/qt-5/qtmultimedia-index.html) cada objeto de la clase [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html) representa un *frame* de vídeo.

## La clase QVideoFrame

El motivo por el que no se usa para esto la clase [QImage](https://doc.qt.io/qt-5/qimage.html) es porque [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html) no siempre contiene los datos de los píxeles del *frame*, como sí ocurre con la clase [QImage](https://doc.qt.io/qt-5/qimage.html).

Cada sistema operativo tiene su propio API multimedia que tiene una manera particular de gestionar los *buffers* de memoria —por ejemplo, como una textura en OpenGL, como un *buffer* de memoria compartida en XVideo, como una CImage en MacOS X, etc.— . Estos *buffers* suelen ser internos al API, así que las aplicaciones los identifican a través de manejadores o *handlers*. Copiar los datos de los píxeles desde los *buffers* internos a la memoria del programa suele tener cierto coste, por lo que [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html) evita hacerlo, a menos que el programador lo solicite. Esto tiene una serie de implicaciones para nuestros fines:

* Por lo general [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html) no contiene los datos de los píxeles sino un manejador al *buffer* interno del API donde están almacenados. Dicho manejador se puede obtener a través del método [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html)::[handle](https://doc.qt.io/qt-5/qvideoframe.html#handle)().
    
* Para conocer el tipo de manejador —algo que depende del API nativo que esté usando el módulo [Qt Multimedia](https://doc.qt.io/qt-5/qtmultimedia-index.html)— se puede usar el método [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html)::[handleType](https://doc.qt.io/qt-5/qvideoframe.html#handletype)().
    
* Para acceder al contenido de los píxeles se puede usar el método [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html)::[bits](https://doc.qt.io/qt-5/qvideoframe.html#bits)() pero primero hay que asegurarse de que dichos datos son copiados desde el *buffer* interno a la memoria del programa. Eso se hace invocando previamente el método [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html)::[map](https://doc.qt.io/qt-5/qvideoframe.html#map)(). Cuando el acceso a estos datos ya no es necesario se debe usar el método [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html)::[unmap](https://doc.qt.io/qt-5/qvideoframe.html#unmap)() para liberar la memoria.
    
* El contenido de los objetos [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html) se comparte explícitamente, por lo que cualquier cambio en un *frame* será visible en todas las copias.
    

## Ganar acceso a los frames

Si estamos interesados en procesar los *frames* capturados, la forma más sencilla —aunque no la única— es crear nuestro propio visor para la cámara. El método [QCamera](https://doc.qt.io/qt-5/qcamera.html)::[setViewfinder](https://doc.qt.io/qt-5/qcamera.html#setViewfinder)() admite un puntero a objetos de la clase [QAbstractVideoSurface](https://doc.qt.io/qt-5/qabstractvideosurface.html), que define la interfaz genérica para las superficies que saben como mostrar vídeo. Por lo que será esa la clase de la que heredaremos la nuestra:

```c++
class CaptureBuffer : public QAbstractVideoSurface
{
    Q_OBJECT
    
public:

    QList<QVideoFrame::PixelFormat> supportedPixelFormats(
            QAbstractVideoBuffer::HandleType handleType =
            QAbstractVideoBuffer::NoHandle) const
    {
        // A través de este método nos preguntan que formatos de
        // vídeo soportamos. Como vamos a guardar los frames en
        // objetos QImage nos sirve cualquiera de los formatos
        // sorportados por dicha clase: http://kcy.me/z6pa
        QList<QVideoFrame::PixelFormat> formats;
        formats << QVideoFrame::Format_ARGB32;
        formats << QVideoFrame::Format_ARGB32_Premultiplied;
        formats << QVideoFrame::Format_RGB32;
        formats << QVideoFrame::Format_RGB24;
        formats << QVideoFrame::Format_RGB565;
        formats << QVideoFrame::Format_RGB555;
        return formats;
    }

    bool present(const QVideoFrame &frame)
    {
        // A través de este método nos darán el frame para que
        // lo mostremos.
        return true;
    }
};
```

De tal forma que sólo tenemos que instanciarla y configurarla como visor de nuestra cámara.

```cpp
CaptureBuffer* captureBuffer = new CaptureBuffer;
camera->setViewfinder(captureBuffer);
```

## Convertir objetos QVideoFrame en QImage

A través del método `present()` de nuestra clase `CaptureBuffer` obtenemos los *frames* capturados. Sin embargo estos son de poca utilidad si no podemos acceder al contenido de los píxeles. Así que vamos a convertir el objeto [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html) en un objeto [QImage](https://doc.qt.io/qt-5/qimage.html).

Como ya sabemos, [QImage](https://doc.qt.io/qt-5/qimage.html) tiene diversos constructores. Entre ellos el siguiente:

```cpp
QImage (const uchar *buffer, int width, int height,
        int bytesPerLine, QImage::Format format)
```

que permite crear un objeto [QImage](https://doc.qt.io/qt-5/qimage.html) si tenemos:

* El ancho —`width`— y el alto —`height`— del frame. Esto lo podemos obtener a través de los métodos [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html)::[width](https://doc.qt.io/qt-5/qvideoframe.html#width)() y [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html)::[width](https://doc.qt.io/qt-5/qvideoframe.html#width)().
    
* El número de bytes por línea —`bytesPerLine`— . Para lo que tenemos el método [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html)::[bytesPerLine](https://doc.qt.io/qt-5/qvideoframe.html#bytesperline)().
    
* El formato de los píxeles —`format`—. En este caso el método [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html)::[pixelFormat](https://doc.qt.io/qt-5/qvideoframe.html#pixelformat)() proporciona dicho formato para el *frame* y podemos convertirlo en uno equivalente de [QImage](https://doc.qt.io/qt-5/qimage.html) usando [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html)::[imageFormatFromPixelFormat](https://doc.qt.io/qt-5/qvideoframe.html#imageformatfrompixelformat)().
    
* Un puntero al *buffer* en memoria que contiene los datos de los píxeles. Como comentamos anteriormente, [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html)::[bits](https://doc.qt.io/qt-5/qvideoframe.html#bits)() nos proporciona esa información pero primero tenemos que invocar al método [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html)::[map](https://doc.qt.io/qt-5/qvideoframe.html#map)() para que dichos datos sean copiados desde el *buffer* interno a la memoria del programa.
    

```cpp
frame.map(QAbstractVideoBuffer::ReadOnly);
QImage frameAsImage = QImage(frame.bits(), frame.width(),
    frame.height(), frame.bytesPerLine(),
    QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat()));

// Aquí el código que manipula frameAsImage...

frame.unmap();
```

Hay que tener en cuenta que cuando se crea un objeto [QImage](https://doc.qt.io/qt-5/qimage.html) de esta manera, no se hace una copia del contenido de los píxeles, por lo que es importante asegurarse de que el puntero devuelto por [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html)::[bits](https://doc.qt.io/qt-5/qvideoframe.html#bits)() es válido mientras se esté haciendo uso del objeto. Por eso se invoca al método [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html)::[unmap](https://doc.qt.io/qt-5/qvideoframe.html#unmap)() después de manipularlo y no antes.

Si se quiere conservar la imagen del objeto [QImage](https://doc.qt.io/qt-5/qimage.html) después de invocar a [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html)::[unmap](https://doc.qt.io/qt-5/qvideoframe.html#unmap)() es necesario hacer una copia usando, por ejemplo, [QImage](https://doc.qt.io/qt-5/qimage.html)::[copy](https://doc.qt.io/qt-5/qimage.html#copy)().

# Referencias

* [Qt Multimedia](https://doc.qt.io/qt-5/qtmultimedia-index.html) — Qt Documentation.
    
* [QCamera](https://doc.qt.io/qt-5/qcamera.html) — Qt Documentation.
    
* [QVideoFrame](https://doc.qt.io/qt-5/qvideoframe.html) — Qt Documentation.
    
* [Video Overview](https://doc.qt.io/qt-5/videooverview.html) — Qt Documentation.
