Revision control
Copy as Markdown
// Copyright (C) 2020 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 "qffmpegaudiodecoder_p.h"
#include "qffmpegresampler_p.h"
#include "qaudiobuffer.h"
#include "qffmpegplaybackengine_p.h"
#include "playbackengine/qffmpegrenderer_p.h"
#include <QtMultimedia/qplaybackoptions.h>
#include <qloggingcategory.h>
Q_STATIC_LOGGING_CATEGORY(qLcAudioDecoder, "qt.multimedia.ffmpeg.audioDecoder")
QT_BEGIN_NAMESPACE
namespace QFFmpeg
{
using TrackType = QPlatformMediaPlayer::TrackType;
class SteppingAudioRenderer : public Renderer
{
Q_OBJECT
public:
SteppingAudioRenderer(const PlaybackEngineObjectID &id, const QAudioFormat &format)
: Renderer(id, {}), m_format(format)
{
}
RenderingResult renderInternal(Frame frame) override
{
if (!frame.isValid())
return {};
if (!m_resampler)
m_resampler = QFFmpegResampler::createFromCodecContext(frame.codecContext(), m_format);
if (m_resampler)
Q_EMIT newAudioBuffer(m_resampler->resample(frame.avFrame()));
return {};
}
Q_SIGNALS:
void newAudioBuffer(QAudioBuffer);
private:
QAudioFormat m_format;
std::unique_ptr<QFFmpegResampler> m_resampler;
};
class AudioDecoder : public PlaybackEngine
{
Q_OBJECT
public:
explicit AudioDecoder(const QAudioFormat &format, const QPlaybackOptions &options)
: PlaybackEngine{ options }, m_format(format)
{
}
RendererPtr createRenderer(TrackType trackType) override
{
if (trackType != TrackType::AudioStream)
return RendererPtr{ {}, {} };
auto result = createPlaybackEngineObject<SteppingAudioRenderer>(m_format);
m_audioRenderer = result.get();
connect(result.get(), &SteppingAudioRenderer::newAudioBuffer, this,
&AudioDecoder::newAudioBuffer);
return result;
}
void nextBuffer()
{
Q_ASSERT(m_audioRenderer);
Q_ASSERT(!m_audioRenderer->isStepForced());
m_audioRenderer->doForceStep();
// updateObjectsPausedState();
}
Q_SIGNALS:
void newAudioBuffer(QAudioBuffer);
private:
QPointer<Renderer> m_audioRenderer;
QAudioFormat m_format;
};
} // namespace QFFmpeg
QFFmpegAudioDecoder::QFFmpegAudioDecoder(QAudioDecoder *parent)
: QPlatformAudioDecoder(parent)
{
}
QFFmpegAudioDecoder::~QFFmpegAudioDecoder() = default;
QUrl QFFmpegAudioDecoder::source() const
{
return m_url;
}
void QFFmpegAudioDecoder::setSource(const QUrl &fileName)
{
stop();
m_sourceDevice = nullptr;
if (std::exchange(m_url, fileName) != fileName)
sourceChanged();
}
QIODevice *QFFmpegAudioDecoder::sourceDevice() const
{
return m_sourceDevice;
}
void QFFmpegAudioDecoder::setSourceDevice(QIODevice *device)
{
stop();
m_url.clear();
if (std::exchange(m_sourceDevice, device) != device)
sourceChanged();
}
void QFFmpegAudioDecoder::start()
{
qCDebug(qLcAudioDecoder) << "start";
auto checkNoError = [this]() {
if (error() == QAudioDecoder::NoError)
return true;
durationChanged(kInvalidDuration);
positionChanged(kInvalidPosition);
m_decoder.reset();
return false;
};
QPlaybackOptions defaultOptions;
m_decoder = std::make_unique<AudioDecoder>(m_audioFormat, defaultOptions);
connect(m_decoder.get(), &AudioDecoder::errorOccured, this, &QFFmpegAudioDecoder::errorSignal);
connect(m_decoder.get(), &AudioDecoder::endOfStream, this, &QFFmpegAudioDecoder::done);
connect(m_decoder.get(), &AudioDecoder::newAudioBuffer, this,
&QFFmpegAudioDecoder::newAudioBuffer);
QFFmpeg::MediaDataHolder::Maybe media =
QFFmpeg::MediaDataHolder::create(m_url, m_sourceDevice, defaultOptions, nullptr);
if (media) {
Q_ASSERT(media.value());
if (media.value()->streamInfo(QFFmpeg::TrackType::AudioStream).isEmpty())
error(QAudioDecoder::FormatError,
QLatin1String("The media doesn't contain an audio stream"));
else
m_decoder->setMedia(std::move(*media.value()));
} else {
auto [code, description] = media.error();
errorSignal(code, description);
}
if (!checkNoError())
return;
m_decoder->setState(QMediaPlayer::PausedState);
if (!checkNoError())
return;
m_decoder->nextBuffer();
if (!checkNoError())
return;
durationChanged(std::chrono::milliseconds{ QFFmpeg::toUserDuration(m_decoder->duration()).get() });
setIsDecoding(true);
}
void QFFmpegAudioDecoder::stop()
{
qCDebug(qLcAudioDecoder) << ">>>>> stop";
if (m_decoder) {
m_decoder.reset();
done();
}
}
QAudioFormat QFFmpegAudioDecoder::audioFormat() const
{
return m_audioFormat;
}
void QFFmpegAudioDecoder::setAudioFormat(const QAudioFormat &format)
{
if (std::exchange(m_audioFormat, format) != format)
formatChanged(m_audioFormat);
}
QAudioBuffer QFFmpegAudioDecoder::read()
{
auto buffer = std::exchange(m_audioBuffer, QAudioBuffer{});
if (!buffer.isValid())
return buffer;
qCDebug(qLcAudioDecoder) << "reading buffer" << buffer.startTime();
bufferAvailableChanged(false);
if (m_decoder)
m_decoder->nextBuffer();
return buffer;
}
void QFFmpegAudioDecoder::newAudioBuffer(const QAudioBuffer &b)
{
Q_ASSERT(b.isValid());
Q_ASSERT(!m_audioBuffer.isValid());
Q_ASSERT(!bufferAvailable());
qCDebug(qLcAudioDecoder) << "new audio buffer" << b.startTime();
m_audioBuffer = b;
// QAudioBuffer::startTime is in microseconds; convert to milliseconds
const qint64 pos = b.startTime();
positionChanged(
std::chrono::round<std::chrono::milliseconds>(std::chrono::microseconds{ pos }));
bufferAvailableChanged(b.isValid());
bufferReady();
}
void QFFmpegAudioDecoder::done()
{
qCDebug(qLcAudioDecoder) << ">>>>> DONE!";
finished();
}
void QFFmpegAudioDecoder::errorSignal(int err, const QString &errorString)
{
// unfortunately the error enums for QAudioDecoder and QMediaPlayer aren't identical.
// Map them.
switch (QMediaPlayer::Error(err)) {
case QMediaPlayer::NoError:
error(QAudioDecoder::NoError, errorString);
break;
case QMediaPlayer::ResourceError:
error(QAudioDecoder::ResourceError, errorString);
break;
case QMediaPlayer::FormatError:
error(QAudioDecoder::FormatError, errorString);
break;
case QMediaPlayer::NetworkError:
// fall through, Network error doesn't exist in QAudioDecoder
case QMediaPlayer::AccessDeniedError:
error(QAudioDecoder::AccessDeniedError, errorString);
break;
}
}
QT_END_NAMESPACE
#include "moc_qffmpegaudiodecoder_p.cpp"
#include "qffmpegaudiodecoder.moc"