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>
: m_surface(surface)
, m_pool(0)
, m_renderReturn(GST_FLOW_ERROR)
+ , m_lastPrerolledBuffer(0)
, m_bytesPerLine(0)
, m_startCanceled(false)
{
QVideoSurfaceGstDelegate::~QVideoSurfaceGstDelegate()
{
qDeleteAll(m_pools);
+ setLastPrerolledBuffer(0);
}
QList<QVideoFrame::PixelFormat> QVideoSurfaceGstDelegate::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const
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()
{
if (!m_startCanceled) {
sink->delegate = new QVideoSurfaceGstDelegate(surface);
+ g_signal_connect(G_OBJECT(sink), "notify::show-preroll-frame", G_CALLBACK(handleShowPrerollChange), sink);
+
return sink;
}
base_sink_class->start = QVideoSurfaceGstSink::start;
base_sink_class->stop = QVideoSurfaceGstSink::stop;
// 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->render = QVideoSurfaceGstSink::render;
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(
GstBaseSink *base, guint64 offset, guint size, GstCaps *caps, GstBuffer **buffer)
gboolean QVideoSurfaceGstSink::event(GstBaseSink *base, GstEvent *event)
{
- Q_UNUSED(base);
- Q_UNUSED(event);
+ // discard prerolled frame
+ if (event->type == GST_EVENT_FLUSH_START) {
+ VO_SINK(base);
+ sink->delegate->setLastPrerolledBuffer(0);
+ }
return TRUE;
}
GstFlowReturn QVideoSurfaceGstSink::preroll(GstBaseSink *base, GstBuffer *buffer)
{
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)
{
VO_SINK(base);
+ sink->delegate->setLastPrerolledBuffer(0); // discard prerolled buffer
return sink->delegate->render(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:
void queuedStart();
void queuedStop();
QVideoSurfaceFormat m_format;
QVideoFrame m_frame;
GstFlowReturn m_renderReturn;
+ // this pointer is not 0 when there is a prerolled buffer waiting to be displayed
+ GstBuffer *m_lastPrerolledBuffer;
int m_bytesPerLine;
bool m_started;
bool m_startCanceled;
static QVideoSurfaceGstSink *createSink(QAbstractVideoSurface *surface);
static QVideoSurfaceFormat formatForCaps(GstCaps *caps, int *bytesPerLine = 0);
+ static void handleShowPrerollChange(GObject *o, GParamSpec *p, gpointer d);
+
private:
static GType get_type();
static void class_init(gpointer g_class, gpointer class_data);
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
//the pipeline is paused instead of playing, seeked to requested position,
//and after seeking is finished (position updated) playback is restarted
if (m_state != QMediaPlayer::StoppedState) {
m_state = QMediaPlayer::StoppedState;
+ m_session->showPrerollFrames(false); // stop showing prerolled frames in stop state
if (m_resources->isGranted())
m_session->pause();
m_state = QMediaPlayer::StoppedState;
QMediaContent oldMedia = m_currentResource;
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 (!m_resources->isRequested() && !m_resources->isGranted())
//seek request is complete, it's safe to resume playback
//with prerolled frame displayed
m_pendingSeekPosition = -1;
- m_session->showPrerollFrames(true);
+ if (m_state != QMediaPlayer::StoppedState)
+ m_session->showPrerollFrames(true);
if (m_state == QMediaPlayer::PlayingState) {
m_session->play();
}
#include <QtTest/QtTest>
#include <QDebug>
+#include <qabstractvideosurface.h>
#include "qmediaservice.h"
#include "qmediaplayer.h"
void volumeAndMuted();
void volumeAcrossFiles_data();
void volumeAcrossFiles();
+ void seekPauseSeek();
private:
//one second local wav file
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()
{
}
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)
#include "tst_qmediaplayerbackend.moc"