| /**************************************************************************** |
| ** |
| ** 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 "Wmcodecdsp.h" |
| #include "mfaudiodecodercontrol.h" |
| |
| MFAudioDecoderControl::MFAudioDecoderControl(QObject *parent) |
| : QAudioDecoderControl(parent) |
| , m_decoderSourceReader(new MFDecoderSourceReader) |
| , m_sourceResolver(new SourceResolver) |
| , m_resampler(0) |
| , m_state(QAudioDecoder::StoppedState) |
| , m_device(0) |
| , m_mfInputStreamID(0) |
| , m_mfOutputStreamID(0) |
| , m_bufferReady(false) |
| , m_duration(0) |
| , m_position(0) |
| , m_loadingSource(false) |
| , m_mfOutputType(0) |
| , m_convertSample(0) |
| , m_sourceReady(false) |
| , m_resamplerDirty(false) |
| { |
| CoCreateInstance(CLSID_CResamplerMediaObject, NULL, CLSCTX_INPROC_SERVER, IID_IMFTransform, (LPVOID*)(&m_resampler)); |
| if (!m_resampler) { |
| qCritical("MFAudioDecoderControl: Failed to create resampler(CLSID_CResamplerMediaObject)!"); |
| return; |
| } |
| m_resampler->AddInputStreams(1, &m_mfInputStreamID); |
| |
| connect(m_sourceResolver, SIGNAL(mediaSourceReady()), this, SLOT(handleMediaSourceReady())); |
| connect(m_sourceResolver, SIGNAL(error(long)), this, SLOT(handleMediaSourceError(long))); |
| connect(m_decoderSourceReader, SIGNAL(finished()), this, SLOT(handleSourceFinished())); |
| |
| QAudioFormat defaultFormat; |
| defaultFormat.setCodec("audio/pcm"); |
| setAudioFormat(defaultFormat); |
| } |
| |
| MFAudioDecoderControl::~MFAudioDecoderControl() |
| { |
| if (m_mfOutputType) |
| m_mfOutputType->Release(); |
| m_decoderSourceReader->shutdown(); |
| m_decoderSourceReader->Release(); |
| m_sourceResolver->Release(); |
| if (m_resampler) |
| m_resampler->Release(); |
| } |
| |
| QAudioDecoder::State MFAudioDecoderControl::state() const |
| { |
| return m_state; |
| } |
| |
| QString MFAudioDecoderControl::sourceFilename() const |
| { |
| return m_sourceFilename; |
| } |
| |
| void MFAudioDecoderControl::onSourceCleared() |
| { |
| bool positionDirty = false; |
| bool durationDirty = false; |
| if (m_position != 0) { |
| m_position = 0; |
| positionDirty = true; |
| } |
| if (m_duration != 0) { |
| m_duration = 0; |
| durationDirty = true; |
| } |
| if (positionDirty) |
| emit positionChanged(m_position); |
| if (durationDirty) |
| emit durationChanged(m_duration); |
| } |
| |
| void MFAudioDecoderControl::setSourceFilename(const QString &fileName) |
| { |
| if (!m_device && m_sourceFilename == fileName) |
| return; |
| m_sourceReady = false; |
| m_sourceResolver->cancel(); |
| m_decoderSourceReader->setSource(0, m_audioFormat); |
| m_device = 0; |
| m_sourceFilename = fileName; |
| if (!m_sourceFilename.isEmpty()) { |
| m_sourceResolver->shutdown(); |
| QUrl url; |
| if (m_sourceFilename.startsWith(':')) |
| url = QUrl(QStringLiteral("qrc%1").arg(m_sourceFilename)); |
| else |
| url = QUrl::fromLocalFile(m_sourceFilename); |
| m_sourceResolver->load(url, 0); |
| m_loadingSource = true; |
| } else { |
| onSourceCleared(); |
| } |
| emit sourceChanged(); |
| } |
| |
| QIODevice* MFAudioDecoderControl::sourceDevice() const |
| { |
| return m_device; |
| } |
| |
| void MFAudioDecoderControl::setSourceDevice(QIODevice *device) |
| { |
| if (m_device == device && m_sourceFilename.isEmpty()) |
| return; |
| m_sourceReady = false; |
| m_sourceResolver->cancel(); |
| m_decoderSourceReader->setSource(0, m_audioFormat); |
| m_sourceFilename.clear(); |
| m_device = device; |
| if (m_device) { |
| m_sourceResolver->shutdown(); |
| m_sourceResolver->load(QUrl(), m_device); |
| m_loadingSource = true; |
| } else { |
| onSourceCleared(); |
| } |
| emit sourceChanged(); |
| } |
| |
| void MFAudioDecoderControl::updateResamplerOutputType() |
| { |
| m_resamplerDirty = false; |
| if (m_audioFormat == m_sourceOutputFormat) |
| return; |
| HRESULT hr = m_resampler->SetOutputType(m_mfOutputStreamID, m_mfOutputType, 0); |
| if (SUCCEEDED(hr)) { |
| MFT_OUTPUT_STREAM_INFO streamInfo; |
| m_resampler->GetOutputStreamInfo(m_mfOutputStreamID, &streamInfo); |
| if ((streamInfo.dwFlags & (MFT_OUTPUT_STREAM_PROVIDES_SAMPLES | MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES)) == 0) { |
| //if resampler does not allocate output sample memory, we do it here |
| if (m_convertSample) { |
| m_convertSample->Release(); |
| m_convertSample = 0; |
| } |
| if (SUCCEEDED(MFCreateSample(&m_convertSample))) { |
| IMFMediaBuffer *mbuf = 0;; |
| if (SUCCEEDED(MFCreateMemoryBuffer(streamInfo.cbSize, &mbuf))) { |
| m_convertSample->AddBuffer(mbuf); |
| mbuf->Release(); |
| } |
| } |
| } |
| } else { |
| qWarning() << "MFAudioDecoderControl: failed to SetOutputType of resampler" << hr; |
| } |
| } |
| |
| void MFAudioDecoderControl::handleMediaSourceReady() |
| { |
| m_loadingSource = false; |
| m_sourceReady = true; |
| IMFMediaType *mediaType = m_decoderSourceReader->setSource(m_sourceResolver->mediaSource(), m_audioFormat); |
| m_sourceOutputFormat = QAudioFormat(); |
| |
| if (mediaType) { |
| m_sourceOutputFormat = m_audioFormat; |
| QAudioFormat af = m_audioFormat; |
| |
| UINT32 val = 0; |
| if (SUCCEEDED(mediaType->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &val))) { |
| m_sourceOutputFormat.setChannelCount(int(val)); |
| } |
| if (SUCCEEDED(mediaType->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &val))) { |
| m_sourceOutputFormat.setSampleRate(int(val)); |
| } |
| if (SUCCEEDED(mediaType->GetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, &val))) { |
| m_sourceOutputFormat.setSampleSize(int(val)); |
| } |
| |
| GUID subType; |
| if (SUCCEEDED(mediaType->GetGUID(MF_MT_SUBTYPE, &subType))) { |
| if (subType == MFAudioFormat_Float) { |
| m_sourceOutputFormat.setSampleType(QAudioFormat::Float); |
| } else if (m_sourceOutputFormat.sampleSize() == 8) { |
| m_sourceOutputFormat.setSampleType(QAudioFormat::UnSignedInt); |
| } else { |
| m_sourceOutputFormat.setSampleType(QAudioFormat::SignedInt); |
| } |
| } |
| if (m_sourceOutputFormat.sampleType() != QAudioFormat::Float) { |
| m_sourceOutputFormat.setByteOrder(QAudioFormat::LittleEndian); |
| } |
| |
| if (m_audioFormat.sampleType() != QAudioFormat::Float |
| && m_audioFormat.sampleType() != QAudioFormat::SignedInt) { |
| af.setSampleType(m_sourceOutputFormat.sampleType()); |
| } |
| if (af.sampleType() == QAudioFormat::SignedInt) { |
| af.setByteOrder(QAudioFormat::LittleEndian); |
| } |
| if (m_audioFormat.channelCount() <= 0) { |
| af.setChannelCount(m_sourceOutputFormat.channelCount()); |
| } |
| if (m_audioFormat.sampleRate() <= 0) { |
| af.setSampleRate(m_sourceOutputFormat.sampleRate()); |
| } |
| if (m_audioFormat.sampleSize() <= 0) { |
| af.setSampleSize(m_sourceOutputFormat.sampleSize()); |
| } |
| setAudioFormat(af); |
| } |
| |
| if (m_sourceResolver->mediaSource()) { |
| if (mediaType && m_resampler) { |
| HRESULT hr = S_OK; |
| hr = m_resampler->SetInputType(m_mfInputStreamID, mediaType, 0); |
| if (SUCCEEDED(hr)) { |
| updateResamplerOutputType(); |
| } else { |
| qWarning() << "MFAudioDecoderControl: failed to SetInputType of resampler" << hr; |
| } |
| } |
| IMFPresentationDescriptor *pd; |
| if (SUCCEEDED(m_sourceResolver->mediaSource()->CreatePresentationDescriptor(&pd))) { |
| UINT64 duration = 0; |
| pd->GetUINT64(MF_PD_DURATION, &duration); |
| pd->Release(); |
| duration /= 10000; |
| if (m_duration != qint64(duration)) { |
| m_duration = qint64(duration); |
| emit durationChanged(m_duration); |
| } |
| } |
| if (m_state == QAudioDecoder::DecodingState) { |
| activatePipeline(); |
| } |
| } else if (m_state != QAudioDecoder::StoppedState) { |
| m_state = QAudioDecoder::StoppedState; |
| emit stateChanged(m_state); |
| } |
| } |
| |
| void MFAudioDecoderControl::handleMediaSourceError(long hr) |
| { |
| Q_UNUSED(hr); |
| m_loadingSource = false; |
| m_decoderSourceReader->setSource(0, m_audioFormat); |
| if (m_state != QAudioDecoder::StoppedState) { |
| m_state = QAudioDecoder::StoppedState; |
| emit stateChanged(m_state); |
| } |
| } |
| |
| void MFAudioDecoderControl::activatePipeline() |
| { |
| Q_ASSERT(!m_bufferReady); |
| m_state = QAudioDecoder::DecodingState; |
| connect(m_decoderSourceReader, SIGNAL(sampleAdded()), this, SLOT(handleSampleAdded())); |
| if (m_resamplerDirty) { |
| updateResamplerOutputType(); |
| } |
| m_decoderSourceReader->reset(); |
| m_decoderSourceReader->readNextSample(); |
| if (m_position != 0) { |
| m_position = 0; |
| emit positionChanged(0); |
| } |
| } |
| |
| void MFAudioDecoderControl::start() |
| { |
| if (m_state != QAudioDecoder::StoppedState) |
| return; |
| |
| if (m_loadingSource) { |
| //deferred starting |
| m_state = QAudioDecoder::DecodingState; |
| emit stateChanged(m_state); |
| return; |
| } |
| |
| if (!m_decoderSourceReader->mediaSource()) |
| return; |
| activatePipeline(); |
| emit stateChanged(m_state); |
| } |
| |
| void MFAudioDecoderControl::stop() |
| { |
| if (m_state == QAudioDecoder::StoppedState) |
| return; |
| m_state = QAudioDecoder::StoppedState; |
| disconnect(m_decoderSourceReader, SIGNAL(sampleAdded()), this, SLOT(handleSampleAdded())); |
| if (m_bufferReady) { |
| m_bufferReady = false; |
| emit bufferAvailableChanged(m_bufferReady); |
| } |
| emit stateChanged(m_state); |
| } |
| |
| void MFAudioDecoderControl::handleSampleAdded() |
| { |
| QList<IMFSample*> samples = m_decoderSourceReader->takeSamples(); |
| Q_ASSERT(samples.count() > 0); |
| Q_ASSERT(!m_bufferReady); |
| Q_ASSERT(m_resampler); |
| LONGLONG sampleStartTime = 0; |
| IMFSample *firstSample = samples.first(); |
| firstSample->GetSampleTime(&sampleStartTime); |
| QByteArray abuf; |
| if (m_sourceOutputFormat == m_audioFormat) { |
| //no need for resampling |
| for (IMFSample *s : qAsConst(samples)) { |
| IMFMediaBuffer *buffer; |
| s->ConvertToContiguousBuffer(&buffer); |
| DWORD bufLen = 0; |
| BYTE *buf = 0; |
| if (SUCCEEDED(buffer->Lock(&buf, NULL, &bufLen))) { |
| abuf.push_back(QByteArray(reinterpret_cast<char*>(buf), bufLen)); |
| buffer->Unlock(); |
| } |
| buffer->Release(); |
| LONGLONG sampleTime = 0, sampleDuration = 0; |
| s->GetSampleTime(&sampleTime); |
| s->GetSampleDuration(&sampleDuration); |
| m_position = qint64(sampleTime + sampleDuration) / 10000; |
| s->Release(); |
| } |
| } else { |
| for (IMFSample *s : qAsConst(samples)) { |
| HRESULT hr = m_resampler->ProcessInput(m_mfInputStreamID, s, 0); |
| if (SUCCEEDED(hr)) { |
| MFT_OUTPUT_DATA_BUFFER outputDataBuffer; |
| outputDataBuffer.dwStreamID = m_mfOutputStreamID; |
| while (true) { |
| outputDataBuffer.pEvents = 0; |
| outputDataBuffer.dwStatus = 0; |
| outputDataBuffer.pSample = m_convertSample; |
| DWORD status = 0; |
| if (SUCCEEDED(m_resampler->ProcessOutput(0, 1, &outputDataBuffer, &status))) { |
| IMFMediaBuffer *buffer; |
| outputDataBuffer.pSample->ConvertToContiguousBuffer(&buffer); |
| DWORD bufLen = 0; |
| BYTE *buf = 0; |
| if (SUCCEEDED(buffer->Lock(&buf, NULL, &bufLen))) { |
| abuf.push_back(QByteArray(reinterpret_cast<char*>(buf), bufLen)); |
| buffer->Unlock(); |
| } |
| buffer->Release(); |
| } else { |
| break; |
| } |
| } |
| } |
| LONGLONG sampleTime = 0, sampleDuration = 0; |
| s->GetSampleTime(&sampleTime); |
| s->GetSampleDuration(&sampleDuration); |
| m_position = qint64(sampleTime + sampleDuration) / 10000; |
| s->Release(); |
| } |
| } |
| // WMF uses 100-nanosecond units, QAudioDecoder uses milliseconds, QAudioBuffer uses microseconds... |
| m_cachedAudioBuffer = QAudioBuffer(abuf, m_audioFormat, qint64(sampleStartTime / 10)); |
| m_bufferReady = true; |
| emit positionChanged(m_position); |
| emit bufferAvailableChanged(m_bufferReady); |
| emit bufferReady(); |
| } |
| |
| void MFAudioDecoderControl::handleSourceFinished() |
| { |
| stop(); |
| emit finished(); |
| } |
| |
| QAudioFormat MFAudioDecoderControl::audioFormat() const |
| { |
| return m_audioFormat; |
| } |
| |
| void MFAudioDecoderControl::setAudioFormat(const QAudioFormat &format) |
| { |
| if (m_audioFormat == format || !m_resampler) |
| return; |
| if (format.codec() != QLatin1String("audio/x-wav") && format.codec() != QLatin1String("audio/pcm")) { |
| qWarning("MFAudioDecoderControl does not accept non-pcm audio format!"); |
| return; |
| } |
| m_audioFormat = format; |
| |
| if (m_audioFormat.isValid()) { |
| IMFMediaType *mediaType = 0; |
| MFCreateMediaType(&mediaType); |
| mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); |
| if (format.sampleType() == QAudioFormat::Float) { |
| mediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_Float); |
| } else { |
| mediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM); |
| } |
| |
| mediaType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, UINT32(m_audioFormat.channelCount())); |
| mediaType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, UINT32(m_audioFormat.sampleRate())); |
| UINT32 alignmentBlock = UINT32(m_audioFormat.channelCount() * m_audioFormat.sampleSize() / 8); |
| mediaType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, alignmentBlock); |
| UINT32 avgBytesPerSec = UINT32(m_audioFormat.sampleRate() * m_audioFormat.sampleSize() / 8 * m_audioFormat.channelCount()); |
| mediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, avgBytesPerSec); |
| mediaType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, UINT32(m_audioFormat.sampleSize())); |
| mediaType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); |
| |
| if (m_mfOutputType) |
| m_mfOutputType->Release(); |
| m_mfOutputType = mediaType; |
| } else { |
| if (m_mfOutputType) |
| m_mfOutputType->Release(); |
| m_mfOutputType = NULL; |
| } |
| |
| if (m_sourceReady && m_state == QAudioDecoder::StoppedState) { |
| updateResamplerOutputType(); |
| } else { |
| m_resamplerDirty = true; |
| } |
| |
| emit formatChanged(m_audioFormat); |
| } |
| |
| QAudioBuffer MFAudioDecoderControl::read() |
| { |
| if (!m_bufferReady) |
| return QAudioBuffer(); |
| QAudioBuffer buffer = m_cachedAudioBuffer; |
| m_bufferReady = false; |
| emit bufferAvailableChanged(m_bufferReady); |
| m_decoderSourceReader->readNextSample(); |
| return buffer; |
| } |
| |
| bool MFAudioDecoderControl::bufferAvailable() const |
| { |
| return m_bufferReady; |
| } |
| |
| qint64 MFAudioDecoderControl::position() const |
| { |
| return m_position; |
| } |
| |
| qint64 MFAudioDecoderControl::duration() const |
| { |
| return m_duration; |
| } |