Changes to QMediaPlayer GStreamer backend to allow setPosition before pause
Do not display prerolled frames in stopped state. Instead store prerolled frame and display it only after switching to pause or playback state. Added new unit test with a sample video file to check this functionality. Change-Id: I3fd159a199b65ca10fdf9843af5675c5ae9dad05 Reviewed-by: Michael Goddard <michael.goddard@nokia.com>
This commit is contained in:
committed by
Qt by Nokia
parent
7415e4c879
commit
39c96db2b3
@@ -66,6 +66,7 @@ QVideoSurfaceGstDelegate::QVideoSurfaceGstDelegate(
|
|||||||
: m_surface(surface)
|
: m_surface(surface)
|
||||||
, m_pool(0)
|
, m_pool(0)
|
||||||
, m_renderReturn(GST_FLOW_ERROR)
|
, m_renderReturn(GST_FLOW_ERROR)
|
||||||
|
, m_lastPrerolledBuffer(0)
|
||||||
, m_bytesPerLine(0)
|
, m_bytesPerLine(0)
|
||||||
, m_startCanceled(false)
|
, m_startCanceled(false)
|
||||||
{
|
{
|
||||||
@@ -87,6 +88,7 @@ QVideoSurfaceGstDelegate::QVideoSurfaceGstDelegate(
|
|||||||
QVideoSurfaceGstDelegate::~QVideoSurfaceGstDelegate()
|
QVideoSurfaceGstDelegate::~QVideoSurfaceGstDelegate()
|
||||||
{
|
{
|
||||||
qDeleteAll(m_pools);
|
qDeleteAll(m_pools);
|
||||||
|
setLastPrerolledBuffer(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QVideoFrame::PixelFormat> QVideoSurfaceGstDelegate::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const
|
QList<QVideoFrame::PixelFormat> QVideoSurfaceGstDelegate::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const
|
||||||
@@ -222,6 +224,23 @@ GstFlowReturn QVideoSurfaceGstDelegate::render(GstBuffer *buffer)
|
|||||||
return m_renderReturn;
|
return m_renderReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QVideoSurfaceGstDelegate::setLastPrerolledBuffer(GstBuffer *prerolledBuffer)
|
||||||
|
{
|
||||||
|
// discard previously stored buffer
|
||||||
|
if (m_lastPrerolledBuffer) {
|
||||||
|
gst_buffer_unref(m_lastPrerolledBuffer);
|
||||||
|
m_lastPrerolledBuffer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prerolledBuffer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// store a reference to the buffer
|
||||||
|
Q_ASSERT(!m_lastPrerolledBuffer);
|
||||||
|
m_lastPrerolledBuffer = prerolledBuffer;
|
||||||
|
gst_buffer_ref(m_lastPrerolledBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
void QVideoSurfaceGstDelegate::queuedStart()
|
void QVideoSurfaceGstDelegate::queuedStart()
|
||||||
{
|
{
|
||||||
if (!m_startCanceled) {
|
if (!m_startCanceled) {
|
||||||
@@ -393,6 +412,8 @@ QVideoSurfaceGstSink *QVideoSurfaceGstSink::createSink(QAbstractVideoSurface *su
|
|||||||
|
|
||||||
sink->delegate = new QVideoSurfaceGstDelegate(surface);
|
sink->delegate = new QVideoSurfaceGstDelegate(surface);
|
||||||
|
|
||||||
|
g_signal_connect(G_OBJECT(sink), "notify::show-preroll-frame", G_CALLBACK(handleShowPrerollChange), sink);
|
||||||
|
|
||||||
return sink;
|
return sink;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,7 +456,7 @@ void QVideoSurfaceGstSink::class_init(gpointer g_class, gpointer class_data)
|
|||||||
base_sink_class->start = QVideoSurfaceGstSink::start;
|
base_sink_class->start = QVideoSurfaceGstSink::start;
|
||||||
base_sink_class->stop = QVideoSurfaceGstSink::stop;
|
base_sink_class->stop = QVideoSurfaceGstSink::stop;
|
||||||
// base_sink_class->unlock = QVideoSurfaceGstSink::unlock; // Not implemented.
|
// base_sink_class->unlock = QVideoSurfaceGstSink::unlock; // Not implemented.
|
||||||
// base_sink_class->event = QVideoSurfaceGstSink::event; // Not implemented.
|
base_sink_class->event = QVideoSurfaceGstSink::event;
|
||||||
base_sink_class->preroll = QVideoSurfaceGstSink::preroll;
|
base_sink_class->preroll = QVideoSurfaceGstSink::preroll;
|
||||||
base_sink_class->render = QVideoSurfaceGstSink::render;
|
base_sink_class->render = QVideoSurfaceGstSink::render;
|
||||||
|
|
||||||
@@ -664,6 +685,26 @@ QVideoSurfaceFormat QVideoSurfaceGstSink::formatForCaps(GstCaps *caps, int *byte
|
|||||||
return QVideoSurfaceFormat();
|
return QVideoSurfaceFormat();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QVideoSurfaceGstSink::handleShowPrerollChange(GObject *o, GParamSpec *p, gpointer d)
|
||||||
|
{
|
||||||
|
Q_UNUSED(o);
|
||||||
|
Q_UNUSED(p);
|
||||||
|
QVideoSurfaceGstSink *sink = reinterpret_cast<QVideoSurfaceGstSink *>(d);
|
||||||
|
|
||||||
|
gboolean value = true; // "show-preroll-frame" property is true by default
|
||||||
|
g_object_get(G_OBJECT(sink), "show-preroll-frame", &value, NULL);
|
||||||
|
|
||||||
|
GstBuffer *buffer = sink->delegate->lastPrerolledBuffer();
|
||||||
|
// Render the stored prerolled buffer if requested.
|
||||||
|
// e.g. player is in stopped mode, then seek operation is requested,
|
||||||
|
// surface now stores a prerolled frame, but doesn't display it until
|
||||||
|
// "show-preroll-frame" property is set to "true"
|
||||||
|
// when switching to pause or playing state.
|
||||||
|
if (value && buffer) {
|
||||||
|
sink->delegate->render(buffer);
|
||||||
|
sink->delegate->setLastPrerolledBuffer(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GstFlowReturn QVideoSurfaceGstSink::buffer_alloc(
|
GstFlowReturn QVideoSurfaceGstSink::buffer_alloc(
|
||||||
GstBaseSink *base, guint64 offset, guint size, GstCaps *caps, GstBuffer **buffer)
|
GstBaseSink *base, guint64 offset, guint size, GstCaps *caps, GstBuffer **buffer)
|
||||||
@@ -781,8 +822,11 @@ gboolean QVideoSurfaceGstSink::unlock(GstBaseSink *base)
|
|||||||
|
|
||||||
gboolean QVideoSurfaceGstSink::event(GstBaseSink *base, GstEvent *event)
|
gboolean QVideoSurfaceGstSink::event(GstBaseSink *base, GstEvent *event)
|
||||||
{
|
{
|
||||||
Q_UNUSED(base);
|
// discard prerolled frame
|
||||||
Q_UNUSED(event);
|
if (event->type == GST_EVENT_FLUSH_START) {
|
||||||
|
VO_SINK(base);
|
||||||
|
sink->delegate->setLastPrerolledBuffer(0);
|
||||||
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -790,12 +834,23 @@ gboolean QVideoSurfaceGstSink::event(GstBaseSink *base, GstEvent *event)
|
|||||||
GstFlowReturn QVideoSurfaceGstSink::preroll(GstBaseSink *base, GstBuffer *buffer)
|
GstFlowReturn QVideoSurfaceGstSink::preroll(GstBaseSink *base, GstBuffer *buffer)
|
||||||
{
|
{
|
||||||
VO_SINK(base);
|
VO_SINK(base);
|
||||||
return sink->delegate->render(buffer);
|
|
||||||
|
gboolean value = true; // "show-preroll-frame" property is true by default
|
||||||
|
g_object_get(G_OBJECT(base), "show-preroll-frame", &value, NULL);
|
||||||
|
if (value) {
|
||||||
|
sink->delegate->setLastPrerolledBuffer(0); // discard prerolled buffer
|
||||||
|
return sink->delegate->render(buffer); // display frame
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise keep a reference to the buffer to display it later
|
||||||
|
sink->delegate->setLastPrerolledBuffer(buffer);
|
||||||
|
return GST_FLOW_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
GstFlowReturn QVideoSurfaceGstSink::render(GstBaseSink *base, GstBuffer *buffer)
|
GstFlowReturn QVideoSurfaceGstSink::render(GstBaseSink *base, GstBuffer *buffer)
|
||||||
{
|
{
|
||||||
VO_SINK(base);
|
VO_SINK(base);
|
||||||
|
sink->delegate->setLastPrerolledBuffer(0); // discard prerolled buffer
|
||||||
return sink->delegate->render(buffer);
|
return sink->delegate->render(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,9 @@ public:
|
|||||||
|
|
||||||
GstFlowReturn render(GstBuffer *buffer);
|
GstFlowReturn render(GstBuffer *buffer);
|
||||||
|
|
||||||
|
GstBuffer *lastPrerolledBuffer() const { return m_lastPrerolledBuffer; }
|
||||||
|
void setLastPrerolledBuffer(GstBuffer *lastPrerolledBuffer); // set prerolledBuffer to 0 to discard prerolled buffer
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void queuedStart();
|
void queuedStart();
|
||||||
void queuedStop();
|
void queuedStop();
|
||||||
@@ -118,6 +121,8 @@ private:
|
|||||||
QVideoSurfaceFormat m_format;
|
QVideoSurfaceFormat m_format;
|
||||||
QVideoFrame m_frame;
|
QVideoFrame m_frame;
|
||||||
GstFlowReturn m_renderReturn;
|
GstFlowReturn m_renderReturn;
|
||||||
|
// this pointer is not 0 when there is a prerolled buffer waiting to be displayed
|
||||||
|
GstBuffer *m_lastPrerolledBuffer;
|
||||||
int m_bytesPerLine;
|
int m_bytesPerLine;
|
||||||
bool m_started;
|
bool m_started;
|
||||||
bool m_startCanceled;
|
bool m_startCanceled;
|
||||||
@@ -131,6 +136,8 @@ public:
|
|||||||
static QVideoSurfaceGstSink *createSink(QAbstractVideoSurface *surface);
|
static QVideoSurfaceGstSink *createSink(QAbstractVideoSurface *surface);
|
||||||
static QVideoSurfaceFormat formatForCaps(GstCaps *caps, int *bytesPerLine = 0);
|
static QVideoSurfaceFormat formatForCaps(GstCaps *caps, int *bytesPerLine = 0);
|
||||||
|
|
||||||
|
static void handleShowPrerollChange(GObject *o, GParamSpec *p, gpointer d);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static GType get_type();
|
static GType get_type();
|
||||||
static void class_init(gpointer g_class, gpointer class_data);
|
static void class_init(gpointer g_class, gpointer class_data);
|
||||||
|
|||||||
@@ -259,6 +259,10 @@ void QGstreamerPlayerControl::playOrPause(QMediaPlayer::State newState)
|
|||||||
|
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
|
|
||||||
|
// show prerolled frame if switching from stopped state
|
||||||
|
if (newState != QMediaPlayer::StoppedState && m_state == QMediaPlayer::StoppedState && m_pendingSeekPosition == -1)
|
||||||
|
m_session->showPrerollFrames(true);
|
||||||
|
|
||||||
//To prevent displaying the first video frame when playback is resumed
|
//To prevent displaying the first video frame when playback is resumed
|
||||||
//the pipeline is paused instead of playing, seeked to requested position,
|
//the pipeline is paused instead of playing, seeked to requested position,
|
||||||
//and after seeking is finished (position updated) playback is restarted
|
//and after seeking is finished (position updated) playback is restarted
|
||||||
@@ -299,6 +303,7 @@ void QGstreamerPlayerControl::stop()
|
|||||||
|
|
||||||
if (m_state != QMediaPlayer::StoppedState) {
|
if (m_state != QMediaPlayer::StoppedState) {
|
||||||
m_state = QMediaPlayer::StoppedState;
|
m_state = QMediaPlayer::StoppedState;
|
||||||
|
m_session->showPrerollFrames(false); // stop showing prerolled frames in stop state
|
||||||
if (m_resources->isGranted())
|
if (m_resources->isGranted())
|
||||||
m_session->pause();
|
m_session->pause();
|
||||||
|
|
||||||
@@ -342,7 +347,7 @@ void QGstreamerPlayerControl::setMedia(const QMediaContent &content, QIODevice *
|
|||||||
m_state = QMediaPlayer::StoppedState;
|
m_state = QMediaPlayer::StoppedState;
|
||||||
QMediaContent oldMedia = m_currentResource;
|
QMediaContent oldMedia = m_currentResource;
|
||||||
m_pendingSeekPosition = -1;
|
m_pendingSeekPosition = -1;
|
||||||
m_session->showPrerollFrames(true);
|
m_session->showPrerollFrames(false); // do not show prerolled frames until pause() or play() explicitly called
|
||||||
|
|
||||||
if (!content.isNull() || stream) {
|
if (!content.isNull() || stream) {
|
||||||
if (!m_resources->isRequested() && !m_resources->isGranted())
|
if (!m_resources->isRequested() && !m_resources->isGranted())
|
||||||
@@ -767,7 +772,8 @@ void QGstreamerPlayerControl::updatePosition(qint64 pos)
|
|||||||
//seek request is complete, it's safe to resume playback
|
//seek request is complete, it's safe to resume playback
|
||||||
//with prerolled frame displayed
|
//with prerolled frame displayed
|
||||||
m_pendingSeekPosition = -1;
|
m_pendingSeekPosition = -1;
|
||||||
m_session->showPrerollFrames(true);
|
if (m_state != QMediaPlayer::StoppedState)
|
||||||
|
m_session->showPrerollFrames(true);
|
||||||
if (m_state == QMediaPlayer::PlayingState) {
|
if (m_state == QMediaPlayer::PlayingState) {
|
||||||
m_session->play();
|
m_session->play();
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
tests/auto/integration/qmediaplayerbackend/testdata/colors.mp4
vendored
Normal file
BIN
tests/auto/integration/qmediaplayerbackend/testdata/colors.mp4
vendored
Normal file
Binary file not shown.
@@ -41,6 +41,7 @@
|
|||||||
|
|
||||||
#include <QtTest/QtTest>
|
#include <QtTest/QtTest>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <qabstractvideosurface.h>
|
||||||
#include "qmediaservice.h"
|
#include "qmediaservice.h"
|
||||||
#include "qmediaplayer.h"
|
#include "qmediaplayer.h"
|
||||||
|
|
||||||
@@ -76,12 +77,38 @@ private slots:
|
|||||||
void volumeAndMuted();
|
void volumeAndMuted();
|
||||||
void volumeAcrossFiles_data();
|
void volumeAcrossFiles_data();
|
||||||
void volumeAcrossFiles();
|
void volumeAcrossFiles();
|
||||||
|
void seekPauseSeek();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//one second local wav file
|
//one second local wav file
|
||||||
QMediaContent localWavFile;
|
QMediaContent localWavFile;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is a simple video surface which records all presented frames.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class TestVideoSurface : public QAbstractVideoSurface
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit TestVideoSurface() { }
|
||||||
|
|
||||||
|
//video surface
|
||||||
|
QList<QVideoFrame::PixelFormat> supportedPixelFormats(
|
||||||
|
QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const;
|
||||||
|
|
||||||
|
bool start(const QVideoSurfaceFormat &format);
|
||||||
|
void stop();
|
||||||
|
bool present(const QVideoFrame &frame);
|
||||||
|
|
||||||
|
QList<QVideoFrame>& frameList() { return m_frameList; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<QVideoFrame> m_frameList;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
void tst_QMediaPlayerBackend::init()
|
void tst_QMediaPlayerBackend::init()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -409,6 +436,104 @@ void tst_QMediaPlayerBackend::volumeAcrossFiles()
|
|||||||
QCOMPARE(player.isMuted(), muted);
|
QCOMPARE(player.isMuted(), muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_QMediaPlayerBackend::seekPauseSeek()
|
||||||
|
{
|
||||||
|
QMediaPlayer player;
|
||||||
|
|
||||||
|
QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64)));
|
||||||
|
|
||||||
|
TestVideoSurface *surface = new TestVideoSurface;
|
||||||
|
player.setVideoOutput(surface);
|
||||||
|
|
||||||
|
QFileInfo videoFile(QLatin1String(TESTDATA_DIR "testdata/colors.mp4"));
|
||||||
|
QVERIFY(videoFile.exists());
|
||||||
|
|
||||||
|
player.setMedia(QUrl::fromLocalFile(videoFile.absoluteFilePath()));
|
||||||
|
QCOMPARE(player.state(), QMediaPlayer::StoppedState);
|
||||||
|
QVERIFY(surface->frameList().isEmpty()); // frame must not appear until we call pause() or play()
|
||||||
|
|
||||||
|
positionSpy.clear();
|
||||||
|
player.setPosition((qint64)7000);
|
||||||
|
QTRY_VERIFY(!positionSpy.isEmpty() && qAbs(player.position() - (qint64)7000) < (qint64)500);
|
||||||
|
QCOMPARE(player.state(), QMediaPlayer::StoppedState);
|
||||||
|
QTest::qWait(250); // wait a bit to ensure the frame is not rendered
|
||||||
|
QVERIFY(surface->frameList().isEmpty()); // still no frame, we must call pause() or play() to see a frame
|
||||||
|
|
||||||
|
player.pause();
|
||||||
|
QTRY_COMPARE(player.state(), QMediaPlayer::PausedState); // it might take some time for the operation to be completed
|
||||||
|
QTRY_COMPARE(surface->frameList().size(), 1); // we must see a frame at position 7000 here
|
||||||
|
|
||||||
|
{
|
||||||
|
QVideoFrame frame = surface->frameList().back();
|
||||||
|
QVERIFY(qAbs(frame.startTime() - (qint64)7000) < (qint64)500);
|
||||||
|
QCOMPARE(frame.width(), 160);
|
||||||
|
QCOMPARE(frame.height(), 120);
|
||||||
|
|
||||||
|
// create QImage for QVideoFrame to verify RGB pixel colors
|
||||||
|
QVERIFY(frame.map(QAbstractVideoBuffer::ReadOnly));
|
||||||
|
QImage image(frame.bits(), frame.width(), frame.height(), QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat()));
|
||||||
|
QVERIFY(!image.isNull());
|
||||||
|
QVERIFY(qRed(image.pixel(0, 0)) >= 240); // conversion from YUV => RGB, that's why it's not 255
|
||||||
|
QCOMPARE(qGreen(image.pixel(0, 0)), 0);
|
||||||
|
QCOMPARE(qBlue(image.pixel(0, 0)), 0);
|
||||||
|
frame.unmap();
|
||||||
|
}
|
||||||
|
|
||||||
|
positionSpy.clear();
|
||||||
|
player.setPosition((qint64)12000);
|
||||||
|
QTRY_VERIFY(!positionSpy.isEmpty() && qAbs(player.position() - (qint64)12000) < (qint64)500);
|
||||||
|
QCOMPARE(player.state(), QMediaPlayer::PausedState);
|
||||||
|
QCOMPARE(surface->frameList().size(), 2);
|
||||||
|
|
||||||
|
{
|
||||||
|
QVideoFrame frame = surface->frameList().back();
|
||||||
|
QVERIFY(qAbs(frame.startTime() - (qint64)12000) < (qint64)500);
|
||||||
|
QCOMPARE(frame.width(), 160);
|
||||||
|
QCOMPARE(frame.height(), 120);
|
||||||
|
|
||||||
|
QVERIFY(frame.map(QAbstractVideoBuffer::ReadOnly));
|
||||||
|
QImage image(frame.bits(), frame.width(), frame.height(), QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat()));
|
||||||
|
QVERIFY(!image.isNull());
|
||||||
|
QCOMPARE(qRed(image.pixel(0, 0)), 0);
|
||||||
|
QVERIFY(qGreen(image.pixel(0, 0)) >= 240);
|
||||||
|
QCOMPARE(qBlue(image.pixel(0, 0)), 0);
|
||||||
|
frame.unmap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QVideoFrame::PixelFormat> TestVideoSurface::supportedPixelFormats(
|
||||||
|
QAbstractVideoBuffer::HandleType handleType) const
|
||||||
|
{
|
||||||
|
if (handleType == QAbstractVideoBuffer::NoHandle) {
|
||||||
|
return QList<QVideoFrame::PixelFormat>()
|
||||||
|
<< QVideoFrame::Format_RGB32
|
||||||
|
<< QVideoFrame::Format_ARGB32
|
||||||
|
<< QVideoFrame::Format_ARGB32_Premultiplied
|
||||||
|
<< QVideoFrame::Format_RGB565
|
||||||
|
<< QVideoFrame::Format_RGB555;
|
||||||
|
} else {
|
||||||
|
return QList<QVideoFrame::PixelFormat>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestVideoSurface::start(const QVideoSurfaceFormat &format)
|
||||||
|
{
|
||||||
|
if (!isFormatSupported(format)) return false;
|
||||||
|
|
||||||
|
return QAbstractVideoSurface::start(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestVideoSurface::stop()
|
||||||
|
{
|
||||||
|
QAbstractVideoSurface::stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestVideoSurface::present(const QVideoFrame &frame)
|
||||||
|
{
|
||||||
|
m_frameList.push_back(frame);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
QTEST_MAIN(tst_QMediaPlayerBackend)
|
QTEST_MAIN(tst_QMediaPlayerBackend)
|
||||||
#include "tst_qmediaplayerbackend.moc"
|
#include "tst_qmediaplayerbackend.moc"
|
||||||
|
|||||||
Reference in New Issue
Block a user