Hi, Habr!
In this article I will talk about working with a webcam from Qt5 under Windows (but the example should also work under Linux and Mac OS X with the gstreamer plugin installed).

')
If you are interested in how to make such an application and overcome the problems arising from this, then I ask for a cat.
Prehistory
Once I wanted to add webcam support to my screenshot (which, in principle, not really a screenshot). Since at that time I used Qt4, I began to look for ready-made solutions for this version, but then
EuroElessar suggested to me that Qt5 has a
QCamera class that just fit my tasks.
It was decided to switch to Qt5, which was still in the alpha state (and even now only the prebet).
First problems
The first problems began at the compilation stage. Because of the curves of scripts / guides, I didn’t want to compile qtwebkit, which is why I lost one evening, but then the whole framework was compiled as a debug and release version.
Further - more interesting.
Going into the examples for QtMultimedia and finding the camera directory there, I decided to start and see how it works.
Here I was waiting for the second problem:

Obviously, Kutyam is missing some kind of plug-in. To find it, I
went to
QtMultimedia \ src \ plugins . There, my first glance fell on gstreamer, but rather quickly I realized that under Windows I couldn’t compile it.
Then I found the unfinished directshow there.
Direct show
Having compiled this plugin and putting it in
QtBase \ plugins \ mediaservice , I successfully launched an example from QtMultimedia, which showed a list of cameras and even tried to display an image, but it worked out badly and striped for him:

Spitting on this, I began to write my code, hoping that I would not have this problem. And it really did not appear, but it was different: the resolution of the images was always 320x240. After looking through the plug-in directshow code a bit, I decided to go to sleep to deal with it tomorrow. The next day again did not bring any results with directshow, but I completely added the code in my application. Therefore, there was only one thing - to finish this directshow.
The next day I found a solution, which, as usually happens in such situations, turned out to be quite simple and obvious. The code nowhere did not
call the function
updateProperties () , which received information about the supported permissions, as well as the dimensions of the 320x240 were fixed in the class constructor itself. Correcting this function and adding its call, I began to receive the image of the highest possible resolution.
Correction1) Open the
QtMultimedia \ src \ plugins \ directshow \ camera \ dscamerasession.cpp file .
2) In the
DSCameraSession :: setDevice (...) function, at the very end, add
updateProperties () to the end of the if block
; .
3) The
updateProperties ( ) function is replaced by this:
void DSCameraSession::updateProperties() { HRESULT hr; AM_MEDIA_TYPE *pmt = NULL; VIDEOINFOHEADER *pvi = NULL; VIDEO_STREAM_CONFIG_CAPS scc; IAMStreamConfig* pConfig = 0; hr = pBuild->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video,pCap, IID_IAMStreamConfig, (void**)&pConfig); if (FAILED(hr)) { qWarning()<<"failed to get config on capture device"; return; } int iCount; int iSize; hr = pConfig->GetNumberOfCapabilities(&iCount, &iSize); if (FAILED(hr)) { qWarning()<<"failed to get capabilities"; return; } QList<QSize> sizes; QVideoFrame::PixelFormat f = QVideoFrame::Format_Invalid; types.clear(); resolutions.clear(); for (int iIndex = 0; iIndex < iCount; iIndex++) { hr = pConfig->GetStreamCaps(iIndex, &pmt, reinterpret_cast<BYTE*>(&scc)); if (hr == S_OK) { pvi = (VIDEOINFOHEADER*)pmt->pbFormat; if ((pmt->majortype == MEDIATYPE_Video) && (pmt->formattype == FORMAT_VideoInfo)) { // Add types if (pmt->subtype == MEDIASUBTYPE_RGB24) { if (!types.contains(QVideoFrame::Format_RGB24)) { types.append(QVideoFrame::Format_RGB24); f = QVideoFrame::Format_RGB24; } } else if (pmt->subtype == MEDIASUBTYPE_RGB32) { if (!types.contains(QVideoFrame::Format_RGB32)) { types.append(QVideoFrame::Format_RGB32); f = QVideoFrame::Format_RGB32; } } else if (pmt->subtype == MEDIASUBTYPE_YUY2) { if (!types.contains(QVideoFrame::Format_YUYV)) { types.append(QVideoFrame::Format_YUYV); f = QVideoFrame::Format_YUYV; } } else if (pmt->subtype == MEDIASUBTYPE_MJPG) { } else if (pmt->subtype == MEDIASUBTYPE_I420) { if (!types.contains(QVideoFrame::Format_YUV420P)) { types.append(QVideoFrame::Format_YUV420P); f = QVideoFrame::Format_YUV420P; } } else if (pmt->subtype == MEDIASUBTYPE_RGB555) { if (!types.contains(QVideoFrame::Format_RGB555)) { types.append(QVideoFrame::Format_RGB555); f = QVideoFrame::Format_RGB555; } } else if (pmt->subtype == MEDIASUBTYPE_YVU9) { } else if (pmt->subtype == MEDIASUBTYPE_UYVY) { if (!types.contains(QVideoFrame::Format_UYVY)) { types.append(QVideoFrame::Format_UYVY); f = QVideoFrame::Format_UYVY; } } else { qWarning() << "UNKNOWN FORMAT: " << pmt->subtype.Data1; } // Add resolutions QSize res(pvi->bmiHeader.biWidth, pvi->bmiHeader.biHeight); if (!resolutions.contains(f)) { sizes.clear(); resolutions.insert(f,sizes); } resolutions[f].append(res); if ( m_windowSize.width() < res.width() && m_windowSize.height() < res.height() ) m_windowSize = res; } } } pConfig->Release(); pixelF = QVideoFrame::Format_RGB24; actualFormat = QVideoSurfaceFormat(m_windowSize,pixelF); }
Now go directly to the code.
Using Qt5 with a webcam
Since the example is small and serves only for demonstration, I described all the slots in the constructor.
We draw molds
To begin with we will sketch two small forms in the designer.
webcam.ui - in fact, the main window:
webcamselect.ui - used to select a webcam, if several of them are installed:

Header file
Here I just give the code of the header file, because there is nothing to comment on.
webcam.h #ifndef WEBCAM_H #define WEBCAM_H #include <QtGui/QClipboard> #include <QtMultimedia/QtMultimedia> #include <QtMultimediaWidgets/QtMultimediaWidgets> #include <QtWidgets/QtWidgets> #include #include class webCam : public QWidget { Q_OBJECT public: webCam(); ~webCam(); bool nativeEvent( QByteArray ba, void *message, long *result ); public slots: void cameraError( QCamera::Error value ); void cameraStateChanged( QCamera::State state ); void capture( bool checked = false ); protected: void mouseMoveEvent( QMouseEvent* event ); void mousePressEvent( QMouseEvent* event ); void paintEvent( QPaintEvent *event ); void resizeEvent( QResizeEvent *event ); private: Ui::webCamClass ui; Ui::webCamSelectClass select_ui; QPoint m_drag_pos; static QByteArray m_defaultDevice; QDialog *m_selectDialog; QPointer< QCamera > m_camera; QPointer< QCameraImageCapture > m_imageCapture; QPixmap m_pixmap; QTimer *m_timer; int m_timerPaintState; };
Camera selection
As you can see from webcam.h, we have a static member in the class called m_defaultDevice, which we define before the constructor:
QByteArray webCam::m_defaultDevice = QByteArray();
In the constructor itself, the function
QCamera :: availableDevices () will get a list of cameras, and then check if our m_defaultDevice is in the list. Depending on this, we will have two further paths:
1) If the device is listed, then just skip this step.
2) If he was not there, then you need to display a dialog with a choice:

However, if there are no webcams, then you just need to come up with an error, and if it is only one, then select it.
But if there are several webcams, then in the loop we will create buttons for each webcam and show the dialog:
foreach( QByteArray webCam, cams ) { auto commandLinkButton = new QCommandLinkButton( QCamera::deviceDescription( webCam ) ); commandLinkButton->setProperty( "webCam", webCam ); connect( commandLinkButton, &QCommandLinkButton::clicked, [=]( bool ) { m_defaultDevice = commandLinkButton->property( "webCam" ).toByteArray(); m_selectDialog->accept(); } ); select_ui.verticalLayout->addWidget( commandLinkButton ); } if ( m_selectDialog->exec() == QDialog::Rejected ) { deleteLater(); return; }
Here it is very convenient to use the
new syntax of signal slots in order not to spread the code throughout the class, which I did.
After selecting the user, the program will either exit (he clicked on the cross), or in m_defaultDevice will be the id of our device.
Creating QCamera and QCameraViewfinder Objects
When creating these objects, no problems should arise, so we simply pass the camera's QCamera id to the constructor and connect it to the error and status change slots:
m_camera = new QCamera( m_defaultDevice ); connect( m_camera, SIGNAL( error( QCamera::Error ) ), this, SLOT( cameraError( QCamera::Error ) ) ); connect( m_camera, SIGNAL( stateChanged( QCamera::State ) ), this, SLOT ( cameraStateChanged( QCamera::State ) ) );
QCameraViewfinder is such an object that allows you to display an image from a webcam directly to the widget (because we want the user to not blindly take pictures of himself?).
Create, set the minimum size (otherwise our widget can not be reduced) and connect with the camera object:
auto viewfinder = new QCameraViewfinder; viewfinder->setMinimumSize( 50, 50 ); m_camera->setViewfinder( viewfinder ); m_camera->setCaptureMode( QCamera::CaptureStillImage );
(The
QCamera :: CaptureStillImage parameter is required so that you can capture images.)
Setting UI and timer buttons
Create a new label that will be drawn on top of the image and count, and a template variable for it:
auto timerLabel = new QLabel; QString timerLabelTpl = "<p align=\"center\"><span style=\"font-size:50pt; font-weight:600; color:#FF0000;\">%1</span></p>";
and put it on the viewfinder:
ui.gridLayout_3->addWidget( viewfinder, 0, 0 ); ui.gridLayout_3->addWidget( timerLabel, 0, 0 );
Next, we will declare a timer that will run during the countdown and its slot:
m_timerPaintState = 0; m_timer = new QTimer( this ); m_timer->setInterval( 1000 ); connect( m_timer, &QTimer::timeout, [=]() { m_timerPaintState--; if ( m_timerPaintState ) { timerLabel->setText( timerLabelTpl.arg( QString::number( m_timerPaintState ) ) ); } else { m_timer->stop(); timerLabel->hide(); capture(); } } );
As you can see from the code, if there is still time, then simply reduce it by a second, and if it is over, then we take pictures, turning off the timer and hiding the counter.
Control button slots
Since the code for all of them is quite simple, I will not bring it here, but I will say a few words about QClipboard:
connect( ui.copyButton, &QPushButton::clicked, [=]( bool ) { QApplication::clipboard()->setImage( m_pixmap.toImage() ); } );
In the current version of Qt, it works rather strangely: it may not write the image to the buffer (it rarely happens), or, while getting it from there, spoil it. Hopefully this will be fixed for release.
Image capture
m_camera->start(); m_imageCapture = new QCameraImageCapture( m_camera ); //m_imageCapture->setCaptureDestination( QCameraImageCapture::CaptureToBuffer ); m_imageCapture->setCaptureDestination( QCameraImageCapture::CaptureToFile );
We turn on the camera and create a
QCameraImageCapture object that must support writing to the buffer (
QCameraImageCapture :: CaptureToBuffer ), but it still writes to the file.
The
imageSaved () slot almost duplicates
imageCaptured () , so I only describe it in the article.
connect( m_imageCapture, &QCameraImageCapture::imageSaved, [=]( int id, const QString &fileName ) { QFile imageFile( fileName ); if ( imageFile.exists() ) { m_pixmap = QPixmap::fromImage( QImage( fileName ).mirrored( true, false ) ); ui.picture->setPixmap( m_pixmap.scaled( ui.picture->width(), ui.picture->height(), Qt::KeepAspectRatio ) ); imageFile.remove(); } else { QMessageBox::critical( this, "Error", "Image file are not found!" ); deleteLater(); return; } } );
We open the file in which the camera has placed the image, and read the image from it, which we then mirror and place in m_pixmap, and then, stretching or compressing in size, in QLabel picture. Delete the file so as not to litter.
Capture function
The capture function, like all other functions, is not more complex and consists of 3 significant lines:
void webCam::capture( bool ) { m_camera->searchAndLock(); m_imageCapture->capture( QCoreApplication::applicationDirPath() + "/image.jpg" ); m_camera->unlock(); ui.captureButton->setEnabled( true ); ui.timerButton->setEnabled( true ); }
First, we focus and block the camera. Locking must be done to prevent another application from changing the settings we have configured to take a snapshot.
Secondly, we take a snapshot of the file, passed as a parameter.
Third, unlock the camera.
The remaining functions are of no interest and, I think, it makes no sense to comment on them.
Conclusion
Despite the fact that Qt5 is still not even beta-capable, things like a webcam can already be used, but with some reservations and problems to be solved.
The source code of the application can be found
here .
I hope this article will help someone.
(Since this is my first article, I ask you to report all typos and design errors to the PM.)