| /**************************************************************************** |
| ** |
| ** 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(¤tTime); |
| 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 |