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 "qandroidaudiosink_p.h"
#include "qandroidaudioutil_p.h"
#include <QtMultimedia/private/qaudiohelpers_p.h>
QT_BEGIN_NAMESPACE
namespace QtAAudio {
Q_STATIC_LOGGING_CATEGORY(qLcAndroidAudioSink, "qt.multimedia.android.audiosink")
QAndroidAudioSinkStream::QAndroidAudioSinkStream(QAudioDevice device, const QAudioFormat &format,
std::optional<qsizetype> ringbufferSize,
QAndroidAudioSink *parent, float volume,
std::optional<NativePeriodFrames> nativePeriodFrames,
AudioEndpointRole role)
: QtMultimediaPrivate::QPlatformAudioSinkStream(std::move(device), format, ringbufferSize,
nativePeriodFrames, volume),
m_parent(parent),
m_role(role)
{
QtAAudio::StreamBuilder builder(format);
qCDebug(qLcAndroidAudioSink) << "Creating sink for device id:" << m_audioDevice.id()
<< ", description:" << m_audioDevice.description();
builder.deviceId = m_audioDevice.id().toInt();
// Set buffer parameters
builder.bufferCapacity = m_nativePeriodFrames
? qToUnderlying(*m_nativePeriodFrames)
: 1024;
// NOTE: AAudio doesn't support UINT8, so convert to INT16 if that's requested
if (format.sampleFormat() == QAudioFormat::UInt8) {
m_hostFormat = format;
m_hostFormat->setSampleFormat(QAudioFormat::Int16);
}
// Set builder parameters for audio sink
builder.params.sharingMode = AAUDIO_SHARING_MODE_SHARED;
builder.params.direction = AAUDIO_DIRECTION_OUTPUT;
switch (m_role) {
case QtMultimediaPrivate::AudioEndpointRole::SoundEffect:
builder.params.outputUsage = AAUDIO_USAGE_GAME;
builder.params.outputContentType = AAUDIO_CONTENT_TYPE_SONIFICATION;
break;
case QtMultimediaPrivate::AudioEndpointRole::Accessibility:
builder.params.outputUsage = AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY;
builder.params.outputContentType = AAUDIO_CONTENT_TYPE_SPEECH;
break;
case QtMultimediaPrivate::AudioEndpointRole::MediaPlayback:
case QtMultimediaPrivate::AudioEndpointRole::Other:
builder.params.outputUsage = AAUDIO_USAGE_MEDIA;
builder.params.outputContentType = AAUDIO_CONTENT_TYPE_MUSIC;
break;
}
if (QAndroidAudioUtil::supportsLowLatency() && !QAndroidAudioUtil::isBluetoothDevice(m_audioDevice))
builder.params.performanceMode = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
builder.userData = this;
builder.callback = [](AAudioStream *, void *userData, void *audioData,
int32_t numFrames) -> int {
auto *stream = reinterpret_cast<QAndroidAudioSinkStream *>(userData);
Q_ASSERT(stream);
QSpan<std::byte> audioSpan = stream->getHostSpan(audioData, numFrames);
return stream->m_audioCallback ? stream->processCallback(audioSpan)
: stream->processRingbuffer(audioSpan, numFrames);
};
builder.errorCallback = [](AAudioStream *, void *userData, aaudio_result_t error) -> void {
auto *stream = reinterpret_cast<QAndroidAudioSinkStream *>(userData);
Q_ASSERT(stream);
stream->handleError(error);
};
builder.setupBuilder();
m_stream = std::make_unique<QtAAudio::Stream>(builder);
if (builder.format.sampleFormat() != format.sampleFormat()) {
// Original sample format unsupported, so doing sample format conversion
Q_ASSERT(builder.format.sampleFormat() == QAudioFormat::Float);
m_hostFormat = builder.format;
}
}
bool QAndroidAudioSinkStream::open()
{
if (!m_stream->isOpen()) {
qCWarning(qLcAndroidAudioSink) << "Stream null";
requestStop();
return false;
}
if (!m_stream->areStreamParametersRespected())
qCWarning(qLcAndroidAudioSink) << "Stream parameters not correct";
return true;
}
bool QAndroidAudioSinkStream::start(QIODevice *device)
{
Q_ASSERT(thread()->isCurrentThread());
setQIODevice(device);
pullFromQIODevice();
createQIODeviceConnections(device);
// TODO: Fill host ringbuffer before starting
if (!m_stream->start()) {
requestStop();
return false;
}
return true;
}
QIODevice *QAndroidAudioSinkStream::start()
{
auto *writer = createRingbufferWriterDevice();
return start(writer) ? writer : nullptr;
}
bool QAndroidAudioSinkStream::start(AudioCallback cb)
{
Q_ASSERT(thread()->isCurrentThread());
m_audioCallback = std::move(cb);
if (!m_stream->start()) {
requestStop();
return false;
}
return true;
}
void QAndroidAudioSinkStream::suspend()
{
Q_ASSERT(thread()->isCurrentThread());
m_stream->stop();
}
void QAndroidAudioSinkStream::resume()
{
Q_ASSERT(thread()->isCurrentThread());
m_stream->start();
}
void QAndroidAudioSinkStream::stop(ShutdownPolicy policy)
{
requestStop();
disconnectQIODeviceConnections();
switch (policy) {
case ShutdownPolicy::DrainRingbuffer:
stop();
break;
case ShutdownPolicy::DiscardRingbuffer:
reset();
break;
default:
Q_UNREACHABLE_RETURN();
}
}
void QAndroidAudioSinkStream::stop()
{
if (isIdle() || m_audioCallback)
return reset();
stopIdleDetection();
connectIdleHandler([this] {
Q_ASSERT(thread()->isCurrentThread());
if (!isIdle()) // Only handle <not idle> -> <idle> transitions
return;
// We have written everything we want to write, synchronous stop on application thread
m_stream->stop();
m_self = nullptr; // might delete the instance
});
m_parent = nullptr;
// Take ownership of self to avoid deletion until AAudio stream is stopped
m_self = shared_from_this();
}
void QAndroidAudioSinkStream::reset()
{
Q_ASSERT(thread()->isCurrentThread());
m_stream->stop();
}
void QAndroidAudioSinkStream::updateStreamIdle(bool arg)
{
if (m_parent)
m_parent->updateStreamIdle(arg);
}
QSpan<std::byte>
QAndroidAudioSinkStream::getHostSpan(void *audioData,
int numFrames) const noexcept Q_DECL_NONBLOCKING_FUNCTION
{
qsizetype byteAmount = m_hostFormat ? m_hostFormat->bytesForFrames(numFrames)
: m_format.bytesForFrames(numFrames);
return QSpan{ reinterpret_cast<std::byte *>(audioData), byteAmount };
}
aaudio_data_callback_result_t
QAndroidAudioSinkStream::processRingbuffer(QSpan<std::byte> audioSpan,
int numFrames) noexcept Q_DECL_NONBLOCKING_FUNCTION
{
auto consumedFrames = m_hostFormat
? QPlatformAudioSinkStream::process(
audioSpan, numFrames,
QAudioHelperInternal::toNativeSampleFormat(m_hostFormat->sampleFormat()))
: QPlatformAudioSinkStream::process(audioSpan, numFrames);
if (consumedFrames != static_cast<uint64_t>(numFrames) && isStopRequested())
return AAUDIO_CALLBACK_RESULT_STOP;
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
aaudio_data_callback_result_t
QAndroidAudioSinkStream::processCallback(QSpan<std::byte> audioSpan) noexcept Q_DECL_NONBLOCKING_FUNCTION
{
if (isStopRequested())
return AAUDIO_CALLBACK_RESULT_STOP;
if (m_hostFormat)
QtMultimediaPrivate::runAudioCallback(*m_audioCallback, audioSpan, m_format, volume(),
*m_hostFormat);
else
QtMultimediaPrivate::runAudioCallback(*m_audioCallback, audioSpan, m_format, volume());
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
void QAndroidAudioSinkStream::handleError(aaudio_result_t)
{
// Handle as IO error which closes the stream
// TODO: Check for underruns
requestStop();
invokeOnAppThread([this] {
// clang-format off
handleIOError(m_parent);
// clang-format on
});
}
QAndroidAudioSink::QAndroidAudioSink(QAudioDevice device, const QAudioFormat &fmt, QObject *parent)
: BaseClass(std::move(device), fmt, parent)
{
}
QAndroidAudioSink::~QAndroidAudioSink()
= default;
} // namespace QtAAudio
QT_END_NAMESPACE