Revision control

Copy as Markdown

// 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 "mfaudiodecodercontrol_p.h"
#include <QtMultimedia/private/qwindowsaudioutils_p.h>
#include <QtMultimedia/private/qwmf_support_p.h>
#include <QtCore/qdebug.h>
#include <QtCore/qglobal.h>
#include <chrono>
#include <mferror.h>
#include <system_error>
QT_BEGIN_NAMESPACE
MFAudioDecoderControl::MFAudioDecoderControl(QAudioDecoder *parent)
: QPlatformAudioDecoder(parent)
, m_sourceResolver(makeComObject<SourceResolver>())
{
connect(m_sourceResolver.Get(), &SourceResolver::mediaSourceReady, this, &MFAudioDecoderControl::handleMediaSourceReady);
connect(m_sourceResolver.Get(), &SourceResolver::error, this, &MFAudioDecoderControl::handleMediaSourceError);
}
MFAudioDecoderControl::~MFAudioDecoderControl()
{
m_sourceResolver->shutdown();
}
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(const ComPtr<IMFMediaSource> &source)
{
Q_ASSERT(source);
using namespace QWindowsAudioUtils;
using namespace std::chrono;
m_decoderSourceReader = std::make_unique<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 durationHns = 0;
pd->GetUINT64(MF_PD_DURATION, &durationHns);
durationChanged(round<milliseconds>(QWMF::reference_time{ static_cast<long long>(durationHns) }));
}
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 {
ComPtr<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);
finished();
}
void MFAudioDecoderControl::handleNewSample(ComPtr<IMFSample> sample)
{
Q_ASSERT(sample);
qint64 sampleStartTimeUs = m_resampler.outputFormat().durationForBytes(m_resampler.totalOutputBytes());
QByteArray out = m_resampler.resample(sample);
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();
}
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);
// buffer.startTime() is microseconds; convert to milliseconds
positionChanged(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::microseconds{ buffer.startTime() }));
bufferAvailableChanged(false);
m_decoderSourceReader->readNextSample();
}
return buffer;
}
QT_END_NAMESPACE
#include "moc_mfaudiodecodercontrol_p.cpp"