blob: 4b7afc266f4743503d4f60e523495554e0fc463e [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "videosurfacefilter.h"
#include "directshoweventloop.h"
#include "directshowglobal.h"
#include "directshowvideobuffer.h"
#include <QtCore/qthread.h>
#include <QtCore/qloggingcategory.h>
#include <qabstractvideosurface.h>
#include <mutex>
#include <initguid.h>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(qLcRenderFilter, "qt.multimedia.plugins.directshow.renderfilter")
// { e23cad72-153d-406c-bf3f-4c4b523d96f2 }
DEFINE_GUID(CLSID_VideoSurfaceFilter,
0xe23cad72, 0x153d, 0x406c, 0xbf, 0x3f, 0x4c, 0x4b, 0x52, 0x3d, 0x96, 0xf2);
class VideoSurfaceInputPin : public DirectShowInputPin
{
COM_REF_MIXIN
public:
VideoSurfaceInputPin(VideoSurfaceFilter *filter);
STDMETHODIMP QueryInterface(REFIID riid, void **ppv) override;
bool isMediaTypeSupported(const AM_MEDIA_TYPE *type) override;
bool setMediaType(const AM_MEDIA_TYPE *type) override;
HRESULT completeConnection(IPin *pin) override;
HRESULT connectionEnded() override;
// IPin
STDMETHODIMP ReceiveConnection(IPin *pConnector, const AM_MEDIA_TYPE *pmt) override;
STDMETHODIMP Disconnect() override;
STDMETHODIMP EndOfStream() override;
STDMETHODIMP BeginFlush() override;
STDMETHODIMP EndFlush() override;
// IMemInputPin
STDMETHODIMP GetAllocatorRequirements(ALLOCATOR_PROPERTIES *pProps) override;
STDMETHODIMP Receive(IMediaSample *pMediaSample) override;
private:
VideoSurfaceFilter *m_videoSurfaceFilter;
};
VideoSurfaceInputPin::VideoSurfaceInputPin(VideoSurfaceFilter *filter)
: DirectShowInputPin(filter, QStringLiteral("Input"))
, m_videoSurfaceFilter(filter)
{
}
HRESULT VideoSurfaceInputPin::QueryInterface(REFIID riid, void **ppv)
{
if (ppv == nullptr)
return E_POINTER;
if (riid == IID_IUnknown)
*ppv = static_cast<IUnknown *>(static_cast<DirectShowPin *>(this));
else if (riid == IID_IPin)
*ppv = static_cast<IPin *>(this);
else if (riid == IID_IMemInputPin)
*ppv =static_cast<IMemInputPin*>(this);
else
return E_NOINTERFACE;
AddRef();
return S_OK;
}
bool VideoSurfaceInputPin::isMediaTypeSupported(const AM_MEDIA_TYPE *type)
{
return m_videoSurfaceFilter->isMediaTypeSupported(type);
}
bool VideoSurfaceInputPin::setMediaType(const AM_MEDIA_TYPE *type)
{
if (!DirectShowInputPin::setMediaType(type))
return false;
return m_videoSurfaceFilter->setMediaType(type);
}
HRESULT VideoSurfaceInputPin::completeConnection(IPin *pin)
{
HRESULT hr = DirectShowInputPin::completeConnection(pin);
if (FAILED(hr))
return hr;
return m_videoSurfaceFilter->completeConnection(pin);
}
HRESULT VideoSurfaceInputPin::connectionEnded()
{
HRESULT hr = DirectShowInputPin::connectionEnded();
if (FAILED(hr))
return hr;
return m_videoSurfaceFilter->connectionEnded();
}
HRESULT VideoSurfaceInputPin::ReceiveConnection(IPin *pConnector, const AM_MEDIA_TYPE *pmt)
{
QMutexLocker lock(&m_videoSurfaceFilter->m_mutex);
return DirectShowInputPin::ReceiveConnection(pConnector, pmt);
}
HRESULT VideoSurfaceInputPin::Disconnect()
{
QMutexLocker lock(&m_videoSurfaceFilter->m_mutex);
return DirectShowInputPin::Disconnect();
}
HRESULT VideoSurfaceInputPin::EndOfStream()
{
QMutexLocker lock(&m_videoSurfaceFilter->m_mutex);
const std::lock_guard<QRecursiveMutex> renderLocker(m_videoSurfaceFilter->m_renderMutex);
HRESULT hr = DirectShowInputPin::EndOfStream();
if (hr != S_OK)
return hr;
return m_videoSurfaceFilter->EndOfStream();
}
HRESULT VideoSurfaceInputPin::BeginFlush()
{
QMutexLocker lock(&m_videoSurfaceFilter->m_mutex);
{
const std::lock_guard<QRecursiveMutex> renderLocker(m_videoSurfaceFilter->m_renderMutex);
DirectShowInputPin::BeginFlush();
m_videoSurfaceFilter->BeginFlush();
}
m_videoSurfaceFilter->resetEOS();
return S_OK;
}
HRESULT VideoSurfaceInputPin::EndFlush()
{
QMutexLocker lock(&m_videoSurfaceFilter->m_mutex);
const std::lock_guard<QRecursiveMutex> renderLocker(m_videoSurfaceFilter->m_renderMutex);
HRESULT hr = m_videoSurfaceFilter->EndFlush();
if (SUCCEEDED(hr))
hr = DirectShowInputPin::EndFlush();
return hr;
}
HRESULT VideoSurfaceInputPin::GetAllocatorRequirements(ALLOCATOR_PROPERTIES *pProps)
{
if (!pProps)
return E_POINTER;
// We need at least two allocated buffers, one for holding the frame currently being
// rendered and another one to decode the following frame at the same time.
pProps->cBuffers = 2;
return S_OK;
}
HRESULT VideoSurfaceInputPin::Receive(IMediaSample *pMediaSample)
{
HRESULT hr = m_videoSurfaceFilter->Receive(pMediaSample);
if (FAILED(hr)) {
QMutexLocker locker(&m_videoSurfaceFilter->m_mutex);
if (m_videoSurfaceFilter->state() != State_Stopped && !m_flushing && !m_inErrorState) {
m_videoSurfaceFilter->NotifyEvent(EC_ERRORABORT, hr, 0);
{
const std::lock_guard<QRecursiveMutex> renderLocker(m_videoSurfaceFilter->m_renderMutex);
if (m_videoSurfaceFilter->m_running && !m_videoSurfaceFilter->m_EOSDelivered)
m_videoSurfaceFilter->notifyEOS();
}
m_inErrorState = true;
}
}
return hr;
}
VideoSurfaceFilter::VideoSurfaceFilter(QAbstractVideoSurface *surface, DirectShowEventLoop *loop, QObject *parent)
: QObject(parent)
, m_loop(loop)
, m_surface(surface)
, m_renderEvent(CreateEvent(nullptr, FALSE, FALSE, nullptr))
, m_flushEvent(CreateEvent(nullptr, TRUE, FALSE, nullptr))
{
supportedFormatsChanged();
connect(surface, &QAbstractVideoSurface::supportedFormatsChanged,
this, &VideoSurfaceFilter::supportedFormatsChanged);
}
VideoSurfaceFilter::~VideoSurfaceFilter()
{
clearPendingSample();
if (m_pin)
m_pin->Release();
CloseHandle(m_flushEvent);
CloseHandle(m_renderEvent);
}
HRESULT VideoSurfaceFilter::QueryInterface(REFIID riid, void **ppv)
{
if (ppv == nullptr)
return E_POINTER;
if (riid == IID_IUnknown)
*ppv = static_cast<IUnknown *>(static_cast<DirectShowBaseFilter *>(this));
else if (riid == IID_IPersist || riid == IID_IMediaFilter || riid == IID_IBaseFilter)
*ppv = static_cast<IBaseFilter *>(this);
else if (riid == IID_IAMFilterMiscFlags)
*ppv = static_cast<IAMFilterMiscFlags *>(this);
else
return E_NOINTERFACE;
AddRef();
return S_OK;
}
QList<DirectShowPin *> VideoSurfaceFilter::pins()
{
if (!m_pin)
m_pin = new VideoSurfaceInputPin(this);
return QList<DirectShowPin *>() << m_pin;
}
HRESULT VideoSurfaceFilter::GetClassID(CLSID *pClassID)
{
*pClassID = CLSID_VideoSurfaceFilter;
return S_OK;
}
ULONG VideoSurfaceFilter::GetMiscFlags()
{
return AM_FILTER_MISC_FLAGS_IS_RENDERER;
}
void VideoSurfaceFilter::supportedFormatsChanged()
{
QWriteLocker writeLocker(&m_typesLock);
qCDebug(qLcRenderFilter, "supportedFormatChanged");
m_supportedTypes.clear();
const QList<QVideoFrame::PixelFormat> formats = m_surface->supportedPixelFormats();
m_supportedTypes.reserve(formats.count());
for (QVideoFrame::PixelFormat format : formats) {
GUID subtype = DirectShowMediaType::convertPixelFormat(format);
if (!IsEqualGUID(subtype, MEDIASUBTYPE_None)) {
qCDebug(qLcRenderFilter) << " " << format;
m_supportedTypes.append(subtype);
}
}
}
bool VideoSurfaceFilter::isMediaTypeSupported(const AM_MEDIA_TYPE *type)
{
if (type->majortype != MEDIATYPE_Video || type->bFixedSizeSamples == FALSE)
return false;
QReadLocker readLocker(&m_typesLock);
for (const GUID &supportedType : m_supportedTypes) {
if (IsEqualGUID(supportedType, type->subtype))
return true;
}
return false;
}
bool VideoSurfaceFilter::setMediaType(const AM_MEDIA_TYPE *type)
{
if (!type) {
qCDebug(qLcRenderFilter, "clear media type");
m_surfaceFormat = QVideoSurfaceFormat();
m_bytesPerLine = 0;
return true;
}
m_surfaceFormat = DirectShowMediaType::videoFormatFromType(type);
m_bytesPerLine = DirectShowMediaType::bytesPerLine(m_surfaceFormat);
qCDebug(qLcRenderFilter) << "setMediaType -->" << m_surfaceFormat;
return m_surfaceFormat.isValid();
}
HRESULT VideoSurfaceFilter::completeConnection(IPin *pin)
{
Q_UNUSED(pin);
qCDebug(qLcRenderFilter, "completeConnection");
return startSurface() ? S_OK : VFW_E_TYPE_NOT_ACCEPTED;
}
HRESULT VideoSurfaceFilter::connectionEnded()
{
qCDebug(qLcRenderFilter, "connectionEnded");
stopSurface();
return S_OK;
}
HRESULT VideoSurfaceFilter::Run(REFERENCE_TIME tStart)
{
QMutexLocker locker(&m_mutex);
if (m_state == State_Running)
return S_OK;
qCDebug(qLcRenderFilter, "Run (start=%lli)", tStart);
HRESULT hr = DirectShowBaseFilter::Run(tStart);
if (FAILED(hr))
return hr;
ResetEvent(m_flushEvent);
IMemAllocator *allocator;
if (SUCCEEDED(m_pin->GetAllocator(&allocator))) {
allocator->Commit();
allocator->Release();
}
const std::lock_guard<QRecursiveMutex> renderLocker(m_renderMutex);
m_running = true;
if (!m_pendingSample)
checkEOS();
else if (!scheduleSample(m_pendingSample))
SetEvent(m_renderEvent); // render immediately
return S_OK;
}
HRESULT VideoSurfaceFilter::Pause()
{
QMutexLocker locker(&m_mutex);
if (m_state == State_Paused)
return S_OK;
qCDebug(qLcRenderFilter, "Pause");
HRESULT hr = DirectShowBaseFilter::Pause();
if (FAILED(hr))
return hr;
m_renderMutex.lock();
m_EOSDelivered = false;
m_running = false;
m_renderMutex.unlock();
resetEOSTimer();
ResetEvent(m_flushEvent);
unscheduleSample();
IMemAllocator *allocator;
if (SUCCEEDED(m_pin->GetAllocator(&allocator))) {
allocator->Commit();
allocator->Release();
}
return S_OK;
}
HRESULT VideoSurfaceFilter::Stop()
{
QMutexLocker locker(&m_mutex);
if (m_state == State_Stopped)
return S_OK;
qCDebug(qLcRenderFilter, "Stop");
DirectShowBaseFilter::Stop();
clearPendingSample();
m_renderMutex.lock();
m_EOSDelivered = false;
m_running = false;
m_renderMutex.unlock();
SetEvent(m_flushEvent);
resetEOS();
unscheduleSample();
flushSurface();
IMemAllocator *allocator;
if (SUCCEEDED(m_pin->GetAllocator(&allocator))) {
allocator->Decommit();
allocator->Release();
}
return S_OK;
}
HRESULT VideoSurfaceFilter::EndOfStream()
{
const std::lock_guard<QRecursiveMutex> renderLocker(m_renderMutex);
qCDebug(qLcRenderFilter, "EndOfStream");
m_EOS = true;
if (!m_pendingSample && m_running)
checkEOS();
stopSurface();
return S_OK;
}
HRESULT VideoSurfaceFilter::BeginFlush()
{
qCDebug(qLcRenderFilter, "BeginFlush");
SetEvent(m_flushEvent);
unscheduleSample();
clearPendingSample();
return S_OK;
}
HRESULT VideoSurfaceFilter::EndFlush()
{
qCDebug(qLcRenderFilter, "EndFlush");
ResetEvent(m_flushEvent);
return S_OK;
}
HRESULT VideoSurfaceFilter::Receive(IMediaSample *pMediaSample)
{
{
QMutexLocker locker(&m_mutex);
qCDebug(qLcRenderFilter, "Receive (sample=%p)", pMediaSample);
HRESULT hr = m_pin->DirectShowInputPin::Receive(pMediaSample);
if (hr != S_OK) {
qCDebug(qLcRenderFilter, " can't receive sample (error %X)", uint(hr));
return E_FAIL;
}
// If the format dynamically changed, the sample contains information about the new format.
// We need to reset the format and restart the QAbstractVideoSurface.
if (m_pin->currentSampleProperties()->pMediaType
&& (!m_pin->setMediaType(m_pin->currentSampleProperties()->pMediaType)
|| !restartSurface())) {
qCWarning(qLcRenderFilter, " dynamic format change failed, aborting rendering");
NotifyEvent(EC_ERRORABORT, VFW_E_TYPE_NOT_ACCEPTED, 0);
return VFW_E_INVALIDMEDIATYPE;
}
{
const std::lock_guard<QRecursiveMutex> locker(m_renderMutex);
if (m_pendingSample || m_EOS)
return E_UNEXPECTED;
if (m_running && !scheduleSample(pMediaSample)) {
qCWarning(qLcRenderFilter, " sample can't be scheduled, discarding it");
return S_OK;
}
m_pendingSample = pMediaSample;
m_pendingSample->AddRef();
m_pendingSampleEndTime = m_pin->currentSampleProperties()->tStop;
}
if (m_state == State_Paused) // Render immediately
renderPendingSample();
}
qCDebug(qLcRenderFilter, " waiting for render time");
// Wait for render time. The clock will wake us up whenever the time comes.
// It can also be interrupted by a flush, pause or stop.
HANDLE waitObjects[] = { m_flushEvent, m_renderEvent };
DWORD result = WAIT_TIMEOUT;
while (result == WAIT_TIMEOUT)
result = WaitForMultipleObjects(2, waitObjects, FALSE, INFINITE);
if (result == WAIT_OBJECT_0) {
// render interrupted (flush, pause, stop)
qCDebug(qLcRenderFilter, " rendering of sample %p interrupted", pMediaSample);
return S_OK;
}
m_adviseCookie = 0;
QMutexLocker locker(&m_mutex);
// State might have changed just before the lock
if (m_state == State_Stopped) {
qCDebug(qLcRenderFilter, " state changed to Stopped, discarding sample (%p)", pMediaSample);
return S_OK;
}
std::unique_lock<QRecursiveMutex> renderLocker(m_renderMutex);
// Flush or pause might have happened just before the lock
if (m_pendingSample && m_running) {
renderLocker.unlock();
renderPendingSample();
renderLocker.lock();
} else {
qCDebug(qLcRenderFilter, " discarding sample (%p)", pMediaSample);
}
clearPendingSample();
checkEOS();
ResetEvent(m_renderEvent);
return S_OK;
}
bool VideoSurfaceFilter::scheduleSample(IMediaSample *sample)
{
if (!sample)
return false;
qCDebug(qLcRenderFilter, "scheduleSample (sample=%p)", sample);
REFERENCE_TIME sampleStart, sampleEnd;
if (FAILED(sample->GetTime(&sampleStart, &sampleEnd)) || !m_clock) {
qCDebug(qLcRenderFilter, " render now");
SetEvent(m_renderEvent); // Render immediately
return true;
}
if (sampleEnd < sampleStart) { // incorrect times
qCWarning(qLcRenderFilter, " invalid sample times (start=%lli, end=%lli)", sampleStart, sampleEnd);
return false;
}
HRESULT hr = m_clock->AdviseTime(m_startTime, sampleStart, (HEVENT)m_renderEvent, &m_adviseCookie);
if (FAILED(hr)) {
qCWarning(qLcRenderFilter, " clock failed to advise time (error=%X)", uint(hr));
return false;
}
return true;
}
void VideoSurfaceFilter::unscheduleSample()
{
if (m_adviseCookie) {
qCDebug(qLcRenderFilter, "unscheduleSample");
m_clock->Unadvise(m_adviseCookie);
m_adviseCookie = 0;
}
ResetEvent(m_renderEvent);
}
void VideoSurfaceFilter::clearPendingSample()
{
const std::lock_guard<QRecursiveMutex> locker(m_renderMutex);
if (m_pendingSample) {
qCDebug(qLcRenderFilter, "clearPendingSample");
m_pendingSample->Release();
m_pendingSample = nullptr;
}
}
void QT_WIN_CALLBACK EOSTimerCallback(UINT, UINT, DWORD_PTR dwUser, DWORD_PTR, DWORD_PTR)
{
VideoSurfaceFilter *that = reinterpret_cast<VideoSurfaceFilter *>(dwUser);
that->onEOSTimerTimeout();
}
void VideoSurfaceFilter::onEOSTimerTimeout()
{
const std::lock_guard<QRecursiveMutex> locker(m_renderMutex);
if (m_EOSTimer) {
m_EOSTimer = 0;
checkEOS();
}
}
void VideoSurfaceFilter::checkEOS()
{
const std::lock_guard<QRecursiveMutex> locker(m_renderMutex);
if (!m_EOS || m_EOSDelivered || m_EOSTimer)
return;
if (!m_clock) {
notifyEOS();
return;
}
REFERENCE_TIME eosTime = m_startTime + m_pendingSampleEndTime;
REFERENCE_TIME currentTime;
m_clock->GetTime(&currentTime);
LONG delay = LONG((eosTime - currentTime) / 10000);
if (delay < 1) {
notifyEOS();
} else {
qCDebug(qLcRenderFilter, "will trigger EOS in %li", delay);
m_EOSTimer = timeSetEvent(delay,
1,
EOSTimerCallback,
reinterpret_cast<DWORD_PTR>(this),
TIME_ONESHOT | TIME_CALLBACK_FUNCTION | TIME_KILL_SYNCHRONOUS);
if (!m_EOSTimer) {
qDebug("Error with timer");
notifyEOS();
}
}
}
void VideoSurfaceFilter::notifyEOS()
{
const std::lock_guard<QRecursiveMutex> locker(m_renderMutex);
if (!m_running)
return;
qCDebug(qLcRenderFilter, "notifyEOS, delivering EC_COMPLETE event");
m_EOSTimer = 0;
m_EOSDelivered = true;
NotifyEvent(EC_COMPLETE, S_OK, (LONG_PTR)(IBaseFilter *)this);
}
void VideoSurfaceFilter::resetEOS()
{
resetEOSTimer();
const std::lock_guard<QRecursiveMutex> locker(m_renderMutex);
if (m_EOS)
qCDebug(qLcRenderFilter, "resetEOS (delivered=%s)", m_EOSDelivered ? "true" : "false");
m_EOS = false;
m_EOSDelivered = false;
m_pendingSampleEndTime = 0;
}
void VideoSurfaceFilter::resetEOSTimer()
{
if (m_EOSTimer) {
timeKillEvent(m_EOSTimer);
m_EOSTimer = 0;
}
}
bool VideoSurfaceFilter::startSurface()
{
if (QThread::currentThread() != thread()) {
m_loop->postEvent(this, new QEvent(QEvent::Type(StartSurface)));
m_waitSurface.wait(&m_mutex);
return m_surfaceStarted;
}
m_surfaceStarted = m_surface->start(m_surfaceFormat);
qCDebug(qLcRenderFilter, "startSurface %s", m_surfaceStarted ? "succeeded" : "failed");
return m_surfaceStarted;
}
void VideoSurfaceFilter::stopSurface()
{
if (!m_surfaceStarted)
return;
if (QThread::currentThread() != thread()) {
m_loop->postEvent(this, new QEvent(QEvent::Type(StopSurface)));
m_waitSurface.wait(&m_mutex);
} else {
qCDebug(qLcRenderFilter, "stopSurface");
m_surface->stop();
m_surfaceStarted = false;
}
}
bool VideoSurfaceFilter::restartSurface()
{
if (QThread::currentThread() != thread()) {
m_loop->postEvent(this, new QEvent(QEvent::Type(RestartSurface)));
m_waitSurface.wait(&m_mutex);
return m_surfaceStarted;
}
m_surface->stop();
m_surfaceStarted = m_surface->start(m_surfaceFormat);
qCDebug(qLcRenderFilter, "restartSurface %s", m_surfaceStarted ? "succeeded" : "failed");
return m_surfaceStarted;
}
void VideoSurfaceFilter::flushSurface()
{
if (QThread::currentThread() != thread()) {
m_loop->postEvent(this, new QEvent(QEvent::Type(FlushSurface)));
m_waitSurface.wait(&m_mutex);
} else {
qCDebug(qLcRenderFilter, "flushSurface");
m_surface->present(QVideoFrame());
}
}
void VideoSurfaceFilter::renderPendingSample()
{
if (QThread::currentThread() != thread()) {
m_loop->postEvent(this, new QEvent(QEvent::Type(RenderSample)));
m_waitSurface.wait(&m_mutex);
} else {
const std::lock_guard<QRecursiveMutex> locker(m_renderMutex);
if (!m_pendingSample)
return;
qCDebug(qLcRenderFilter, "presentSample (sample=%p)", m_pendingSample);
m_surface->present(QVideoFrame(new DirectShowVideoBuffer(m_pendingSample, m_bytesPerLine),
m_surfaceFormat.frameSize(),
m_surfaceFormat.pixelFormat()));
}
}
bool VideoSurfaceFilter::event(QEvent *e)
{
switch (e->type()) {
case StartSurface: {
QMutexLocker locker(&m_mutex);
startSurface();
m_waitSurface.wakeAll();
return true;
}
case StopSurface: {
QMutexLocker locker(&m_mutex);
stopSurface();
m_waitSurface.wakeAll();
return true;
}
case RestartSurface: {
QMutexLocker locker(&m_mutex);
restartSurface();
m_waitSurface.wakeAll();
return true;
}
case FlushSurface: {
QMutexLocker locker(&m_mutex);
flushSurface();
m_waitSurface.wakeAll();
return true;
}
case RenderSample: {
QMutexLocker locker(&m_mutex);
renderPendingSample();
m_waitSurface.wakeAll();
return true;
}
default:
break;
}
return QObject::event(e);
}
QT_END_NAMESPACE