diff options
Diffstat (limited to 'src/plugins/multimedia/windows/decoder')
4 files changed, 466 insertions, 0 deletions
diff --git a/src/plugins/multimedia/windows/decoder/mfaudiodecodercontrol.cpp b/src/plugins/multimedia/windows/decoder/mfaudiodecodercontrol.cpp new file mode 100644 index 000000000..912ab5e94 --- /dev/null +++ b/src/plugins/multimedia/windows/decoder/mfaudiodecodercontrol.cpp @@ -0,0 +1,225 @@ +// 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 <system_error> +#include <mferror.h> +#include <qglobal.h> +#include "wmcodecdsp.h" +#include "mfaudiodecodercontrol_p.h" +#include <private/qwindowsaudioutils_p.h> + +QT_BEGIN_NAMESPACE + +MFAudioDecoderControl::MFAudioDecoderControl(QAudioDecoder *parent) + : QPlatformAudioDecoder(parent) + , m_sourceResolver(new SourceResolver) +{ + connect(m_sourceResolver, &SourceResolver::mediaSourceReady, this, &MFAudioDecoderControl::handleMediaSourceReady); + connect(m_sourceResolver, &SourceResolver::error, this, &MFAudioDecoderControl::handleMediaSourceError); +} + +MFAudioDecoderControl::~MFAudioDecoderControl() +{ + m_sourceResolver->shutdown(); + m_sourceResolver->Release(); +} + +void MFAudioDecoderControl::setSource(const QUrl &fileName) +{ + if (!m_device && m_source == fileName) + return; + stop(); + m_sourceResolver->cancel(); + m_sourceResolver->shutdown(); + m_device = nullptr; + m_source = fileName; + sourceChanged(); + + if (!m_source.isEmpty()) { + m_sourceResolver->load(m_source, 0); + m_loadingSource = true; + } +} + +void MFAudioDecoderControl::setSourceDevice(QIODevice *device) +{ + if (m_device == device && m_source.isEmpty()) + return; + stop(); + m_sourceResolver->cancel(); + m_sourceResolver->shutdown(); + m_source.clear(); + m_device = device; + sourceChanged(); + + if (m_device) { + if (m_device->isOpen() && m_device->isReadable()) { + m_sourceResolver->load(QUrl(), m_device); + m_loadingSource = true; + } + } +} + +void MFAudioDecoderControl::handleMediaSourceReady() +{ + m_loadingSource = false; + if (m_deferredStart) { + m_deferredStart = false; + startReadingSource(m_sourceResolver->mediaSource()); + } +} + +void MFAudioDecoderControl::handleMediaSourceError(long hr) +{ + m_loadingSource = false; + m_deferredStart = false; + if (hr == MF_E_UNSUPPORTED_BYTESTREAM_TYPE) { + error(QAudioDecoder::FormatError, tr("Unsupported media type")); + } else if (hr == ERROR_FILE_NOT_FOUND) { + error(QAudioDecoder::ResourceError, tr("Media not found")); + } else { + error(QAudioDecoder::ResourceError, tr("Unable to load specified URL") + + QString::fromStdString(std::system_category().message(hr))); + } +} + +void MFAudioDecoderControl::startReadingSource(IMFMediaSource *source) +{ + Q_ASSERT(source); + + m_decoderSourceReader = makeComObject<MFDecoderSourceReader>(); + if (!m_decoderSourceReader) { + error(QAudioDecoder::ResourceError, tr("Could not instantiate MFDecoderSourceReader")); + return; + } + + auto mediaType = m_decoderSourceReader->setSource(source, m_outputFormat.sampleFormat()); + QAudioFormat mediaFormat = QWindowsAudioUtils::mediaTypeToFormat(mediaType.Get()); + if (!mediaFormat.isValid()) { + error(QAudioDecoder::FormatError, tr("Invalid media format")); + m_decoderSourceReader.Reset(); + return; + } + + ComPtr<IMFPresentationDescriptor> pd; + if (SUCCEEDED(source->CreatePresentationDescriptor(pd.GetAddressOf()))) { + UINT64 duration = 0; + pd->GetUINT64(MF_PD_DURATION, &duration); + duration /= 10000; + m_duration = qint64(duration); + durationChanged(m_duration); + } + + if (!m_resampler.setup(mediaFormat, m_outputFormat.isValid() ? m_outputFormat : mediaFormat)) { + qWarning() << "Failed to set up resampler"; + return; + } + + connect(m_decoderSourceReader.Get(), &MFDecoderSourceReader::finished, this, &MFAudioDecoderControl::handleSourceFinished); + connect(m_decoderSourceReader.Get(), &MFDecoderSourceReader::newSample, this, &MFAudioDecoderControl::handleNewSample); + + setIsDecoding(true); + + m_decoderSourceReader->readNextSample(); +} + +void MFAudioDecoderControl::start() +{ + if (isDecoding()) + return; + + if (m_loadingSource) { + m_deferredStart = true; + } else { + IMFMediaSource *source = m_sourceResolver->mediaSource(); + if (!source) { + if (m_device) + error(QAudioDecoder::ResourceError, tr("Unable to read from specified device")); + else if (m_source.isValid()) + error(QAudioDecoder::ResourceError, tr("Unable to load specified URL")); + else + error(QAudioDecoder::ResourceError, tr("No media source specified")); + return; + } else { + startReadingSource(source); + } + } +} + +void MFAudioDecoderControl::stop() +{ + m_deferredStart = false; + if (!isDecoding()) + return; + + disconnect(m_decoderSourceReader.Get()); + m_decoderSourceReader->clearSource(); + m_decoderSourceReader.Reset(); + + if (bufferAvailable()) { + QAudioBuffer buffer; + m_audioBuffer.swap(buffer); + bufferAvailableChanged(false); + } + setIsDecoding(false); + + if (m_position != -1) { + m_position = -1; + positionChanged(m_position); + } + if (m_duration != -1) { + m_duration = -1; + durationChanged(m_duration); + } +} + +void MFAudioDecoderControl::handleNewSample(ComPtr<IMFSample> sample) +{ + Q_ASSERT(sample); + + qint64 sampleStartTimeUs = m_resampler.outputFormat().durationForBytes(m_resampler.totalOutputBytes()); + QByteArray out = m_resampler.resample(sample.Get()); + + if (out.isEmpty()) { + error(QAudioDecoder::Error::ResourceError, tr("Failed processing a sample")); + + } else { + m_audioBuffer = QAudioBuffer(out, m_resampler.outputFormat(), sampleStartTimeUs); + + bufferAvailableChanged(true); + bufferReady(); + } +} + +void MFAudioDecoderControl::handleSourceFinished() +{ + stop(); + finished(); +} + +void MFAudioDecoderControl::setAudioFormat(const QAudioFormat &format) +{ + if (m_outputFormat == format) + return; + m_outputFormat = format; + formatChanged(m_outputFormat); +} + +QAudioBuffer MFAudioDecoderControl::read() +{ + QAudioBuffer buffer; + + if (bufferAvailable()) { + buffer.swap(m_audioBuffer); + m_position = buffer.startTime() / 1000; + positionChanged(m_position); + bufferAvailableChanged(false); + m_decoderSourceReader->readNextSample(); + } + + return buffer; +} + +QT_END_NAMESPACE + +#include "moc_mfaudiodecodercontrol_p.cpp" diff --git a/src/plugins/multimedia/windows/decoder/mfaudiodecodercontrol_p.h b/src/plugins/multimedia/windows/decoder/mfaudiodecodercontrol_p.h new file mode 100644 index 000000000..9bb2371ec --- /dev/null +++ b/src/plugins/multimedia/windows/decoder/mfaudiodecodercontrol_p.h @@ -0,0 +1,75 @@ +// 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 + +#ifndef MFAUDIODECODERCONTROL_H +#define MFAUDIODECODERCONTROL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "mfdecodersourcereader_p.h" +#include <private/qplatformaudiodecoder_p.h> +#include <sourceresolver_p.h> +#include <private/qcomptr_p.h> +#include <private/qwindowsresampler_p.h> + +QT_BEGIN_NAMESPACE + +class MFAudioDecoderControl : public QPlatformAudioDecoder +{ + Q_OBJECT +public: + MFAudioDecoderControl(QAudioDecoder *parent); + ~MFAudioDecoderControl() override; + + QUrl source() const override { return m_source; } + void setSource(const QUrl &fileName) override; + + QIODevice* sourceDevice() const override { return m_device; } + void setSourceDevice(QIODevice *device) override; + + void start() override; + void stop() override; + + QAudioFormat audioFormat() const override { return m_outputFormat; } + void setAudioFormat(const QAudioFormat &format) override; + + QAudioBuffer read() override; + bool bufferAvailable() const override { return m_audioBuffer.sampleCount() > 0; } + + qint64 position() const override { return m_position; } + qint64 duration() const override { return m_duration; } + +private Q_SLOTS: + void handleMediaSourceReady(); + void handleMediaSourceError(long hr); + void handleNewSample(ComPtr<IMFSample>); + void handleSourceFinished(); + +private: + void startReadingSource(IMFMediaSource *source); + + ComPtr<MFDecoderSourceReader> m_decoderSourceReader; + SourceResolver *m_sourceResolver; + QWindowsResampler m_resampler; + QUrl m_source; + QIODevice *m_device = nullptr; + QAudioFormat m_outputFormat; + QAudioBuffer m_audioBuffer; + qint64 m_duration = -1; + qint64 m_position = -1; + bool m_loadingSource = false; + bool m_deferredStart = false; +}; + +QT_END_NAMESPACE + +#endif//MFAUDIODECODERCONTROL_H diff --git a/src/plugins/multimedia/windows/decoder/mfdecodersourcereader.cpp b/src/plugins/multimedia/windows/decoder/mfdecodersourcereader.cpp new file mode 100644 index 000000000..097f83437 --- /dev/null +++ b/src/plugins/multimedia/windows/decoder/mfdecodersourcereader.cpp @@ -0,0 +1,103 @@ +// 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 <system_error> +#include <mferror.h> +#include <qlogging.h> +#include <qdebug.h> +#include "mfdecodersourcereader_p.h" + +QT_BEGIN_NAMESPACE + +ComPtr<IMFMediaType> MFDecoderSourceReader::setSource(IMFMediaSource *source, QAudioFormat::SampleFormat sampleFormat) +{ + ComPtr<IMFMediaType> mediaType; + m_sourceReader.Reset(); + + if (!source) + return mediaType; + + ComPtr<IMFAttributes> attr; + MFCreateAttributes(attr.GetAddressOf(), 1); + if (FAILED(attr->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, this))) + return mediaType; + if (FAILED(attr->SetUINT32(MF_SOURCE_READER_DISCONNECT_MEDIASOURCE_ON_SHUTDOWN, TRUE))) + return mediaType; + + HRESULT hr = MFCreateSourceReaderFromMediaSource(source, attr.Get(), m_sourceReader.GetAddressOf()); + if (FAILED(hr)) { + qWarning() << "MFDecoderSourceReader: failed to set up source reader: " + << std::system_category().message(hr).c_str(); + return mediaType; + } + + m_sourceReader->SetStreamSelection(DWORD(MF_SOURCE_READER_ALL_STREAMS), FALSE); + m_sourceReader->SetStreamSelection(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM), TRUE); + + ComPtr<IMFMediaType> pPartialType; + MFCreateMediaType(pPartialType.GetAddressOf()); + pPartialType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); + pPartialType->SetGUID(MF_MT_SUBTYPE, sampleFormat == QAudioFormat::Float ? MFAudioFormat_Float : MFAudioFormat_PCM); + m_sourceReader->SetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM), nullptr, pPartialType.Get()); + m_sourceReader->GetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM), mediaType.GetAddressOf()); + // Ensure the stream is selected. + m_sourceReader->SetStreamSelection(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM), TRUE); + + return mediaType; +} + +void MFDecoderSourceReader::readNextSample() +{ + if (m_sourceReader) + m_sourceReader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, NULL, NULL, NULL, NULL); +} + +//from IUnknown +STDMETHODIMP MFDecoderSourceReader::QueryInterface(REFIID riid, LPVOID *ppvObject) +{ + if (!ppvObject) + return E_POINTER; + if (riid == IID_IMFSourceReaderCallback) { + *ppvObject = static_cast<IMFSourceReaderCallback*>(this); + } else if (riid == IID_IUnknown) { + *ppvObject = static_cast<IUnknown*>(this); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) MFDecoderSourceReader::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) MFDecoderSourceReader::Release() +{ + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) { + this->deleteLater(); + } + return cRef; +} + +//from IMFSourceReaderCallback +STDMETHODIMP MFDecoderSourceReader::OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex, + DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample) +{ + Q_UNUSED(hrStatus); + Q_UNUSED(dwStreamIndex); + Q_UNUSED(llTimestamp); + if (pSample) { + emit newSample(ComPtr<IMFSample>{pSample}); + } else if ((dwStreamFlags & MF_SOURCE_READERF_ENDOFSTREAM) == MF_SOURCE_READERF_ENDOFSTREAM) { + emit finished(); + } + return S_OK; +} + +QT_END_NAMESPACE + +#include "moc_mfdecodersourcereader_p.cpp" diff --git a/src/plugins/multimedia/windows/decoder/mfdecodersourcereader_p.h b/src/plugins/multimedia/windows/decoder/mfdecodersourcereader_p.h new file mode 100644 index 000000000..dee6f8bf5 --- /dev/null +++ b/src/plugins/multimedia/windows/decoder/mfdecodersourcereader_p.h @@ -0,0 +1,63 @@ +// 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 + +#ifndef MFDECODERSOURCEREADER_H +#define MFDECODERSOURCEREADER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <mfapi.h> +#include <mfidl.h> +#include <mfreadwrite.h> + +#include <QtCore/qobject.h> +#include "qaudioformat.h" +#include <private/qcomptr_p.h> + +QT_BEGIN_NAMESPACE + +class MFDecoderSourceReader : public QObject, public IMFSourceReaderCallback +{ + Q_OBJECT +public: + MFDecoderSourceReader() {} + ~MFDecoderSourceReader() override {} + + void clearSource() { m_sourceReader.Reset(); } + ComPtr<IMFMediaType> setSource(IMFMediaSource *source, QAudioFormat::SampleFormat); + + void readNextSample(); + + //from IUnknown + STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject) override; + STDMETHODIMP_(ULONG) AddRef() override; + STDMETHODIMP_(ULONG) Release() override; + + //from IMFSourceReaderCallback + STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex, + DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample) override; + STDMETHODIMP OnFlush(DWORD) override { return S_OK; } + STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *) override { return S_OK; } + +Q_SIGNALS: + void newSample(ComPtr<IMFSample>); + void finished(); + +private: + long m_cRef = 1; + ComPtr<IMFSourceReader> m_sourceReader; + +}; + +QT_END_NAMESPACE + +#endif//MFDECODERSOURCEREADER_H |