Unverified Commit 87fed960 authored by Maikel Llamaret Heredia's avatar Maikel Llamaret Heredia Committed by GitHub

Merge pull request #23 from llamaret/qtmultimedia

More Mplayer replacement work
parents 3a713525 4b225275
......@@ -4,7 +4,7 @@
#
#-------------------------------------------------
QT += core gui network opengl multimedia qml widgets quick quickwidgets
QT += core gui network opengl multimedia qml widgets quick quickwidgets multimediawidgets
#DEFINES += QT_DEPRECATED_WARNINGS
QMAKE_CFLAGS_RELEASE += -flto
......@@ -52,12 +52,9 @@ SOURCES += main.cpp \
services/xmllookuptable.cpp \
ui/updatedialog.cpp \
services/settingtimer.cpp \
services/ffplaypreviewer.cpp \
services/abstractpreviewer.cpp \
services/mplayerpreviewer.cpp \
ui/previewdialog.cpp \
ui/interactivecuttingdialog.cpp \
ui/myqmpwidget.cpp \
ui/mediaplayerwidget.cpp \
ui/rangewidgetbinder.cpp
......@@ -100,13 +97,10 @@ HEADERS += \
services/xmllookuptable.h \
ui/updatedialog.h \
services/settingtimer.h \
services/ffplaypreviewer.h \
services/abstractpreviewer.h \
services/mplayerpreviewer.h \
ui/previewdialog.h \
ui/interactivecuttingdialog.h \
ui/mediaplayerwidget.h \
ui/myqmpwidget.h \
ui/rangewidgetbinder.h
FORMS += \
......@@ -194,16 +188,6 @@ DEFINES += VERSION_ID_STRING=$(VERSION_ID_STRING)
DEFINES += OPERATION_TIMEOUT=30000
DEFINES += DEFAULT_THREAD_COUNT=1
# QMPwidget (embedded mplayer)
SOURCES += qmpwidget/qmpwidget.cpp
HEADERS += qmpwidget/qmpwidget.h
INCLUDEPATH += qmpwidget
#CONFIG += qmpwidget_pipemode
!win32:qmpwidget_pipemode: {
HEADERS += qmpwidget/qmpyuvreader.h
DEFINES += QMP_USE_YUVPIPE
}
OTHER_FILES +=
DISTFILES +=
qmpwidget - A Qt widget for embedding MPlayer
=============================================
This is a small class which allows Qt developers to embed an MPlayer
instance into their application for convenient video playback. Starting
with version 4.4, Qt can be build with Phonon, the KDE multimedia
framework (see http://doc.trolltech.com/phonon-overview.html for an
overview). However, the Phonon API provides only a limited amount of
functionality, which may be too limited for some purposes.
In contrast, this class provides a way of embedding a full-fledged movie
player within an application. This means you can use all of MPlayer's
features, but also that you will need to ship a MPlayer binary with your
application if you can't make sure that it is already installed on a
user system.
For more information about MPlayer, please visit http://www.mplayerhq.hu/.
QMPwidget is written by Jonas Gehring and licensed under GPLv3. For more
information about QMPwidget, please visit http://qmpwidget.sourceforge.net/.
This diff is collapsed.
/* MystiQ - a C++/Qt5 gui frontend for ffmpeg
* Copyright (C) 2011-2019 Maikel Llamaret Heredia <llamaret@webmisolutions.com>
* Based in qmpwidget - A Qt widget for embedding MPlayer
* Copyright (C) 2010 by Jonas Gehring
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef QMPWIDGET_H_
#define QMPWIDGET_H_
#include <QHash>
#include <QPointer>
#include <QTimer>
#include <QWidget>
class QAbstractSlider;
class QImage;
class QProcess;
class QStringList;
class QMPProcess;
class QMPwidget : public QWidget
{
Q_OBJECT
Q_PROPERTY(State state READ state)
Q_PROPERTY(double streamPosition READ tell)
Q_PROPERTY(QString videoOutput READ videoOutput WRITE setVideoOutput)
Q_PROPERTY(QString mplayerPath READ mplayerPath WRITE setMPlayerPath)
Q_PROPERTY(QString mplayerVersion READ mplayerVersion)
Q_ENUMS(state)
public:
enum State {
NotStartedState = -1,
IdleState,
LoadingState,
StoppedState,
PlayingState,
BufferingState,
PausedState,
ErrorState
};
struct MediaInfo {
QString videoFormat;
int videoBitrate;
QSize size;
double framesPerSecond;
QString audioFormat;
double audioBitrate;
int sampleRate;
int numChannels;
QHash<QString, QString> tags;
bool ok;
double length;
bool seekable;
MediaInfo();
};
enum Mode {
EmbeddedMode = 0,
PipeMode
};
enum SeekMode {
RelativeSeek = 0,
PercentageSeek,
AbsoluteSeek
};
public:
QMPwidget(QWidget *parent = nullptr);
virtual ~QMPwidget();
State state() const;
MediaInfo mediaInfo() const;
double tell() const;
QProcess *process() const;
void setMode(Mode mode);
Mode mode() const;
void setVideoOutput(const QString &output);
QString videoOutput() const;
void setMPlayerPath(const QString &path);
QString mplayerPath() const;
QString mplayerVersion();
void setSeekSlider(QAbstractSlider *slider);
void setVolumeSlider(QAbstractSlider *slider);
void showImage(const QImage &image);
virtual QSize sizeHint() const;
public slots:
void start(const QStringList &args = QStringList());
void load(const QString &url);
void play();
void pause();
void stop();
bool seek(int offset, int whence = AbsoluteSeek);
bool seek(double offset, int whence = AbsoluteSeek);
void toggleFullScreen();
void writeCommand(const QString &command);
protected:
virtual void mouseDoubleClickEvent(QMouseEvent *event);
virtual void keyPressEvent(QKeyEvent *event);
virtual void resizeEvent(QResizeEvent *event);
private:
void updateWidgetSize();
private slots:
void setVolume(int volume);
void mpStateChanged(int state);
void mpStreamPositionChanged(double position);
void mpVolumeChanged(int volume);
void delayedSeek();
signals:
void stateChanged(int state);
void error(const QString &reason);
void readStandardOutput(const QString &line);
void readStandardError(const QString &line);
private:
QMPProcess *m_process;
QWidget *m_widget;
QPointer<QAbstractSlider> m_seekSlider;
QPointer<QAbstractSlider> m_volumeSlider;
Qt::WindowFlags m_windowFlags;
QRect m_geometry;
QTimer m_seekTimer;
QString m_seekCommand;
};
#endif // QMPWIDGET_H_
/*
* qmpwidget - A Qt widget for embedding MPlayer
* Copyright (C) 2010 by Jonas Gehring
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QImage>
#include <QDir>
#include <QMutex>
#include <QThread>
#ifdef Q_WS_WIN
#include "windows.h"
#endif
#include <cstdio>
#include <sys/stat.h>
// Internal YUV pipe reader
class QMPYuvReader : public QThread
{
Q_OBJECT
public:
// Constructor
QMPYuvReader(QObject *parent = nullptr)
: QThread(parent), m_stop(false), m_saveme(nullptr), m_savemeSize(-1)
{
QString tdir = QDir::tempPath();
// Create pipe in a temporary directory
char *temp = new char[tdir.length() + 12];
strcpy(temp, tdir.toLocal8Bit().data());
strcat(temp, "/XXXXXX");
if (mkdtemp(temp) == nullptr) {
qWarning("Can't create temporary directory");
return;
}
strcat(temp, "/fifo");
if (mkfifo(temp, 0600) != 0) {
qWarning("Can't create pipe");
return;
}
m_pipe = QString(temp);
delete[] temp;
initTables();
}
// Destructor
~QMPYuvReader()
{
delete[] m_saveme;
if (!m_pipe.isEmpty()) {
QFile::remove(m_pipe);
QDir().rmdir(QFileInfo(m_pipe).dir().path());
}
}
// Tells the thread to stop and exit
void stop()
{
m_mutex.lock();
m_stop = true;
m_mutex.unlock();
wait();
}
protected:
// Main thread loop
void run()
{
FILE *f = fopen(m_pipe.toLocal8Bit().data(), "rb");
if (f == nullptr) {
qWarning("Can't open pipe");
return;
}
// Parse stream header
char c;
int width, height, fps, t1, t2;
int n = fscanf(f, "YUV4MPEG2 W%d H%d F%d:1 I%c A%d:%d", &width, &height, &fps, &c, &t1, &t2);
if (n < 3) {
fclose(f);
qWarning("Unsupported pipe format");
return;
}
unsigned char *yuv[3];
yuv[0] = new unsigned char[width * height];
yuv[1] = new unsigned char[width * height];
yuv[2] = new unsigned char[width * height];
QImage image(width, height, QImage::Format_ARGB32);
// Read frames
const unsigned int ysize = width * height;
const unsigned int csize = width * height / 4;
while (true) {
m_mutex.lock();
if (m_stop) {
m_mutex.unlock();
break;
}
m_mutex.unlock();
if (fread(yuv[0], 1, 6, f) != 6) {
goto ioerror;
}
if (fread(yuv[0], 1, ysize, f) != ysize) {
goto ioerror;
}
if (fread(yuv[1], 1, csize, f) != csize) {
goto ioerror;
}
if (fread(yuv[2], 1, csize, f) != csize) {
goto ioerror;
}
supersample(yuv[1], width, height);
supersample(yuv[2], width, height);
yuvToQImage(yuv, &image, width, height);
emit imageReady(image);
continue;
ioerror:
qWarning("I/O error reading from pipe");
break;
}
delete[] yuv[0];
delete[] yuv[1];
delete[] yuv[2];
fclose(f);
}
// 420 to 444 supersampling (from mjpegtools)
void supersample(unsigned char *buffer, int width, int height)
{
unsigned char *inm, *in0, *inp, *out0, *out1;
unsigned char cmm, cm0, cmp, c0m, c00, c0p, cpm, cp0, cpp;
int x, y;
if (m_saveme == nullptr || width > m_savemeSize) {
delete[] m_saveme;
m_savemeSize = width;
m_saveme = new unsigned char[m_savemeSize];
}
memcpy(m_saveme, buffer, width);
in0 = buffer + (width * height / 4) - 2;
inm = in0 - width/2;
inp = in0 + width/2;
out1 = buffer + (width * height) - 1;
out0 = out1 - width;
for (y = height; y > 0; y -= 2) {
if (y == 2) {
in0 = m_saveme + width/2 - 2;
inp = in0 + width/2;
}
for (x = width; x > 0; x -= 2) {
cmm = ((x == 2) || (y == 2)) ? in0[1] : inm[0];
cm0 = (y == 2) ? in0[1] : inm[1];
cmp = ((x == width) || (y == 2)) ? in0[1] : inm[2];
c0m = (x == 2) ? in0[1] : in0[0];
c00 = in0[1];
c0p = (x == width) ? in0[1] : in0[2];
cpm = ((x == 2) || (y == height)) ? in0[1] : inp[0];
cp0 = (y == height) ? in0[1] : inp[1];
cpp = ((x == width) || (y == height)) ? in0[1] : inp[2];
inm--;
in0--;
inp--;
*(out1--) = (1*cpp + 3*(cp0+c0p) + 9*c00 + 8) >> 4;
*(out1--) = (1*cpm + 3*(cp0+c0m) + 9*c00 + 8) >> 4;
*(out0--) = (1*cmp + 3*(cm0+c0p) + 9*c00 + 8) >> 4;
*(out0--) = (1*cmm + 3*(cm0+c0m) + 9*c00 + 8) >> 4;
}
out1 -= width;
out0 -= width;
}
}
// Converts YCbCr data to a QImage
void yuvToQImage(unsigned char *planes[], QImage *dest, int width, int height)
{
unsigned char *yptr = planes[0];
unsigned char *cbptr = planes[1];
unsigned char *crptr = planes[2];
// This is partly from mjpegtools
for (int y = 0; y < height; y++) {
QRgb *dptr = (QRgb *)dest->scanLine(y);
for (int x = 0; x < width; x++) {
*dptr = qRgb(qBound(0, (RGB_Y[*yptr] + R_Cr[*crptr]) >> 18, 255),
qBound(0, (RGB_Y[*yptr] + G_Cb[*cbptr]+ G_Cr[*crptr]) >> 18, 255),
qBound(0, (RGB_Y[*yptr] + B_Cb[*cbptr]) >> 18, 255));
++yptr;
++cbptr;
++crptr;
++dptr;
}
}
}
// Rounding towards zero
inline int zround(double n)
{
if (n >= 0) {
return static_cast<int>(n + 0.5);
} else {
return static_cast<int>(n - 0.5);
}
}
// Initializes the YCbCr -> RGB conversion tables (again, from mjpegtools)
void initTables(void)
{
/* clip Y values under 16 */
for (int i = 0; i < 16; i++) {
RGB_Y[i] = zround((1.0 * static_cast<double>(16-16) * 255.0 / 219.0 * static_cast<double>(1<<18)) + static_cast<double>(1<<(18-1)));
}
for (int i = 16; i < 236; i++) {
RGB_Y[i] = zround((1.0 * static_cast<double>(i - 16) * 255.0 / 219.0 * static_cast<double>(1<<18)) + static_cast<double>(1<<(18-1)));
}
/* clip Y values above 235 */
for (int i = 236; i < 256; i++) {
RGB_Y[i] = zround((1.0 * static_cast<double>(235 - 16) * 255.0 / 219.0 * static_cast<double>(1<<18)) + static_cast<double>(1<<(18-1)));
}
/* clip Cb/Cr values below 16 */
for (int i = 0; i < 16; i++) {
R_Cr[i] = zround(1.402 * static_cast<double>(-112) * 255.0 / 224.0 * static_cast<double>(1<<18));
G_Cr[i] = zround(-0.714136 * static_cast<double>(-112) * 255.0 / 224.0 * static_cast<double>(1<<18));
G_Cb[i] = zround(-0.344136 * static_cast<double>(-112) * 255.0 / 224.0 * static_cast<double>(1<<18));
B_Cb[i] = zround(1.772 * static_cast<double>(-112) * 255.0 / 224.0 * static_cast<double>(1<<18));
}
for (int i = 16; i < 241; i++) {
R_Cr[i] = zround(1.402 * static_cast<double>(i - 128) * 255.0 / 224.0 * static_cast<double>(1<<18));
G_Cr[i] = zround(-0.714136 * static_cast<double>(i - 128) * 255.0 / 224.0 * static_cast<double>(1<<18));
G_Cb[i] = zround(-0.344136 * static_cast<double>(i - 128) * 255.0 / 224.0 * static_cast<double>(1<<18));
B_Cb[i] = zround(1.772 * static_cast<double>(i - 128) * 255.0 / 224.0 * static_cast<double>(1<<18));
}
/* clip Cb/Cr values above 240 */
for (int i = 241; i < 256; i++) {
R_Cr[i] = zround(1.402 * static_cast<double>(112) * 255.0 / 224.0 * static_cast<double>(1<<18));
G_Cr[i] = zround(-0.714136 * static_cast<double>(112) * 255.0 / 224.0 * static_cast<double>(1<<18));
G_Cb[i] = zround(-0.344136 * static_cast<double>(i-128) * 255.0 / 224.0 * static_cast<double>(1<<18));
B_Cb[i] = zround(1.772 * static_cast<double>(112) * 255.0 / 224.0 * static_cast<double>(1<<18));
}
}
signals:
void imageReady(const QImage &image);
public:
QString m_pipe;
private:
QMutex m_mutex;
bool m_stop;
// Conversion tables
int RGB_Y[256];
int R_Cr[256];
int G_Cb[256];
int G_Cr[256];
int B_Cb[256];
// Temporary buffers
unsigned char *m_saveme;
int m_savemeSize;
};
/* MystiQ - a C++/Qt5 gui frontend for ffmpeg
* Copyright (C) 2011-2019 Maikel Llamaret Heredia <llamaret@webmisolutions.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ffplaypreviewer.h"
#include "converter/exepath.h"
#include <QProcess>
#include <QDebug>
#ifdef OPERATION_TIMEOUT
# define TIMEOUT OPERATION_TIMEOUT
#else
# define TIMEOUT 3000
#endif
#define DEFAULT_WIDTH 320
#define DEFAULT_HEIGHT 180
FFplayPreviewer::FFplayPreviewer(QObject *parent) :
AbstractPreviewer(parent),
m_proc(new QProcess),
m_w(DEFAULT_WIDTH), m_h(DEFAULT_HEIGHT)
{
}
FFplayPreviewer::~FFplayPreviewer()
{
delete m_proc;
}
bool FFplayPreviewer::available() const
{
return FFplayAvailable();
}
void FFplayPreviewer::play(const QString &filename)
{
ffplay_start(filename, -1, -1);
}
void FFplayPreviewer::play(const QString &filename, int t_begin, int t_end)
{
ffplay_start(filename, t_begin, t_end);
}
void FFplayPreviewer::playFrom(const QString &filename, int t_begin)
{
ffplay_start(filename, t_begin, -1);
}
void FFplayPreviewer::playUntil(const QString &filename, int t_end)
{
ffplay_start(filename, -1, t_end);
}
void FFplayPreviewer::stop()
{
if (m_proc->state() != QProcess::NotRunning) {
m_proc->kill();
m_proc->waitForFinished(TIMEOUT);
}
}
void FFplayPreviewer::setWindowSize(int w, int h)
{
m_w = w; m_h = h;
}
void FFplayPreviewer::setWindowTitle(QString str)
{
m_title = str;
}
bool FFplayPreviewer::FFplayAvailable()
{
QProcess proc;
QStringList param;
/* test whether ffplay could be invoked */
proc.start(ExePath::getPath("ffplay"), param);
if (!proc.waitForStarted(TIMEOUT))
return false;
proc.kill();
proc.waitForFinished(TIMEOUT);
return true;
}
/* If t_begin >= 0, start from t_begin; otherwise, start from time zero.
If t_end >= 0, stop at t_end; othersize, play until end of file. */
void FFplayPreviewer::ffplay_start(const QString& filename, int t_begin, int t_end)
{
QStringList param;
stop();
param.append("-autoexit");
param.append("-x"); param.append(QString::number(m_w));
param.append("-y"); param.append(QString::number(m_h));
if (!m_title.isEmpty())
static_cast<void>(param.append("-window_title")), param.append(m_title);
if (t_begin >= 0)
static_cast<void>(param.append("-ss")), param.append(QString::number(t_begin));
if (t_end >= 0) {
param.append("-t"); // duration
if (t_begin >= 0)
param.append(QString::number(t_end - t_begin)); // duration = end - begin
else
param.append(QString::number(t_end)); // start from time zero
}
param.append(filename);
qDebug() << "ffplay" << param.join(" ");
m_proc->start(ExePath::getPath("ffplay"), param);
m_proc->waitForStarted(TIMEOUT);
}
/* MystiQ - a C++/Qt5 gui frontend for ffmpeg
* Copyright (C) 2011-2019 Maikel Llamaret Heredia <llamaret@webmisolutions.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FFPLAYPREVIEWER_H
#define FFPLAYPREVIEWER_H
#include "abstractpreviewer.h"
class QProcess;
class FFplayPreviewer : public AbstractPreviewer
{
Q_OBJECT
public:
explicit FFplayPreviewer(QObject *parent = nullptr);
virtual ~FFplayPreviewer();
bool available() const;
/** Play the media file with ffplay.
* If a media file is being played, it will be stopped before
* playing the new one.
*/
void play(const QString& filename);
void play(const QString &filename, int t_begin, int t_end);
void playFrom(const QString &filename, int t_begin);
void playUntil(const QString &filename, int t_end);
void stop();
/** Set the window size of ffplay.
* This option takes effect after invoking start() again.
*/
void setWindowSize(int w, int h);
/** Set the ffplay window title.
* This option takes effect after invoking start() again.
* If str is empty, default title is displayed (filename).
*/
void setWindowTitle(QString str);
static bool FFplayAvailable();
signals:
public slots:
private: