summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDoris Verria <doris.verria@qt.io>2021-08-20 15:20:54 +0200
committerDoris Verria <doris.verria@qt.io>2021-08-25 10:31:42 +0200
commit185ff0902181f29990c371d0b19b47915b172143 (patch)
tree733a45af5892e6c551c2ec261328d0855cbc1e8a
parentcbe038d8fbea6d9328a6e05bfca0ee68b7040a61 (diff)
downloadqtmultimedia-185ff0902181f29990c371d0b19b47915b172143.tar.gz
QAudioDecoder: Re-add API for setting desired audio format
Re-add API for setting/getting desired format for the decoded audio samples. Readd support on all backends with the exception of Android. - Add a warning in the docs for lack of support on Android. - Make the behavior of audioFormat() consistent on all platforms. It will return an invalid format if no format is set. Change Windows implementation to do so. - Fix the documentation for audioFormat(). The format returned is the one set to the decoder, and the decoded buffers may not have this format, in the case when it was set to an invalid one. - Some small fixes on darwin and Windows. Partly reverts af1182b5e29afa0cd69f929d54da224019707214. Fixes: QTBUG-95766 Pick-to: 6.2 Change-Id: Id1d6b33a6b9d916e272d3b9d9cee78b6f63609f3 Reviewed-by: Lars Knoll <lars.knoll@qt.io>
-rw-r--r--src/multimedia/audio/qaudiodecoder.cpp51
-rw-r--r--src/multimedia/audio/qaudiodecoder.h5
-rw-r--r--src/multimedia/platform/android/audio/qandroidaudiodecoder_p.h3
-rw-r--r--src/multimedia/platform/darwin/audio/avfaudiodecoder.mm34
-rw-r--r--src/multimedia/platform/darwin/audio/avfaudiodecoder_p.h4
-rw-r--r--src/multimedia/platform/gstreamer/audio/qgstreameraudiodecoder.cpp25
-rw-r--r--src/multimedia/platform/gstreamer/audio/qgstreameraudiodecoder_p.h4
-rw-r--r--src/multimedia/platform/qplatformaudiodecoder.cpp12
-rw-r--r--src/multimedia/platform/qplatformaudiodecoder_p.h5
-rw-r--r--src/multimedia/platform/windows/decoder/mfaudiodecodercontrol.cpp209
-rw-r--r--src/multimedia/platform/windows/decoder/mfaudiodecodercontrol_p.h8
-rw-r--r--src/multimedia/platform/windows/decoder/mfdecodersourcereader.cpp9
-rw-r--r--src/multimedia/platform/windows/decoder/mfdecodersourcereader_p.h2
-rw-r--r--tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp140
-rw-r--r--tests/auto/unit/mockbackend/qmockaudiodecoder.h13
-rw-r--r--tests/auto/unit/multimedia/qaudiodecoder/tst_qaudiodecoder.cpp28
16 files changed, 510 insertions, 42 deletions
diff --git a/src/multimedia/audio/qaudiodecoder.cpp b/src/multimedia/audio/qaudiodecoder.cpp
index 610c6b332..e0587d31a 100644
--- a/src/multimedia/audio/qaudiodecoder.cpp
+++ b/src/multimedia/audio/qaudiodecoder.cpp
@@ -217,6 +217,49 @@ void QAudioDecoder::setSourceDevice(QIODevice *device)
}
/*!
+ Returns the audio format the decoder is set to.
+
+ \note This may be different than the format of the decoded
+ samples, if the audio format was set to an invalid one.
+
+ \sa setAudioFormat(), formatChanged()
+*/
+QAudioFormat QAudioDecoder::audioFormat() const
+{
+ if (decoder)
+ return decoder->audioFormat();
+ return QAudioFormat();
+}
+
+/*!
+ Set the desired audio format for decoded samples to \a format.
+
+ This property can only be set while the decoder is stopped.
+ Setting this property at other times will be ignored.
+
+ If the decoder does not support this format, \l error() will
+ be set to \c FormatError.
+
+ If you do not specify a format, the format of the decoded
+ audio itself will be used. Otherwise, some format conversion
+ will be applied.
+
+ If you wish to reset the decoded format to that of the original
+ audio file, you can specify an invalid \a format.
+
+ \warning Setting a desired audio format is not yet supported
+ on Android.
+*/
+void QAudioDecoder::setAudioFormat(const QAudioFormat &format)
+{
+ if (isDecoding())
+ return;
+
+ if (decoder != nullptr)
+ decoder->setAudioFormat(format);
+}
+
+/*!
Returns true if a buffer is available to be read,
and false otherwise. If there is no buffer available, calling
the \l read() function will return an invalid buffer.
@@ -301,6 +344,14 @@ QAudioBuffer QAudioDecoder::read() const
*/
/*!
+ \fn void QAudioDecoder::formatChanged(const QAudioFormat &format)
+
+ Signals that the current audio format of the decoder has changed to \a format.
+
+ \sa audioFormat(), setAudioFormat()
+*/
+
+/*!
\fn void QAudioDecoder::bufferReady()
Signals that a new decoded audio buffer is available to be read.
diff --git a/src/multimedia/audio/qaudiodecoder.h b/src/multimedia/audio/qaudiodecoder.h
index 465bb3974..451c3a657 100644
--- a/src/multimedia/audio/qaudiodecoder.h
+++ b/src/multimedia/audio/qaudiodecoder.h
@@ -79,6 +79,9 @@ public:
QIODevice* sourceDevice() const;
void setSourceDevice(QIODevice *device);
+ QAudioFormat audioFormat() const;
+ void setAudioFormat(const QAudioFormat &format);
+
Error error() const;
QString errorString() const;
@@ -98,6 +101,8 @@ Q_SIGNALS:
void finished();
void isDecodingChanged(bool);
+ void formatChanged(const QAudioFormat &format);
+
void error(QAudioDecoder::Error error);
void sourceChanged();
diff --git a/src/multimedia/platform/android/audio/qandroidaudiodecoder_p.h b/src/multimedia/platform/android/audio/qandroidaudiodecoder_p.h
index 397deb920..9fe3b9e2a 100644
--- a/src/multimedia/platform/android/audio/qandroidaudiodecoder_p.h
+++ b/src/multimedia/platform/android/audio/qandroidaudiodecoder_p.h
@@ -110,6 +110,9 @@ public:
void start() override;
void stop() override;
+ QAudioFormat audioFormat() const override { return {}; }
+ void setAudioFormat(const QAudioFormat &format) override {}
+
QAudioBuffer read() override;
bool bufferAvailable() const override;
diff --git a/src/multimedia/platform/darwin/audio/avfaudiodecoder.mm b/src/multimedia/platform/darwin/audio/avfaudiodecoder.mm
index eb91223c8..079c4b39e 100644
--- a/src/multimedia/platform/darwin/audio/avfaudiodecoder.mm
+++ b/src/multimedia/platform/darwin/audio/avfaudiodecoder.mm
@@ -46,7 +46,7 @@
#include <AVFoundation/AVFoundation.h>
-#define MAX_BUFFERS_IN_QUEUE 6
+#define MAX_BUFFERS_IN_QUEUE 10
QT_USE_NAMESPACE
@@ -90,6 +90,8 @@ QT_USE_NAMESPACE
return;
const AudioStreamBasicDescription* const asbd = CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription);
QAudioFormat qtFormat = CoreAudioUtils::toQAudioFormat(*asbd);
+ if (qtFormat.sampleFormat() == QAudioFormat::Unknown && asbd->mBitsPerChannel == 8)
+ qtFormat.setSampleFormat(QAudioFormat::UInt8);
if (!qtFormat.isValid())
return;
@@ -359,8 +361,8 @@ void AVFAudioDecoder::start()
void AVFAudioDecoder::stop()
{
- if (m_asset)
- [m_asset cancelLoading];
+ m_cachedBuffers.clear();
+
if (m_reader)
[m_reader cancelReading];
@@ -379,6 +381,19 @@ void AVFAudioDecoder::stop()
setIsDecoding(false);
}
+QAudioFormat AVFAudioDecoder::audioFormat() const
+{
+ return m_format;
+}
+
+void AVFAudioDecoder::setAudioFormat(const QAudioFormat &format)
+{
+ if (m_format != format) {
+ m_format = format;
+ emit formatChanged(m_format);
+ }
+}
+
QAudioBuffer AVFAudioDecoder::read()
{
if (!m_buffersAvailable)
@@ -431,10 +446,15 @@ void AVFAudioDecoder::initAssetReader()
// Set format
QAudioFormat format;
- format = qt_format_for_audio_track(track);
- if (!format.isValid()) {
- processInvalidMedia(QAudioDecoder::FormatError, tr("Unsupported source format"));
- return;
+ if (m_format.isValid()) {
+ format = m_format;
+ } else {
+ format = qt_format_for_audio_track(track);
+ if (!format.isValid())
+ {
+ processInvalidMedia(QAudioDecoder::FormatError, tr("Unsupported source format"));
+ return;
+ }
}
// Set duration
diff --git a/src/multimedia/platform/darwin/audio/avfaudiodecoder_p.h b/src/multimedia/platform/darwin/audio/avfaudiodecoder_p.h
index 5348b06fd..73a8cbd38 100644
--- a/src/multimedia/platform/darwin/audio/avfaudiodecoder_p.h
+++ b/src/multimedia/platform/darwin/audio/avfaudiodecoder_p.h
@@ -84,6 +84,9 @@ public:
void start() override;
void stop() override;
+ QAudioFormat audioFormat() const override;
+ void setAudioFormat(const QAudioFormat &format) override;
+
QAudioBuffer read() override;
bool bufferAvailable() const override;
@@ -104,6 +107,7 @@ private:
QUrl m_source;
QIODevice *m_device = nullptr;
+ QAudioFormat m_format;
int m_buffersAvailable = 0;
QList<QAudioBuffer> m_cachedBuffers;
diff --git a/src/multimedia/platform/gstreamer/audio/qgstreameraudiodecoder.cpp b/src/multimedia/platform/gstreamer/audio/qgstreameraudiodecoder.cpp
index db23ac80f..dc44a1c38 100644
--- a/src/multimedia/platform/gstreamer/audio/qgstreameraudiodecoder.cpp
+++ b/src/multimedia/platform/gstreamer/audio/qgstreameraudiodecoder.cpp
@@ -331,9 +331,15 @@ void QGstreamerAudioDecoder::start()
// Set audio format
if (m_appSink) {
- // We want whatever the native audio format is
- setAudioFlags(true);
- gst_app_sink_set_caps(m_appSink, nullptr);
+ if (mFormat.isValid()) {
+ setAudioFlags(false);
+ QGstMutableCaps caps = QGstUtils::capsForAudioFormat(mFormat);
+ gst_app_sink_set_caps(m_appSink, caps.get());
+ } else {
+ // We want whatever the native audio format is
+ setAudioFlags(true);
+ gst_app_sink_set_caps(m_appSink, nullptr);
+ }
}
if (m_playbin.setState(GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
@@ -370,6 +376,19 @@ void QGstreamerAudioDecoder::stop()
setIsDecoding(false);
}
+QAudioFormat QGstreamerAudioDecoder::audioFormat() const
+{
+ return mFormat;
+}
+
+void QGstreamerAudioDecoder::setAudioFormat(const QAudioFormat &format)
+{
+ if (mFormat != format) {
+ mFormat = format;
+ emit formatChanged(mFormat);
+ }
+}
+
QAudioBuffer QGstreamerAudioDecoder::read()
{
QAudioBuffer audioBuffer;
diff --git a/src/multimedia/platform/gstreamer/audio/qgstreameraudiodecoder_p.h b/src/multimedia/platform/gstreamer/audio/qgstreameraudiodecoder_p.h
index de9d59baf..693b00c3d 100644
--- a/src/multimedia/platform/gstreamer/audio/qgstreameraudiodecoder_p.h
+++ b/src/multimedia/platform/gstreamer/audio/qgstreameraudiodecoder_p.h
@@ -90,6 +90,9 @@ public:
void start() override;
void stop() override;
+ QAudioFormat audioFormat() const override;
+ void setAudioFormat(const QAudioFormat &format) override;
+
QAudioBuffer read() override;
bool bufferAvailable() const override;
@@ -125,6 +128,7 @@ private:
QUrl mSource;
QIODevice *mDevice = nullptr;
+ QAudioFormat mFormat;
mutable QMutex m_buffersMutex;
int m_buffersAvailable = 0;
diff --git a/src/multimedia/platform/qplatformaudiodecoder.cpp b/src/multimedia/platform/qplatformaudiodecoder.cpp
index 681e73e0c..cff2ef4ff 100644
--- a/src/multimedia/platform/qplatformaudiodecoder.cpp
+++ b/src/multimedia/platform/qplatformaudiodecoder.cpp
@@ -172,6 +172,18 @@ void QPlatformAudioDecoder::sourceChanged()
}
/*!
+ \fn QPlatformAudioDecoder::formatChanged(const QAudioFormat &format)
+
+ Signals that the current audio format of the decoder has changed to \a format.
+
+ \sa audioFormat(), setAudioFormat()
+*/
+void QPlatformAudioDecoder::formatChanged(const QAudioFormat &format)
+{
+ emit q->formatChanged(format);
+}
+
+/*!
\fn void QPlatformAudioDecoder::finished()
Signals that the decoding has finished successfully.
diff --git a/src/multimedia/platform/qplatformaudiodecoder_p.h b/src/multimedia/platform/qplatformaudiodecoder_p.h
index e097b79fa..b9334102a 100644
--- a/src/multimedia/platform/qplatformaudiodecoder_p.h
+++ b/src/multimedia/platform/qplatformaudiodecoder_p.h
@@ -75,12 +75,17 @@ public:
virtual void start() = 0;
virtual void stop() = 0;
+ virtual QAudioFormat audioFormat() const = 0;
+ virtual void setAudioFormat(const QAudioFormat &format) = 0;
+
virtual QAudioBuffer read() = 0;
virtual bool bufferAvailable() const = 0;
virtual qint64 position() const = 0;
virtual qint64 duration() const = 0;
+ void formatChanged(const QAudioFormat &format);
+
void sourceChanged();
void error(int error, const QString &errorString);
diff --git a/src/multimedia/platform/windows/decoder/mfaudiodecodercontrol.cpp b/src/multimedia/platform/windows/decoder/mfaudiodecodercontrol.cpp
index 7a460533d..de1311835 100644
--- a/src/multimedia/platform/windows/decoder/mfaudiodecodercontrol.cpp
+++ b/src/multimedia/platform/windows/decoder/mfaudiodecodercontrol.cpp
@@ -44,20 +44,32 @@ MFAudioDecoderControl::MFAudioDecoderControl(QAudioDecoder *parent)
: QPlatformAudioDecoder(parent)
, m_decoderSourceReader(new MFDecoderSourceReader)
, m_sourceResolver(new SourceResolver)
+ , m_resampler(0)
, m_device(0)
, m_mfInputStreamID(0)
, m_mfOutputStreamID(0)
, m_bufferReady(false)
- , m_duration(0)
- , m_position(0)
+ , m_duration(-1)
+ , m_position(-1)
, 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;
+ setAudioFormat(defaultFormat);
}
MFAudioDecoderControl::~MFAudioDecoderControl()
@@ -67,6 +79,8 @@ MFAudioDecoderControl::~MFAudioDecoderControl()
m_decoderSourceReader->shutdown();
m_decoderSourceReader->Release();
m_sourceResolver->Release();
+ if (m_resampler)
+ m_resampler->Release();
}
QUrl MFAudioDecoderControl::source() const
@@ -78,12 +92,12 @@ void MFAudioDecoderControl::onSourceCleared()
{
bool positionDirty = false;
bool durationDirty = false;
- if (m_position != 0) {
- m_position = 0;
+ if (m_position != -1) {
+ m_position = -1;
positionDirty = true;
}
- if (m_duration != 0) {
- m_duration = 0;
+ if (m_duration != -1) {
+ m_duration = -1;
durationDirty = true;
}
if (positionDirty)
@@ -98,7 +112,7 @@ void MFAudioDecoderControl::setSource(const QUrl &fileName)
return;
m_sourceReady = false;
m_sourceResolver->cancel();
- m_decoderSourceReader->setSource(nullptr);
+ m_decoderSourceReader->setSource(nullptr, m_audioFormat);
m_device = 0;
m_source = fileName;
if (!m_source.isEmpty()) {
@@ -122,7 +136,7 @@ void MFAudioDecoderControl::setSourceDevice(QIODevice *device)
return;
m_sourceReady = false;
m_sourceResolver->cancel();
- m_decoderSourceReader->setSource(nullptr);
+ m_decoderSourceReader->setSource(nullptr, m_audioFormat);
m_source.clear();
m_device = device;
if (m_device) {
@@ -135,14 +149,44 @@ void MFAudioDecoderControl::setSourceDevice(QIODevice *device)
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());
+ IMFMediaType *mediaType = m_decoderSourceReader->setSource(m_sourceResolver->mediaSource(), m_audioFormat);
m_sourceOutputFormat = QAudioFormat();
if (mediaType) {
+ m_sourceOutputFormat = m_audioFormat;
+
UINT32 val = 0;
if (SUCCEEDED(mediaType->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &val))) {
m_sourceOutputFormat.setChannelCount(int(val));
@@ -165,9 +209,24 @@ void MFAudioDecoderControl::handleMediaSourceReady()
m_sourceOutputFormat.setSampleFormat(QAudioFormat::Int32);
}
}
+
+ if (!m_audioFormat.isValid())
+ setResamplerOutputFormat(m_sourceOutputFormat);
+ else {
+ setResamplerOutputFormat(m_audioFormat);
+ }
}
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;
@@ -187,11 +246,48 @@ void MFAudioDecoderControl::handleMediaSourceReady()
}
}
+void MFAudioDecoderControl::setResamplerOutputFormat(const QAudioFormat &format)
+{
+ if (format.isValid()) {
+ IMFMediaType *mediaType = 0;
+ MFCreateMediaType(&mediaType);
+ mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
+ if (format.sampleFormat() == 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(format.channelCount()));
+ mediaType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, UINT32(format.sampleRate()));
+ UINT32 alignmentBlock = UINT32(format.bytesPerFrame());
+ mediaType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, alignmentBlock);
+ UINT32 avgBytesPerSec = UINT32(format.sampleRate() * format.bytesPerFrame());
+ mediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, avgBytesPerSec);
+ mediaType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, UINT32(format.bytesPerSample()*8));
+ 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 && !isDecoding()) {
+ updateResamplerOutputType();
+ } else {
+ m_resamplerDirty = true;
+ }
+}
+
void MFAudioDecoderControl::handleMediaSourceError(long hr)
{
Q_UNUSED(hr);
m_loadingSource = false;
- m_decoderSourceReader->setSource(nullptr);
+ m_decoderSourceReader->setSource(nullptr, m_audioFormat);
setIsDecoding(false);
}
@@ -200,11 +296,14 @@ void MFAudioDecoderControl::activatePipeline()
Q_ASSERT(!m_bufferReady);
setIsDecoding(true);
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;
- positionChanged(0);
+ if (m_position != -1) {
+ m_position = -1;
+ positionChanged(-1);
}
}
@@ -241,29 +340,67 @@ 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;
- 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();
+ QAudioFormat bufferFormat;
+ if (!m_audioFormat.isValid()) {
+ bufferFormat = m_sourceOutputFormat;
+ //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 {
+ bufferFormat = m_audioFormat;
+ 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();
}
- buffer->Release();
- 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_sourceOutputFormat, qint64(sampleStartTime / 10));
+ m_cachedAudioBuffer = QAudioBuffer(abuf, bufferFormat, qint64(sampleStartTime / 10));
m_bufferReady = true;
emit positionChanged(m_position);
emit bufferAvailableChanged(m_bufferReady);
@@ -276,6 +413,20 @@ void MFAudioDecoderControl::handleSourceFinished()
emit finished();
}
+QAudioFormat MFAudioDecoderControl::audioFormat() const
+{
+ return m_audioFormat;
+}
+
+void MFAudioDecoderControl::setAudioFormat(const QAudioFormat &format)
+{
+ if (m_audioFormat == format || !m_resampler)
+ return;
+ m_audioFormat = format;
+ setResamplerOutputFormat(format);
+ emit formatChanged(m_audioFormat);
+}
+
QAudioBuffer MFAudioDecoderControl::read()
{
if (!m_bufferReady)
diff --git a/src/multimedia/platform/windows/decoder/mfaudiodecodercontrol_p.h b/src/multimedia/platform/windows/decoder/mfaudiodecodercontrol_p.h
index 8cd769e67..6efdbb346 100644
--- a/src/multimedia/platform/windows/decoder/mfaudiodecodercontrol_p.h
+++ b/src/multimedia/platform/windows/decoder/mfaudiodecodercontrol_p.h
@@ -73,6 +73,9 @@ public:
void start();
void stop();
+ QAudioFormat audioFormat() const override;
+ void setAudioFormat(const QAudioFormat &format) override;
+
QAudioBuffer read();
bool bufferAvailable() const;
@@ -86,13 +89,17 @@ private Q_SLOTS:
void handleSourceFinished();
private:
+ void updateResamplerOutputType();
void activatePipeline();
void onSourceCleared();
+ void setResamplerOutputFormat(const QAudioFormat &format);
MFDecoderSourceReader *m_decoderSourceReader;
SourceResolver *m_sourceResolver;
+ IMFTransform *m_resampler;
QUrl m_source;
QIODevice *m_device;
+ QAudioFormat m_audioFormat;
DWORD m_mfInputStreamID;
DWORD m_mfOutputStreamID;
bool m_bufferReady;
@@ -104,6 +111,7 @@ private:
IMFSample *m_convertSample;
QAudioFormat m_sourceOutputFormat;
bool m_sourceReady;
+ bool m_resamplerDirty;
};
#endif//MFAUDIODECODERCONTROL_H
diff --git a/src/multimedia/platform/windows/decoder/mfdecodersourcereader.cpp b/src/multimedia/platform/windows/decoder/mfdecodersourcereader.cpp
index d10839a63..381c60dc5 100644
--- a/src/multimedia/platform/windows/decoder/mfdecodersourcereader.cpp
+++ b/src/multimedia/platform/windows/decoder/mfdecodersourcereader.cpp
@@ -64,7 +64,7 @@ IMFMediaSource* MFDecoderSourceReader::mediaSource()
return m_source;
}
-IMFMediaType* MFDecoderSourceReader::setSource(IMFMediaSource *source)
+IMFMediaType* MFDecoderSourceReader::setSource(IMFMediaSource *source, const QAudioFormat &audioFormat)
{
IMFMediaType *mediaType = NULL;
if (m_source == source)
@@ -90,7 +90,12 @@ IMFMediaType* MFDecoderSourceReader::setSource(IMFMediaSource *source)
IMFMediaType *pPartialType = NULL;
MFCreateMediaType(&pPartialType);
pPartialType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
- pPartialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);
+
+ if (audioFormat.sampleFormat() == QAudioFormat::Float) {
+ pPartialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_Float);
+ } else {
+ pPartialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);
+ }
m_sourceReader->SetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM), NULL, pPartialType);
pPartialType->Release();
diff --git a/src/multimedia/platform/windows/decoder/mfdecodersourcereader_p.h b/src/multimedia/platform/windows/decoder/mfdecodersourcereader_p.h
index 6730dbf7f..7d63f5368 100644
--- a/src/multimedia/platform/windows/decoder/mfdecodersourcereader_p.h
+++ b/src/multimedia/platform/windows/decoder/mfdecodersourcereader_p.h
@@ -69,7 +69,7 @@ public:
void shutdown();
IMFMediaSource* mediaSource();
- IMFMediaType* setSource(IMFMediaSource *source);
+ IMFMediaType* setSource(IMFMediaSource *source, const QAudioFormat &audioFormat);
void reset();
void readNextSample();
diff --git a/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp b/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp
index 5e32efa16..f6ec6fe4b 100644
--- a/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp
+++ b/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp
@@ -105,6 +105,7 @@ void tst_QAudioDecoderBackend::fileTest()
QVERIFY(!d.isDecoding());
QVERIFY(d.bufferAvailable() == false);
QCOMPARE(d.source(), QString(""));
+ QVERIFY(d.audioFormat() == QAudioFormat());
// Test local file
QFileInfo fileInfo(QFINDTESTDATA(TEST_FILE_NAME));
@@ -140,6 +141,8 @@ void tst_QAudioDecoderBackend::fileTest()
QCOMPARE(buffer.format().sampleFormat(), QAudioFormat::Int16);
QCOMPARE(buffer.byteCount(), buffer.sampleCount() * 2); // 16bit mono
+ // The decoder should still have no format set
+ QVERIFY(d.audioFormat() == QAudioFormat());
QVERIFY(errorSpy.isEmpty());
duration += buffer.duration();
@@ -180,6 +183,90 @@ void tst_QAudioDecoderBackend::fileTest()
QTRY_COMPARE(durationSpy.count(), 2);
QCOMPARE(d.duration(), qint64(-1));
QVERIFY(!d.bufferAvailable());
+ readySpy.clear();
+ bufferChangedSpy.clear();
+ isDecodingSpy.clear();
+ durationSpy.clear();
+ finishedSpy.clear();
+ positionSpy.clear();
+
+ // change output audio format
+ QAudioFormat format;
+ format.setChannelCount(2);
+ format.setSampleRate(11050);
+ format.setSampleFormat(QAudioFormat::UInt8);
+
+ d.setAudioFormat(format);
+
+ // We expect 1 second still, at 11050 * 2 samples == 22k samples.
+ // (at 1 byte/sample -> 22kb)
+
+ // Make sure it stuck
+ QVERIFY(d.audioFormat() == format);
+
+ duration = 0;
+ sampleCount = 0;
+ byteCount = 0;
+
+ d.start();
+ QTRY_VERIFY(d.isDecoding());
+ QTRY_VERIFY(!isDecodingSpy.isEmpty());
+ QTRY_VERIFY(!readySpy.isEmpty());
+ QTRY_VERIFY(!bufferChangedSpy.isEmpty());
+ QVERIFY(d.bufferAvailable());
+ QTRY_VERIFY(!durationSpy.isEmpty());
+ QVERIFY(qAbs(d.duration() - 1000) < 20);
+
+ buffer = d.read();
+ QVERIFY(buffer.isValid());
+ // See if we got the right format
+ QVERIFY(buffer.format() == format);
+
+ // The decoder should still have the same format
+ QVERIFY(d.audioFormat() == format);
+
+ QVERIFY(errorSpy.isEmpty());
+
+ duration += buffer.duration();
+ sampleCount += buffer.sampleCount();
+ byteCount += buffer.byteCount();
+
+ // Now drain the decoder
+ if (duration < 998000) {
+ QTRY_COMPARE(d.bufferAvailable(), true);
+ }
+
+ while (d.bufferAvailable()) {
+ buffer = d.read();
+ QVERIFY(buffer.isValid());
+ QTRY_VERIFY(!positionSpy.isEmpty());
+ QVERIFY(positionSpy.takeLast().at(0).toLongLong() == qint64(duration / 1000));
+ QVERIFY(d.position() - (duration / 1000) < 20);
+
+ duration += buffer.duration();
+ sampleCount += buffer.sampleCount();
+ byteCount += buffer.byteCount();
+
+ if (duration < 998000) {
+ QTRY_COMPARE(d.bufferAvailable(), true);
+ }
+ }
+
+ // Resampling might end up with fewer or more samples
+ // so be a bit sloppy
+ QVERIFY(qAbs(sampleCount - 22047) < 100);
+ QVERIFY(qAbs(byteCount - 22047) < 100);
+ QVERIFY(qAbs(qint64(duration) - 1000000) < 20000);
+ QVERIFY(qAbs((d.position() + (buffer.duration() / 1000)) - 1000) < 20);
+ QTRY_COMPARE(finishedSpy.count(), 1);
+ QVERIFY(!d.bufferAvailable());
+ QVERIFY(!d.isDecoding());
+
+ d.stop();
+ QTRY_VERIFY(!d.isDecoding());
+ QTRY_COMPARE(durationSpy.count(), 2);
+ QCOMPARE(d.duration(), qint64(-1));
+ QVERIFY(!d.bufferAvailable());
}
/*
@@ -195,6 +282,7 @@ void tst_QAudioDecoderBackend::unsupportedFileTest()
QVERIFY(!d.isDecoding());
QVERIFY(d.bufferAvailable() == false);
QCOMPARE(d.source(), QString(""));
+ QVERIFY(d.audioFormat() == QAudioFormat());
// Test local file
QFileInfo fileInfo(QFINDTESTDATA(TEST_UNSUPPORTED_FILE_NAME));
@@ -215,6 +303,7 @@ void tst_QAudioDecoderBackend::unsupportedFileTest()
d.start();
QTRY_VERIFY(!d.isDecoding());
QVERIFY(!d.bufferAvailable());
+ QCOMPARE(d.audioFormat(), QAudioFormat());
QCOMPARE(d.duration(), qint64(-1));
QCOMPARE(d.position(), qint64(-1));
@@ -272,6 +361,7 @@ void tst_QAudioDecoderBackend::corruptedFileTest()
QVERIFY(!d.isDecoding());
QVERIFY(d.bufferAvailable() == false);
QCOMPARE(d.source(), QUrl());
+ QVERIFY(d.audioFormat() == QAudioFormat());
// Test local file
QFileInfo fileInfo(QFINDTESTDATA(TEST_CORRUPTED_FILE_NAME));
@@ -292,6 +382,7 @@ void tst_QAudioDecoderBackend::corruptedFileTest()
d.start();
QTRY_VERIFY(!d.isDecoding());
QVERIFY(!d.bufferAvailable());
+ QCOMPARE(d.audioFormat(), QAudioFormat());
QCOMPARE(d.duration(), qint64(-1));
QCOMPARE(d.position(), qint64(-1));
@@ -345,6 +436,7 @@ void tst_QAudioDecoderBackend::invalidSource()
QVERIFY(!d.isDecoding());
QVERIFY(d.bufferAvailable() == false);
QCOMPARE(d.source(), QUrl());
+ QVERIFY(d.audioFormat() == QAudioFormat());
// Test invalid file source
QFileInfo fileInfo(TEST_INVALID_SOURCE);
@@ -365,6 +457,7 @@ void tst_QAudioDecoderBackend::invalidSource()
d.start();
QTRY_VERIFY(!d.isDecoding());
QVERIFY(!d.bufferAvailable());
+ QCOMPARE(d.audioFormat(), QAudioFormat());
QCOMPARE(d.duration(), qint64(-1));
QCOMPARE(d.position(), qint64(-1));
@@ -399,6 +492,7 @@ void tst_QAudioDecoderBackend::invalidSource()
d.start();
QTRY_VERIFY(!d.isDecoding());
QVERIFY(!d.bufferAvailable());
+ QCOMPARE(d.audioFormat(), QAudioFormat());
QCOMPARE(d.duration(), qint64(-1));
QCOMPARE(d.position(), qint64(-1));
@@ -447,6 +541,7 @@ void tst_QAudioDecoderBackend::deviceTest()
QVERIFY(!d.isDecoding());
QVERIFY(d.bufferAvailable() == false);
QCOMPARE(d.source(), QString(""));
+ QVERIFY(d.audioFormat() == QAudioFormat());
QFileInfo fileInfo(QFINDTESTDATA(TEST_FILE_NAME));
QFile file(fileInfo.absoluteFilePath());
@@ -456,6 +551,9 @@ void tst_QAudioDecoderBackend::deviceTest()
QVERIFY(d.sourceDevice() == &file);
QVERIFY(d.source().isEmpty());
+ // We haven't set the format yet
+ QVERIFY(d.audioFormat() == QAudioFormat());
+
d.start();
QTRY_VERIFY(d.isDecoding());
@@ -511,6 +609,48 @@ void tst_QAudioDecoderBackend::deviceTest()
QVERIFY(!d.bufferAvailable());
QTRY_COMPARE(durationSpy.count(), 2);
QCOMPARE(d.duration(), qint64(-1));
+ readySpy.clear();
+ bufferChangedSpy.clear();
+ isDecodingSpy.clear();
+ durationSpy.clear();
+ finishedSpy.clear();
+ positionSpy.clear();
+
+ // Now try changing formats
+ QAudioFormat format;
+ format.setChannelCount(2);
+ format.setSampleRate(8000);
+ format.setSampleFormat(QAudioFormat::UInt8);
+
+ d.setAudioFormat(format);
+
+ // Make sure it stuck
+ QVERIFY(d.audioFormat() == format);
+
+ d.start();
+ QTRY_VERIFY(d.isDecoding());
+ QTRY_VERIFY(!isDecodingSpy.isEmpty());
+ QTRY_VERIFY(!readySpy.isEmpty());
+ QTRY_VERIFY(!bufferChangedSpy.isEmpty());
+ QVERIFY(d.bufferAvailable());
+ QTRY_VERIFY(!durationSpy.isEmpty());
+ QVERIFY(qAbs(d.duration() - 1000) < 20);
+
+ buffer = d.read();
+ QVERIFY(buffer.isValid());
+ // See if we got the right format
+ QVERIFY(buffer.format() == format);
+
+ // The decoder should still have the same format
+ QVERIFY(d.audioFormat() == format);
+
+ QVERIFY(errorSpy.isEmpty());
+
+ d.stop();
+ QTRY_VERIFY(!d.isDecoding());
+ QVERIFY(!d.bufferAvailable());
+ QTRY_COMPARE(durationSpy.count(), 2);
+ QCOMPARE(d.duration(), qint64(-1));
}
QTEST_MAIN(tst_QAudioDecoderBackend)
diff --git a/tests/auto/unit/mockbackend/qmockaudiodecoder.h b/tests/auto/unit/mockbackend/qmockaudiodecoder.h
index 15c95b9df..b31bd29a6 100644
--- a/tests/auto/unit/mockbackend/qmockaudiodecoder.h
+++ b/tests/auto/unit/mockbackend/qmockaudiodecoder.h
@@ -82,6 +82,19 @@ public:
stop();
}
+ QAudioFormat audioFormat() const override
+ {
+ return mFormat;
+ }
+
+ void setAudioFormat(const QAudioFormat &format) override
+ {
+ if (mFormat != format) {
+ mFormat = format;
+ emit formatChanged(mFormat);
+ }
+ }
+
// When decoding we decode to first buffer, then second buffer
// we then stop until the first is read again and so on, for
// 5 buffers
diff --git a/tests/auto/unit/multimedia/qaudiodecoder/tst_qaudiodecoder.cpp b/tests/auto/unit/multimedia/qaudiodecoder/tst_qaudiodecoder.cpp
index 1c200cb21..6049bd4a2 100644
--- a/tests/auto/unit/multimedia/qaudiodecoder/tst_qaudiodecoder.cpp
+++ b/tests/auto/unit/multimedia/qaudiodecoder/tst_qaudiodecoder.cpp
@@ -219,6 +219,28 @@ void tst_QAudioDecoder::format()
b = d.read();
QVERIFY(b.format().isValid());
+ QVERIFY(d.audioFormat() == b.format());
+
+ // Setting format while decoding is forbidden
+ QAudioFormat f(d.audioFormat());
+ f.setChannelCount(2);
+
+ d.setAudioFormat(f);
+ QVERIFY(d.audioFormat() != f);
+ QVERIFY(d.audioFormat() == b.format());
+
+ // Now stop, and set something specific
+ d.stop();
+ d.setAudioFormat(f);
+ QVERIFY(d.audioFormat() == f);
+
+ // Decode again
+ d.start();
+ QTRY_VERIFY(d.bufferAvailable());
+
+ b = d.read();
+ QVERIFY(d.audioFormat() == f);
+ QVERIFY(b.format() == f);
}
void tst_QAudioDecoder::source()
@@ -314,6 +336,12 @@ void tst_QAudioDecoder::nullControl()
d.setSourceDevice(&f);
QVERIFY(d.sourceDevice() == nullptr);
+ QAudioFormat format;
+ format.setChannelCount(2);
+ QVERIFY(!d.audioFormat().isValid());
+ d.setAudioFormat(format);
+ QVERIFY(!d.audioFormat().isValid());
+
QVERIFY(!d.read().isValid());
QVERIFY(!d.bufferAvailable());