I have written a simple video player GUI code in QT. The GUI allows the user to browse the local files and select a video for playing in the GUI. The GUI also has options for 'play', 'pause' and 'stop' to apply to the video selected.
I want to add another button 'Capture', that captures the current frame of the video that is being played, and displays this captured image next to the video (The video should should get paused at this point).
I looked into the documentation of QT, specifically: this and this. But I am still not able to understand how to implement this in my case.
Kindly guide.
My code so far is as follows:
#include "qtwidgetsapplication4.h"
#include <iostream>
QtWidgetsApplication4::QtWidgetsApplication4(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
player = new QMediaPlayer(this);
vw = new QVideoWidget(this);
player->setVideoOutput(vw);
this->setCentralWidget(vw);
}
void QtWidgetsApplication4::on_actionOpen_triggered() {
QString filename = QFileDialog::getOpenFileName(this, "Open a File", "", "Video File (*.*)");
on_actionStop_triggered();
player->setSource(QUrl::fromLocalFile(filename));
on_actionPlay_triggered();
qDebug("Error Message in actionOpen");
qDebug()<<player->mediaStatus();
}
void QtWidgetsApplication4::on_actionPlay_triggered() {
player->play();
ui.statusBar->showMessage("Playing");
qDebug("Error Message in actionPlay");
qDebug() << player->mediaStatus();
}
void QtWidgetsApplication4::on_actionPause_triggered() {
player->pause();
ui.statusBar->showMessage("Paused...");
qDebug("Error Message in actionPause");
qDebug() << player->mediaStatus();
}
void QtWidgetsApplication4::on_actionStop_triggered() {
player->stop();
ui.statusBar->showMessage("Stopped");
qDebug("Error Message in actionStop");
qDebug() << player->mediaStatus();
}
You can use QVideoProbe to capture QVideoFrame. Instantiate and connect videoFrameProbed signal to your slot before pausing video. Convert QVideoFrame to QImage in this slot using frame data.
QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat());
QImage image(frame.bits(), frame.width(), frame.height(), imageFormat);
Take a look at player example for reference. It uses QVideoProbe to calculate histogram of current frame.
Related
I have a simple Qt/C++ program which gathers a webcam image out of one of my LAN devices using a cv::VideoCapture object.
The application is being built using Qt Quick and has an Image QML Item which is being served with a picture every 500 milliseconds via a custom QQuickImageProvider implementation:
WebcamImageProvider::WebcamImageProvider()
: QQuickImageProvider(ImageType::Image)
{
connect(&_timer, &QTimer::timeout, this, &WebcamImageProvider::updateImage);
// refresh our picture every .5 seconds
_timer.start(500);
}
// overridden function from base class
QImage WebcamImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
Q_UNUSED(id)
Q_UNUSED(size)
Q_UNUSED(requestedSize)
return _img;
}
My main function looks like this:
// Qt Quick
#include <QQuickItem>
#include <QQuickWindow>
// QML + GUI
#include <QQmlContext>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
// webcam stuff
#include "webcamimageprovider.h"
/*
* Here we go
*/
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
// we're adding our self-made image provider instance here
WebcamImageProvider wip;
engine.addImageProvider("webcam", &wip);
engine.rootContext()->setContextProperty("webcamImageProvider", &wip);
engine.load(url);
return app.exec();
The code of my main.qml file should not be important here.
Inside my image provider class, I have provided the updateImage slot as follows:
void WebcamImageProvider::updateImage()
{
/*
* Webcam image grapping via https://raspberrypi.stackexchange.com/a/85510/132942
*/
if (!_cap.open(_videoUrl)) {
qDebug() << "Error opening stream.";
}
if (!_cap.read(_mat)) {
qDebug() << "No video capture or webcam error.";
}
// via https://stackoverflow.com/a/12312326/4217759
_img = QImage(
static_cast<uchar*>(_mat.data),
_mat.cols,
_mat.rows,
static_cast<int>(_mat.step),
QImage::Format_BGR888
);
emit imageChanged(_img);
}
My problem here is that when my device is not reachable via network, the application will freeze completely as it gets stuck in the _cap.open() function. Therefore, I am trying to outsource this function to a future so the image will be loaded asynchronously. Since I have literally zero idea of threading and futures, I randomly gave it a shot with using QFutures:
void WebcamImageProvider::updateImage()
{
/*
* Webcam image grapping via https://raspberrypi.stackexchange.com/a/85510/132942
*/
QFuture<void> future = QtConcurrent::run([&](){ _cap.open(_videoUrl); });
if (!future.isRunning()) {
qDebug() << "Error opening stream.";
}
if (future.isFinished()) {
if (!_cap.read(_mat)) {
qDebug() << "No video capture or webcam error.";
}
// via https://stackoverflow.com/a/12312326/4217759
_img = QImage(
static_cast<uchar*>(_mat.data),
_mat.cols,
_mat.rows,
static_cast<int>(_mat.step),
QImage::Format_BGR888
);
emit imageChanged(_img);
}
}
However, I won't get any image output from that.
Can someone help me on how to structure the code correctly so I can load my image asynchronously here?
Thanks a lot in advance.
1- you should know something , create setter for _img and emit changed in that ! because it is possible one day you need set Image in another functions and if you don't do this , you should duplicate emit fooChanged().
2- when you check these and is's not responsible for working in your method , you should throw an exception then handle them and if you wanted use qDebug().
3- my suggest is that (if I understood completely your job) create an thread that working in loop and always getting new images then create a queue worker and create another thread (worker) for process your scenario(here updateImage method).
I am trying to get notifications in Qt for a printer but unfortunately couldn't find any solution. I've already tried to check the state but it never changes, it is always 'PrinterState::Idle'.
void Functions::print(QString fileName)
{
printerTmr = new QTimer(this);
printerTmr->setInterval(2000);
connect(printerTmr, SIGNAL(timeout()), this, SLOT(printerStateCheck()));
printerTmr->start(); //start checking the state of the printer
printer.setPageSize(QPrinter::A6);
printer.setOrientation(QPrinter::Landscape);
QImage img(fileName);
QSize size;
QIcon icon;
QPainter painter( &printer );
int w = printer.pageRect().width();
int h = printer.pageRect().height();
QRect page( 0, 0, w, h );
QImage image(fileName);
if (!image.isNull())
icon.addPixmap(QPixmap::fromImage(image), QIcon::Normal, QIcon::On);
icon = icon;
size = QSize(w,h);
QPixmap pixmap = icon.pixmap(size, QIcon::Normal, QIcon::On);
........
//draw simulated landscape
page.adjust( w/20, h/20, -w/20, -h/20 );
painter.drawPixmap(QPoint(0,0),pixmap);
}
void Functions::printerStateCheck()
{
if(printer.printerState() == QPrinter::PrinterState::Idle){
qDebug()<<"PrinterState::Idle";
}else if(printer.printerState() == QPrinter::PrinterState::Error){
qDebug()<<"PrinterState::Error";
}else if(printer.printerState() == QPrinter::PrinterState::Active){
qDebug()<<"PrinterState::Active";
}else if(printer.printerState() == QPrinter::PrinterState::Aborted){
qDebug()<<"PrinterState::Aborted";
}
}
Printing possibility in Qt is hold very simple. QPrinter device represents a series of pages of printed output, and is used in almost exactly the same way as other paint devices such as QWidget and QPixmap.
When printing directly to a printer on Windows or macOS, QPrinter uses the built-in printer drivers. On X11, QPrinter uses the Common Unix Printing System (CUPS) to send PDF output to the printer.
As an alternative, the printProgram() function can be used to specify the command or utility to use instead of the system default. (P.S.: but only on X11 system for pdf printing)
QPrinter::printerState() Returns the current state of the printer. This may not always be accurate (for example if the printer doesn't have the capability of reporting its state to the operating system).
So like the Qt doc says it is on printer, printer drivers, printing subsystem and OS itself to provide the state. I think you have more luck with printing states under linux whith CUPS then in windows.
Try to use OS printing API directly.
Here is info with example code for WINAPI on How to get the status of a printer and a print job
I use Qt 5.7
I'm writing Music Player, and have one problem. Method duration() of QMediaPlayer always returns 0. How can I fix it?
Example of code:
QMediaPlayer *player = new QMediaPlayer;
player->setMedia(QMediaContent(QUrl(path)));
qDebug() << player->duration(); // returns 0
player->play(); // it works
You cannot do a player->duration() right after the player->setMedia(QMediaContent(QUrl(path)));.
Indeed, the QMediaPlayer::setMedia is asynchronous so if you call the duration right after it, the media won't be set yet and then the duration will be wrong.
From Qt documentation on setMedia:
Note: This function returns immediately after recording the specified source of the media. It does not wait for the media to finish loading and does not check for errors.
When the duration is updated, QMediaPlayer send a signal named durationChanged(qint64 duration). So that you need to do is to connect this signal with a lambda or a slot.
For example,
QMediaPlayer *player = new QMediaPlayer(this);
connect(player, &QMediaPlayer::durationChanged, this, [&](qint64 dur) {
qDebug() << "duration = " << dur;
});
QUrl file = QUrl::fromLocalFile(QFileDialog::getOpenFileName(this, tr("Open Music"), "", tr("")));
if (file.url() == "")
return ;
player->setMedia(file);
qDebug() << player->duration();
player->setVolume(50);
player->play();
the first qDebug will write 0 as your but the second in the lambda will write the new duration of the QMediaPlayer.
I need to capture video from a live camera plugged to my PC ethernet socket. I used Phonon to capture video from a file in my system first. It works fine. Then, I created a socket to read the video. Here, I do not know how to get the buffered data and set it as a source to my Phonon video! I would thank if anyone could help me with this.
Here's the code to read a video :
void PlayVideo::rollOn()
{
media = new Phonon::MediaObject(movieLabel);
media->setCurrentSource(Phonon::MediaSource(QString("/home/saman/4.7/phonon_test/sample.mp4")));
videoPlayer = new Phonon::VideoPlayer(Phonon::VideoCategory, movieLabel);
videoPlayer->setFixedSize(QSize(400, 300));
videoPlayer->show();
connect(videoPlayer, SIGNAL(finished()), videoPlayer, SLOT(deleteLater()));
videoPlayer->play(media->currentSource());
}
and this is how I added sockets to the code:
void PlayVideo::rollOn()
{
udpSocketin = new QUdpSocket(this);
udpSocketin->bind(localPort);
connect(udpSocketin, SIGNAL(readyRead()),this, SLOT(readDatagrams()));
QDataStream out(&datagramout, QIODevice::WriteOnly);
out.setVersion (QDataStream::Qt_4_7);
timer2 = new QTimer(this);
connect(timer2, SIGNAL(timeout()), this, SLOT(playbuff()));
media = new Phonon::MediaObject(movieLabel);
media->setCurrentSource(Phonon::MediaSource(QString("/home/saman/4.7/phonon_test/sample.mp4")));
//media->setCurrentSource (Phonon::MediaSource());
videoPlayer = new Phonon::VideoPlayer(Phonon::VideoCategory, movieLabel);
videoPlayer->setFixedSize(QSize(400, 300));
videoPlayer->show();
connect(videoPlayer, SIGNAL(finished()), videoPlayer, SLOT(deleteLater()));
videoPlayer->play(media->currentSource());
}
void PlayVideo::readDatagrams()
{
if(udpSocketin->hasPendingDatagrams ())
{
datagramin.resize (udpSocketin->pendingDatagramSize ());
qint64 receiveBytes = udpSocketin->readDatagram (datagramin.data (), datagramin.size ));
if(receivedBytes <= 0)
{
qDebug("receivedBytes <= 0");
}
}
}
You can put your data into a QBuffer, which is a subclass of QIODevice. Then, there's a media source constructor that accepts a QIODevice.
My current code is:
void Sound::run() {
QFile audio_file(mResourcePath);
if(audio_file.open(QIODevice::ReadOnly)) {
audio_file.seek(44); // skip wav header
QByteArray audio_data = audio_file.readAll();
audio_file.close();
QBuffer* audio_buffer = new QBuffer(&audio_data);
qDebug() << audio_buffer->size();
QAudioFormat format;
format.setSampleSize(16);
format.setSampleRate(44100);
format.setChannelCount(2);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::UnSignedInt);
QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
if (!info.isFormatSupported(format)) {
qWarning()<<"raw audio format not supported by backend, cannot play audio.";
return;
}
qDebug() << info.deviceName();
QAudioOutput* output = new QAudioOutput(info, format);
output->start(audio_buffer);
}
}
This whole thing is started as a QRunnable in a QThreadPool and that part works fine. Problem is I never get any audio. My sound device is operational, the buffer is filled. I don't know what's wrong. I use app.exec(). Help appreciated.
The device (QBuffer) has to be open:
QBuffer audio_buffer(&audio_data);
audio_buffer.open(QIODevice::ReadOnly);
QAudioOutput needs an event loop to play anything, and that loop has to be running in the thread it belongs to. Which is the thread it was created in when you don't explicitly move it to another thread:
// Create the device and start playing...
QAudioOutput output(info, format);
output.start(&audio_buffer);
// ...then wait for the sound to finish
QEventLoop loop;
QObject::connect(&output, SIGNAL(stateChanged(QAudio::State)), &loop, SLOT(quit()));
do {
loop.exec();
} while(output.state() == QAudio::ActiveState);
Everything you allocate should be deallocated when the sound has finished playing, or you would have a memory leak, and the event loop will now run inside the function, so you can allocate everything locally.