diff options
Diffstat (limited to 'src/plugins/multimedia/windows/evr/evrcustompresenter.cpp')
-rw-r--r-- | src/plugins/multimedia/windows/evr/evrcustompresenter.cpp | 612 |
1 files changed, 224 insertions, 388 deletions
diff --git a/src/plugins/multimedia/windows/evr/evrcustompresenter.cpp b/src/plugins/multimedia/windows/evr/evrcustompresenter.cpp index e8b99a09f..2a3433f4d 100644 --- a/src/plugins/multimedia/windows/evr/evrcustompresenter.cpp +++ b/src/plugins/multimedia/windows/evr/evrcustompresenter.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "evrcustompresenter_p.h" @@ -45,7 +9,7 @@ #include <private/qplatformvideosink_p.h> #include <private/qwindowsmfdefs_p.h> -#include <QtGui/private/qrhi_p.h> +#include <rhi/qrhi.h> #include <QtCore/qmutex.h> #include <QtCore/qvarlengtharray.h> @@ -53,7 +17,7 @@ #include <qthread.h> #include <qcoreapplication.h> #include <qmath.h> -#include <QtCore/qdebug.h> +#include <qloggingcategory.h> #include <mutex> @@ -62,14 +26,14 @@ QT_BEGIN_NAMESPACE +static Q_LOGGING_CATEGORY(qLcEvrCustomPresenter, "qt.multimedia.evrcustompresenter") + const static MFRatio g_DefaultFrameRate = { 30, 1 }; static const DWORD SCHEDULER_TIMEOUT = 5000; static const MFTIME ONE_SECOND = 10000000; static const LONG ONE_MSEC = 1000; // Function declarations. -static HRESULT setDesiredSampleTime(IMFSample *sample, const LONGLONG& hnsSampleTime, const LONGLONG& hnsDuration); -static HRESULT clearDesiredSampleTime(IMFSample *sample); static HRESULT setMixerSourceRect(IMFTransform *mixer, const MFVideoNormalizedRect& nrcSource); static QVideoFrameFormat::PixelFormat pixelFormatFromMediaType(IMFMediaType *type); @@ -97,45 +61,27 @@ bool qt_evr_setCustomPresenter(IUnknown *evr, EVRCustomPresenter *presenter) class PresentSampleEvent : public QEvent { public: - PresentSampleEvent(IMFSample *sample) - : QEvent(QEvent::Type(EVRCustomPresenter::PresentSample)) - , m_sample(sample) - { - if (m_sample) - m_sample->AddRef(); - } - - ~PresentSampleEvent() override + explicit PresentSampleEvent(const ComPtr<IMFSample> &sample) + : QEvent(static_cast<Type>(EVRCustomPresenter::PresentSample)), m_sample(sample) { - if (m_sample) - m_sample->Release(); } - IMFSample *sample() const { return m_sample; } + ComPtr<IMFSample> sample() const { return m_sample; } private: - IMFSample *m_sample; + const ComPtr<IMFSample> m_sample; }; Scheduler::Scheduler(EVRCustomPresenter *presenter) : m_presenter(presenter) - , m_clock(NULL) , m_threadID(0) - , m_schedulerThread(0) - , m_threadReadyEvent(0) - , m_flushEvent(0) , m_playbackRate(1.0f) - , m_perFrameInterval(0) , m_perFrame_1_4th(0) - , m_lastSampleTime(0) { } Scheduler::~Scheduler() { - qt_evr_safe_release(&m_clock); - for (int i = 0; i < m_scheduledSamples.size(); ++i) - m_scheduledSamples[i]->Release(); m_scheduledSamples.clear(); } @@ -146,13 +92,11 @@ void Scheduler::setFrameRate(const MFRatio& fps) // Convert to a duration. MFFrameRateToAverageTimePerFrame(fps.Numerator, fps.Denominator, &AvgTimePerFrame); - m_perFrameInterval = (MFTIME)AvgTimePerFrame; - // Calculate 1/4th of this value, because we use it frequently. - m_perFrame_1_4th = m_perFrameInterval / 4; + m_perFrame_1_4th = AvgTimePerFrame / 4; } -HRESULT Scheduler::startScheduler(IMFClock *clock) +HRESULT Scheduler::startScheduler(ComPtr<IMFClock> clock) { if (m_schedulerThread) return E_UNEXPECTED; @@ -162,44 +106,39 @@ HRESULT Scheduler::startScheduler(IMFClock *clock) HANDLE hObjects[2]; DWORD dwWait = 0; - if (m_clock) - m_clock->Release(); m_clock = clock; - if (m_clock) - m_clock->AddRef(); // Set a high the timer resolution (ie, short timer period). timeBeginPeriod(1); // Create an event to wait for the thread to start. - m_threadReadyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + m_threadReadyEvent = EventHandle{ CreateEvent(NULL, FALSE, FALSE, NULL) }; if (!m_threadReadyEvent) { hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } // Create an event to wait for flush commands to complete. - m_flushEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + m_flushEvent = EventHandle{ CreateEvent(NULL, FALSE, FALSE, NULL) }; if (!m_flushEvent) { hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } // Create the scheduler thread. - m_schedulerThread = CreateThread(NULL, 0, schedulerThreadProc, (LPVOID)this, 0, &dwID); + m_schedulerThread = ThreadHandle{ CreateThread(NULL, 0, schedulerThreadProc, (LPVOID)this, 0, &dwID) }; if (!m_schedulerThread) { hr = HRESULT_FROM_WIN32(GetLastError()); goto done; } // Wait for the thread to signal the "thread ready" event. - hObjects[0] = m_threadReadyEvent; - hObjects[1] = m_schedulerThread; + hObjects[0] = m_threadReadyEvent.get(); + hObjects[1] = m_schedulerThread.get(); dwWait = WaitForMultipleObjects(2, hObjects, FALSE, INFINITE); // Wait for EITHER of these handles. if (WAIT_OBJECT_0 != dwWait) { // The thread terminated early for some reason. This is an error condition. - CloseHandle(m_schedulerThread); - m_schedulerThread = NULL; + m_schedulerThread = {}; hr = E_UNEXPECTED; goto done; @@ -209,10 +148,8 @@ HRESULT Scheduler::startScheduler(IMFClock *clock) done: // Regardless success/failure, we are done using the "thread ready" event. - if (m_threadReadyEvent) { - CloseHandle(m_threadReadyEvent); - m_threadReadyEvent = NULL; - } + m_threadReadyEvent = {}; + return hr; } @@ -225,19 +162,14 @@ HRESULT Scheduler::stopScheduler() PostThreadMessage(m_threadID, Terminate, 0, 0); // Wait for the thread to exit. - WaitForSingleObject(m_schedulerThread, INFINITE); + WaitForSingleObject(m_schedulerThread.get(), INFINITE); // Close handles. - CloseHandle(m_schedulerThread); - m_schedulerThread = NULL; - - CloseHandle(m_flushEvent); - m_flushEvent = NULL; + m_schedulerThread = {}; + m_flushEvent = {}; // Discard samples. m_mutex.lock(); - for (int i = 0; i < m_scheduledSamples.size(); ++i) - m_scheduledSamples[i]->Release(); m_scheduledSamples.clear(); m_mutex.unlock(); @@ -255,7 +187,7 @@ HRESULT Scheduler::flush() // Wait for the scheduler thread to signal the flush event, // OR for the thread to terminate. - HANDLE objects[] = { m_flushEvent, m_schedulerThread }; + HANDLE objects[] = { m_flushEvent.get(), m_schedulerThread.get() }; WaitForMultipleObjects(ARRAYSIZE(objects), objects, FALSE, SCHEDULER_TIMEOUT); } @@ -269,7 +201,7 @@ bool Scheduler::areSamplesScheduled() return m_scheduledSamples.count() > 0; } -HRESULT Scheduler::scheduleSample(IMFSample *sample, bool presentNow) +HRESULT Scheduler::scheduleSample(const ComPtr<IMFSample> &sample, bool presentNow) { if (!m_schedulerThread) return MF_E_NOT_INITIALIZED; @@ -277,16 +209,20 @@ HRESULT Scheduler::scheduleSample(IMFSample *sample, bool presentNow) HRESULT hr = S_OK; DWORD dwExitCode = 0; - GetExitCodeThread(m_schedulerThread, &dwExitCode); + GetExitCodeThread(m_schedulerThread.get(), &dwExitCode); if (dwExitCode != STILL_ACTIVE) return E_FAIL; if (presentNow || !m_clock) { m_presenter->presentSample(sample); } else { + if (m_playbackRate > 0.0f && qt_evr_isSampleTimePassed(m_clock.Get(), sample.Get())) { + qCDebug(qLcEvrCustomPresenter) << "Discard the sample, it came too late"; + return hr; + } + // Queue the sample and ask the scheduler thread to wake up. m_mutex.lock(); - sample->AddRef(); m_scheduledSamples.enqueue(sample); m_mutex.unlock(); @@ -301,25 +237,37 @@ HRESULT Scheduler::processSamplesInQueue(LONG *nextSleep) { HRESULT hr = S_OK; LONG wait = 0; - IMFSample *sample = NULL; + + QQueue<ComPtr<IMFSample>> scheduledSamples; + + m_mutex.lock(); + m_scheduledSamples.swap(scheduledSamples); + m_mutex.unlock(); // Process samples until the queue is empty or until the wait time > 0. - while (!m_scheduledSamples.isEmpty()) { - m_mutex.lock(); - sample = m_scheduledSamples.dequeue(); - m_mutex.unlock(); + while (!scheduledSamples.isEmpty()) { + ComPtr<IMFSample> sample = scheduledSamples.dequeue(); // Process the next sample in the queue. If the sample is not ready // for presentation. the value returned in wait is > 0, which // means the scheduler should sleep for that amount of time. + if (isSampleReadyToPresent(sample.Get(), &wait)) { + m_presenter->presentSample(sample.Get()); + continue; + } - hr = processSample(sample, &wait); - qt_evr_safe_release(&sample); - - if (FAILED(hr) || wait > 0) + if (wait > 0) { + // return the sample to scheduler + scheduledSamples.prepend(sample); break; + } } + m_mutex.lock(); + scheduledSamples.append(std::move(m_scheduledSamples)); + m_scheduledSamples.swap(scheduledSamples); + m_mutex.unlock(); + // If the wait time is zero, it means we stopped because the queue is // empty (or an error occurred). Set the wait time to infinite; this will // make the scheduler thread sleep until it gets another thread message. @@ -330,66 +278,50 @@ HRESULT Scheduler::processSamplesInQueue(LONG *nextSleep) return hr; } -HRESULT Scheduler::processSample(IMFSample *sample, LONG *pNextSleep) +bool Scheduler::isSampleReadyToPresent(IMFSample *sample, LONG *pNextSleep) const { - HRESULT hr = S_OK; - - LONGLONG hnsPresentationTime = 0; - LONGLONG hnsTimeNow = 0; - MFTIME hnsSystemTime = 0; - - bool presentNow = true; - LONG nextSleep = 0; - - if (m_clock) { - // Get the sample's time stamp. It is valid for a sample to - // have no time stamp. - hr = sample->GetSampleTime(&hnsPresentationTime); - - // Get the clock time. (But if the sample does not have a time stamp, - // we don't need the clock time.) - if (SUCCEEDED(hr)) - hr = m_clock->GetCorrelatedTime(0, &hnsTimeNow, &hnsSystemTime); - - // Calculate the time until the sample's presentation time. - // A negative value means the sample is late. - LONGLONG hnsDelta = hnsPresentationTime - hnsTimeNow; - if (m_playbackRate < 0) { - // For reverse playback, the clock runs backward. Therefore, the - // delta is reversed. - hnsDelta = - hnsDelta; - } + *pNextSleep = 0; + if (!m_clock) + return true; - if (hnsDelta < - m_perFrame_1_4th) { - // This sample is late. - presentNow = true; - } else if (hnsDelta > (3 * m_perFrame_1_4th)) { - // This sample is still too early. Go to sleep. - nextSleep = MFTimeToMsec(hnsDelta - (3 * m_perFrame_1_4th)); + MFTIME hnsPresentationTime = 0; + MFTIME hnsTimeNow = 0; + MFTIME hnsSystemTime = 0; - // Adjust the sleep time for the clock rate. (The presentation clock runs - // at m_fRate, but sleeping uses the system clock.) - if (m_playbackRate != 0) - nextSleep = (LONG)(nextSleep / qFabs(m_playbackRate)); + // Get the sample's time stamp. It is valid for a sample to + // have no time stamp. + HRESULT hr = sample->GetSampleTime(&hnsPresentationTime); - // Don't present yet. - presentNow = false; - } + // Get the clock time. (But if the sample does not have a time stamp, + // we don't need the clock time.) + if (SUCCEEDED(hr)) + hr = m_clock->GetCorrelatedTime(0, &hnsTimeNow, &hnsSystemTime); + + // Calculate the time until the sample's presentation time. + // A negative value means the sample is late. + MFTIME hnsDelta = hnsPresentationTime - hnsTimeNow; + if (m_playbackRate < 0) { + // For reverse playback, the clock runs backward. Therefore, the + // delta is reversed. + hnsDelta = - hnsDelta; } - if (presentNow) { - m_presenter->presentSample(sample); + if (hnsDelta < - m_perFrame_1_4th) { + // This sample is late - skip. + return false; + } else if (hnsDelta > (3 * m_perFrame_1_4th)) { + // This sample came too early - reschedule + *pNextSleep = MFTimeToMsec(hnsDelta - (3 * m_perFrame_1_4th)); + + // Adjust the sleep time for the clock rate. (The presentation clock runs + // at m_fRate, but sleeping uses the system clock.) + if (m_playbackRate != 0) + *pNextSleep = (LONG)(*pNextSleep / qFabs(m_playbackRate)); + return *pNextSleep == 0; } else { - // The sample is not ready yet. Return it to the queue. - m_mutex.lock(); - sample->AddRef(); - m_scheduledSamples.prepend(sample); - m_mutex.unlock(); + // This sample can be presented right now + return true; } - - *pNextSleep = nextSleep; - - return hr; } DWORD WINAPI Scheduler::schedulerThreadProc(LPVOID parameter) @@ -412,7 +344,7 @@ DWORD Scheduler::schedulerThreadProcPrivate() PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); // Signal to the scheduler that the thread is ready. - SetEvent(m_threadReadyEvent); + SetEvent(m_threadReadyEvent.get()); while (!exitThread) { // Wait for a thread message OR until the wait time expires. @@ -435,12 +367,10 @@ DWORD Scheduler::schedulerThreadProcPrivate() case Flush: // Flushing: Clear the sample queue and set the event. m_mutex.lock(); - for (int i = 0; i < m_scheduledSamples.size(); ++i) - m_scheduledSamples[i]->Release(); m_scheduledSamples.clear(); m_mutex.unlock(); wait = INFINITE; - SetEvent(m_flushEvent); + SetEvent(m_flushEvent.get()); break; case Schedule: // Process as many samples as we can. @@ -470,47 +400,44 @@ SamplePool::~SamplePool() clear(); } -HRESULT SamplePool::getSample(IMFSample **sample) +ComPtr<IMFSample> SamplePool::takeSample() { QMutexLocker locker(&m_mutex); - if (!m_initialized) - return MF_E_NOT_INITIALIZED; + Q_ASSERT(m_initialized); + if (!m_initialized) { + qCWarning(qLcEvrCustomPresenter) << "SamplePool is not initialized yet"; + return nullptr; + } - if (m_videoSampleQueue.isEmpty()) - return MF_E_SAMPLEALLOCATOR_EMPTY; + if (m_videoSampleQueue.isEmpty()) { + qCDebug(qLcEvrCustomPresenter) << "SamplePool is empty"; + return nullptr; + } // Get a sample from the allocated queue. // It doesn't matter if we pull them from the head or tail of the list, // but when we get it back, we want to re-insert it onto the opposite end. - // (see ReturnSample) - - IMFSample *taken = m_videoSampleQueue.takeFirst(); + // (see returnSample) - // Give the sample to the caller. - *sample = taken; - (*sample)->AddRef(); - - taken->Release(); - - return S_OK; + return m_videoSampleQueue.takeFirst(); } -HRESULT SamplePool::returnSample(IMFSample *sample) +void SamplePool::returnSample(const ComPtr<IMFSample> &sample) { QMutexLocker locker(&m_mutex); - if (!m_initialized) - return MF_E_NOT_INITIALIZED; + Q_ASSERT(m_initialized); + if (!m_initialized) { + qCWarning(qLcEvrCustomPresenter) << "SamplePool is not initialized yet"; + return; + } m_videoSampleQueue.append(sample); - sample->AddRef(); - - return S_OK; } -HRESULT SamplePool::initialize(QList<IMFSample*> &samples) +HRESULT SamplePool::initialize(QList<ComPtr<IMFSample>> &&samples) { QMutexLocker locker(&m_mutex); @@ -518,16 +445,10 @@ HRESULT SamplePool::initialize(QList<IMFSample*> &samples) return MF_E_INVALIDREQUEST; // Move these samples into our allocated queue. - for (auto sample : qAsConst(samples)) { - sample->AddRef(); - m_videoSampleQueue.append(sample); - } + m_videoSampleQueue.append(std::move(samples)); m_initialized = true; - for (auto sample : qAsConst(samples)) - sample->Release(); - samples.clear(); return S_OK; } @@ -535,8 +456,6 @@ HRESULT SamplePool::clear() { QMutexLocker locker(&m_mutex); - for (auto sample : qAsConst(m_videoSampleQueue)) - sample->Release(); m_videoSampleQueue.clear(); m_initialized = false; @@ -552,14 +471,10 @@ EVRCustomPresenter::EVRCustomPresenter(QVideoSink *sink) , m_scheduler(this) , m_tokenCounter(0) , m_sampleNotify(false) - , m_repaint(false) , m_prerolled(false) , m_endStreaming(false) , m_playbackRate(1.0f) , m_presentEngine(new D3DPresentEngine(sink)) - , m_clock(0) - , m_mixer(0) - , m_mediaEventSink(0) , m_mediaType(0) , m_videoSink(0) , m_canRenderToSurface(false) @@ -580,11 +495,6 @@ EVRCustomPresenter::~EVRCustomPresenter() m_scheduler.stopScheduler(); m_samplePool.clear(); - qt_evr_safe_release(&m_clock); - qt_evr_safe_release(&m_mixer); - qt_evr_safe_release(&m_mediaEventSink); - qt_evr_safe_release(&m_mediaType); - delete m_presentEngine; } @@ -623,7 +533,7 @@ ULONG EVRCustomPresenter::Release() { ULONG uCount = InterlockedDecrement(&m_refCount); if (uCount == 0) - delete this; + deleteLater(); return uCount; } @@ -671,9 +581,9 @@ HRESULT EVRCustomPresenter::InitServicePointers(IMFTopologyServiceLookup *lookup if (isActive()) return MF_E_INVALIDREQUEST; - qt_evr_safe_release(&m_clock); - qt_evr_safe_release(&m_mixer); - qt_evr_safe_release(&m_mediaEventSink); + m_clock.Reset(); + m_mixer.Reset(); + m_mediaEventSink.Reset(); // Ask for the clock. Optional, because the EVR might not have a clock. objectCount = 1; @@ -695,7 +605,7 @@ HRESULT EVRCustomPresenter::InitServicePointers(IMFTopologyServiceLookup *lookup return hr; // Make sure that we can work with this mixer. - hr = configureMixer(m_mixer); + hr = configureMixer(m_mixer.Get()); if (FAILED(hr)) return hr; @@ -729,9 +639,9 @@ HRESULT EVRCustomPresenter::ReleaseServicePointers() setMediaType(NULL); // Release all services that were acquired from InitServicePointers. - qt_evr_safe_release(&m_clock); - qt_evr_safe_release(&m_mixer); - qt_evr_safe_release(&m_mediaEventSink); + m_clock.Reset(); + m_mixer.Reset(); + m_mediaEventSink.Reset(); return S_OK; } @@ -932,8 +842,6 @@ HRESULT EVRCustomPresenter::OnClockSetRate(MFTIME, float rate) // frame-step operation. if ((m_playbackRate == 0.0f) && (rate != 0.0f)) { cancelFrameStep(); - for (auto sample : qAsConst(m_frameStep.samples)) - sample->Release(); m_frameStep.samples.clear(); } @@ -1052,6 +960,13 @@ void EVRCustomPresenter::setSink(QVideoSink *sink) supportedFormatsChanged(); } +void EVRCustomPresenter::setCropRect(QRect cropRect) +{ + m_mutex.lock(); + m_cropRect = cropRect; + m_mutex.unlock(); +} + HRESULT EVRCustomPresenter::configureMixer(IMFTransform *mixer) { // Set the zoom rectangle (ie, the source clipping rectangle). @@ -1129,13 +1044,11 @@ HRESULT EVRCustomPresenter::flush() m_scheduler.flush(); // Flush the frame-step queue. - for (auto sample : qAsConst(m_frameStep.samples)) - sample->Release(); m_frameStep.samples.clear(); if (m_renderState == RenderStopped && m_videoSink) { // Repaint with black. - presentSample(NULL); + presentSample(nullptr); } return S_OK; @@ -1223,9 +1136,6 @@ HRESULT EVRCustomPresenter::prepareFrameStep(DWORD steps) HRESULT EVRCustomPresenter::startFrameStep() { - HRESULT hr = S_OK; - IMFSample *sample = NULL; - if (m_frameStep.state == FrameStepWaitingStart) { // We have a frame-step request, and are waiting for the clock to start. // Set the state to "pending," which means we are waiting for samples. @@ -1233,13 +1143,11 @@ HRESULT EVRCustomPresenter::startFrameStep() // If the frame-step queue already has samples, process them now. while (!m_frameStep.samples.isEmpty() && (m_frameStep.state == FrameStepPending)) { - sample = m_frameStep.samples.takeFirst(); + const ComPtr<IMFSample> sample = m_frameStep.samples.takeFirst(); - hr = deliverFrameStepSample(sample); + const HRESULT hr = deliverFrameStepSample(sample.Get()); if (FAILED(hr)) - goto done; - - qt_evr_safe_release(&sample); + return hr; // We break from this loop when: // (a) the frame-step queue is empty, or @@ -1249,22 +1157,18 @@ HRESULT EVRCustomPresenter::startFrameStep() // We are not frame stepping. Therefore, if the frame-step queue has samples, // we need to process them normally. while (!m_frameStep.samples.isEmpty()) { - sample = m_frameStep.samples.takeFirst(); + const ComPtr<IMFSample> sample = m_frameStep.samples.takeFirst(); - hr = deliverSample(sample, false); + const HRESULT hr = deliverSample(sample.Get()); if (FAILED(hr)) - goto done; - - qt_evr_safe_release(&sample); + return hr; } } -done: - qt_evr_safe_release(&sample); - return hr; + return S_OK; } -HRESULT EVRCustomPresenter::completeFrameStep(IMFSample *sample) +HRESULT EVRCustomPresenter::completeFrameStep(const ComPtr<IMFSample> &sample) { HRESULT hr = S_OK; MFTIME sampleTime = 0; @@ -1342,13 +1246,30 @@ HRESULT EVRCustomPresenter::createOptimalVideoType(IMFMediaType *proposedType, I hr = proposedType->GetUINT64(MF_MT_FRAME_SIZE, &size); width = int(HI32(size)); height = int(LO32(size)); - rcOutput.left = 0; - rcOutput.top = 0; - rcOutput.right = width; - rcOutput.bottom = height; + + if (m_cropRect.isValid()) { + rcOutput.left = m_cropRect.x(); + rcOutput.top = m_cropRect.y(); + rcOutput.right = m_cropRect.x() + m_cropRect.width(); + rcOutput.bottom = m_cropRect.y() + m_cropRect.height(); + + m_sourceRect.left = float(m_cropRect.x()) / width; + m_sourceRect.top = float(m_cropRect.y()) / height; + m_sourceRect.right = float(m_cropRect.x() + m_cropRect.width()) / width; + m_sourceRect.bottom = float(m_cropRect.y() + m_cropRect.height()) / height; + + if (m_mixer) + configureMixer(m_mixer.Get()); + } else { + rcOutput.left = 0; + rcOutput.top = 0; + rcOutput.right = width; + rcOutput.bottom = height; + } // Set the geometric aperture, and disable pan/scan. - displayArea = qt_evr_makeMFArea(0, 0, rcOutput.right, rcOutput.bottom); + displayArea = qt_evr_makeMFArea(0, 0, rcOutput.right - rcOutput.left, + rcOutput.bottom - rcOutput.top); hr = mtOptimal->SetUINT32(MF_MT_PAN_SCAN_ENABLED, FALSE); if (FAILED(hr)) @@ -1389,13 +1310,13 @@ HRESULT EVRCustomPresenter::setMediaType(IMFMediaType *mediaType) // Clearing the media type is allowed in any state (including shutdown). if (!mediaType) { stopSurface(); - qt_evr_safe_release(&m_mediaType); + m_mediaType.Reset(); releaseResources(); return S_OK; } MFRatio fps = { 0, 0 }; - QList<IMFSample*> sampleQueue; + QList<ComPtr<IMFSample>> sampleQueue; // Cannot set the media type after shutdown. HRESULT hr = checkShutdown(); @@ -1404,30 +1325,30 @@ HRESULT EVRCustomPresenter::setMediaType(IMFMediaType *mediaType) // Check if the new type is actually different. // Note: This function safely handles NULL input parameters. - if (qt_evr_areMediaTypesEqual(m_mediaType, mediaType)) + if (qt_evr_areMediaTypesEqual(m_mediaType.Get(), mediaType)) goto done; // Nothing more to do. // We're really changing the type. First get rid of the old type. - qt_evr_safe_release(&m_mediaType); + m_mediaType.Reset(); releaseResources(); // Initialize the presenter engine with the new media type. // The presenter engine allocates the samples. - hr = m_presentEngine->createVideoSamples(mediaType, sampleQueue); + hr = m_presentEngine->createVideoSamples(mediaType, sampleQueue, m_cropRect.size()); if (FAILED(hr)) goto done; // Mark each sample with our token counter. If this batch of samples becomes // invalid, we increment the counter, so that we know they should be discarded. - for (auto sample : qAsConst(sampleQueue)) { + for (auto sample : std::as_const(sampleQueue)) { hr = sample->SetUINT32(MFSamplePresenter_SampleCounter, m_tokenCounter); if (FAILED(hr)) goto done; } // Add the samples to the sample pool. - hr = m_samplePool.initialize(sampleQueue); + hr = m_samplePool.initialize(std::move(sampleQueue)); if (FAILED(hr)) goto done; @@ -1544,21 +1465,9 @@ void EVRCustomPresenter::processOutputLoop() HRESULT EVRCustomPresenter::processOutput() { - HRESULT hr = S_OK; - DWORD status = 0; - LONGLONG mixerStartTime = 0, mixerEndTime = 0; - MFTIME systemTime = 0; - BOOL repaint = m_repaint; // Temporarily store this state flag. - - MFT_OUTPUT_DATA_BUFFER dataBuffer; - ZeroMemory(&dataBuffer, sizeof(dataBuffer)); - - IMFSample *sample = NULL; - // If the clock is not running, we present the first sample, // and then don't present any more until the clock starts. - - if ((m_renderState != RenderStarted) && !m_repaint && m_prerolled) + if ((m_renderState != RenderStarted) && m_prerolled) return S_FALSE; // Make sure we have a pointer to the mixer. @@ -1566,45 +1475,33 @@ HRESULT EVRCustomPresenter::processOutput() return MF_E_INVALIDREQUEST; // Try to get a free sample from the video sample pool. - hr = m_samplePool.getSample(&sample); - if (hr == MF_E_SAMPLEALLOCATOR_EMPTY) // No free samples. Try again when a sample is released. - return S_FALSE; - if (FAILED(hr)) - return hr; + ComPtr<IMFSample> sample = m_samplePool.takeSample(); + if (!sample) + return S_FALSE; // No free samples. Try again when a sample is released. // From now on, we have a valid video sample pointer, where the mixer will // write the video data. - if (m_repaint) { - // Repaint request. Ask the mixer for the most recent sample. - setDesiredSampleTime(sample, m_scheduler.lastSampleTime(), m_scheduler.frameDuration()); - - m_repaint = false; // OK to clear this flag now. - } else { - // Not a repaint request. Clear the desired sample time; the mixer will - // give us the next frame in the stream. - clearDesiredSampleTime(sample); + LONGLONG mixerStartTime = 0, mixerEndTime = 0; + MFTIME systemTime = 0; - if (m_clock) { - // Latency: Record the starting time for ProcessOutput. - m_clock->GetCorrelatedTime(0, &mixerStartTime, &systemTime); - } + if (m_clock) { + // Latency: Record the starting time for ProcessOutput. + m_clock->GetCorrelatedTime(0, &mixerStartTime, &systemTime); } // Now we are ready to get an output sample from the mixer. - dataBuffer.dwStreamID = 0; - dataBuffer.pSample = sample; - dataBuffer.dwStatus = 0; - - hr = m_mixer->ProcessOutput(0, 1, &dataBuffer, &status); + DWORD status = 0; + MFT_OUTPUT_DATA_BUFFER dataBuffer = {}; + dataBuffer.pSample = sample.Get(); + HRESULT hr = m_mixer->ProcessOutput(0, 1, &dataBuffer, &status); + // Important: Release any events returned from the ProcessOutput method. + qt_evr_safe_release(&dataBuffer.pEvents); if (FAILED(hr)) { // Return the sample to the pool. - HRESULT hr2 = m_samplePool.returnSample(sample); - if (FAILED(hr2)) { - hr = hr2; - goto done; - } + m_samplePool.returnSample(sample); + // Handle some known error codes from ProcessOutput. if (hr == MF_E_TRANSFORM_TYPE_NOT_SET) { // The mixer's format is not set. Negotiate a new format. @@ -1617,54 +1514,46 @@ HRESULT EVRCustomPresenter::processOutput() // We have to wait for the mixer to get more input. m_sampleNotify = false; } - } else { - // We got an output sample from the mixer. - if (m_clock && !repaint) { - // Latency: Record the ending time for the ProcessOutput operation, - // and notify the EVR of the latency. + return hr; + } - m_clock->GetCorrelatedTime(0, &mixerEndTime, &systemTime); + // We got an output sample from the mixer. + if (m_clock) { + // Latency: Record the ending time for the ProcessOutput operation, + // and notify the EVR of the latency. - LONGLONG latencyTime = mixerEndTime - mixerStartTime; - notifyEvent(EC_PROCESSING_LATENCY, reinterpret_cast<LONG_PTR>(&latencyTime), 0); - } + m_clock->GetCorrelatedTime(0, &mixerEndTime, &systemTime); - // Set up notification for when the sample is released. - hr = trackSample(sample); - if (FAILED(hr)) - goto done; + LONGLONG latencyTime = mixerEndTime - mixerStartTime; + notifyEvent(EC_PROCESSING_LATENCY, reinterpret_cast<LONG_PTR>(&latencyTime), 0); + } - // Schedule the sample. - if ((m_frameStep.state == FrameStepNone) || repaint) { - hr = deliverSample(sample, repaint); - if (FAILED(hr)) - goto done; - } else { - // We are frame-stepping (and this is not a repaint request). - hr = deliverFrameStepSample(sample); - if (FAILED(hr)) - goto done; - } + // Set up notification for when the sample is released. + hr = trackSample(sample); + if (FAILED(hr)) + return hr; - m_prerolled = true; // We have presented at least one sample now. - } + // Schedule the sample. + if (m_frameStep.state == FrameStepNone) + hr = deliverSample(sample); + else // We are frame-stepping + hr = deliverFrameStepSample(sample); -done: - qt_evr_safe_release(&sample); + if (FAILED(hr)) + return hr; - // Important: Release any events returned from the ProcessOutput method. - qt_evr_safe_release(&dataBuffer.pEvents); - return hr; + m_prerolled = true; // We have presented at least one sample now. + return S_OK; } -HRESULT EVRCustomPresenter::deliverSample(IMFSample *sample, bool repaint) +HRESULT EVRCustomPresenter::deliverSample(const ComPtr<IMFSample> &sample) { - // If we are not actively playing, OR we are scrubbing (rate = 0) OR this is a - // repaint request, then we need to present the sample immediately. Otherwise, + // If we are not actively playing, OR we are scrubbing (rate = 0), + // then we need to present the sample immediately. Otherwise, // schedule it normally. - bool presentNow = ((m_renderState != RenderStarted) || isScrubbing() || repaint); + bool presentNow = ((m_renderState != RenderStarted) || isScrubbing()); HRESULT hr = m_scheduler.scheduleSample(sample, presentNow); @@ -1678,19 +1567,18 @@ HRESULT EVRCustomPresenter::deliverSample(IMFSample *sample, bool repaint) return hr; } -HRESULT EVRCustomPresenter::deliverFrameStepSample(IMFSample *sample) +HRESULT EVRCustomPresenter::deliverFrameStepSample(const ComPtr<IMFSample> &sample) { HRESULT hr = S_OK; IUnknown *unk = NULL; // For rate 0, discard any sample that ends earlier than the clock time. - if (isScrubbing() && m_clock && qt_evr_isSampleTimePassed(m_clock, sample)) { + if (isScrubbing() && m_clock && qt_evr_isSampleTimePassed(m_clock.Get(), sample.Get())) { // Discard this sample. } else if (m_frameStep.state >= FrameStepScheduled) { // A frame was already submitted. Put this sample on the frame-step queue, // in case we are asked to step to the next frame. If frame-stepping is // cancelled, this sample will be processed normally. - sample->AddRef(); m_frameStep.samples.append(sample); } else { // We're ready to frame-step. @@ -1705,11 +1593,10 @@ HRESULT EVRCustomPresenter::deliverFrameStepSample(IMFSample *sample) // This is the right frame, but the clock hasn't started yet. Put the // sample on the frame-step queue. When the clock starts, the sample // will be processed. - sample->AddRef(); m_frameStep.samples.append(sample); } else { // This is the right frame *and* the clock has started. Deliver this sample. - hr = deliverSample(sample, false); + hr = deliverSample(sample); if (FAILED(hr)) goto done; @@ -1734,7 +1621,7 @@ done: return hr; } -HRESULT EVRCustomPresenter::trackSample(IMFSample *sample) +HRESULT EVRCustomPresenter::trackSample(const ComPtr<IMFSample> &sample) { IMFTrackedSample *tracked = NULL; @@ -1811,11 +1698,9 @@ HRESULT EVRCustomPresenter::onSampleFree(IMFAsyncResult *result) if (token == m_tokenCounter) { // Return the sample to the sample pool. - hr = m_samplePool.returnSample(sample); - if (SUCCEEDED(hr)) { - // A free sample is available. Process more data if possible. - processOutputLoop(); - } + m_samplePool.returnSample(sample); + // A free sample is available. Process more data if possible. + processOutputLoop(); } m_mutex.unlock(); @@ -1843,7 +1728,7 @@ float EVRCustomPresenter::getMaxRate(bool thin) UINT monitorRateHz = 0; if (!thin && m_mediaType) { - qt_evr_getFrameRate(m_mediaType, &fps); + qt_evr_getFrameRate(m_mediaType.Get(), &fps); monitorRateHz = m_presentEngine->refreshRate(); if (fps.Denominator && fps.Numerator && monitorRateHz) { @@ -1889,7 +1774,7 @@ void EVRCustomPresenter::stopSurface() } } -void EVRCustomPresenter::presentSample(IMFSample *sample) +void EVRCustomPresenter::presentSample(const ComPtr<IMFSample> &sample) { if (thread() != QThread::currentThread()) { QCoreApplication::postEvent(this, new PresentSampleEvent(sample)); @@ -1910,15 +1795,15 @@ void EVRCustomPresenter::presentSample(IMFSample *sample) frame.setEndTime(frame.endTime() + m_positionOffset); } - QWindowsIUPointer<IMFMediaType> inputStreamType; - if (SUCCEEDED(m_mixer->GetInputCurrentType(0, inputStreamType.address()))) { - auto rotation = static_cast<MFVideoRotationFormat>(MFGetAttributeUINT32(inputStreamType.get(), MF_MT_VIDEO_ROTATION, 0)); + ComPtr<IMFMediaType> inputStreamType; + if (SUCCEEDED(m_mixer->GetInputCurrentType(0, inputStreamType.GetAddressOf()))) { + auto rotation = static_cast<MFVideoRotationFormat>(MFGetAttributeUINT32(inputStreamType.Get(), MF_MT_VIDEO_ROTATION, 0)); switch (rotation) { - case MFVideoRotationFormat_0: frame.setRotationAngle(QVideoFrame::Rotation0); break; - case MFVideoRotationFormat_90: frame.setRotationAngle(QVideoFrame::Rotation90); break; - case MFVideoRotationFormat_180: frame.setRotationAngle(QVideoFrame::Rotation180); break; - case MFVideoRotationFormat_270: frame.setRotationAngle(QVideoFrame::Rotation270); break; - default: frame.setRotationAngle(QVideoFrame::Rotation0); + case MFVideoRotationFormat_0: frame.setRotation(QtVideo::Rotation::None); break; + case MFVideoRotationFormat_90: frame.setRotation(QtVideo::Rotation::Clockwise90); break; + case MFVideoRotationFormat_180: frame.setRotation(QtVideo::Rotation::Clockwise180); break; + case MFVideoRotationFormat_270: frame.setRotation(QtVideo::Rotation::Clockwise270); break; + default: frame.setRotation(QtVideo::Rotation::None); } } @@ -1930,55 +1815,6 @@ void EVRCustomPresenter::positionChanged(qint64 position) m_positionOffset = position * 1000; } -HRESULT setDesiredSampleTime(IMFSample *sample, const LONGLONG &sampleTime, const LONGLONG &duration) -{ - if (!sample) - return E_POINTER; - - HRESULT hr = S_OK; - IMFDesiredSample *desired = NULL; - - hr = sample->QueryInterface(IID_PPV_ARGS(&desired)); - if (SUCCEEDED(hr)) - desired->SetDesiredSampleTimeAndDuration(sampleTime, duration); - - qt_evr_safe_release(&desired); - return hr; -} - -HRESULT clearDesiredSampleTime(IMFSample *sample) -{ - if (!sample) - return E_POINTER; - - HRESULT hr = S_OK; - - IMFDesiredSample *desired = NULL; - IUnknown *unkSwapChain = NULL; - - // We store some custom attributes on the sample, so we need to cache them - // and reset them. - // - // This works around the fact that IMFDesiredSample::Clear() removes all of the - // attributes from the sample. - - UINT32 counter = MFGetAttributeUINT32(sample, MFSamplePresenter_SampleCounter, (UINT32)-1); - - hr = sample->QueryInterface(IID_PPV_ARGS(&desired)); - if (SUCCEEDED(hr)) { - desired->Clear(); - - hr = sample->SetUINT32(MFSamplePresenter_SampleCounter, counter); - if (FAILED(hr)) - goto done; - } - -done: - qt_evr_safe_release(&unkSwapChain); - qt_evr_safe_release(&desired); - return hr; -} - HRESULT setMixerSourceRect(IMFTransform *mixer, const MFVideoNormalizedRect &sourceRect) { if (!mixer) |