Revision control
Copy as Markdown
// Copyright (C) 2022 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
#ifndef QAUDIOSYSTEM_H
#define QAUDIOSYSTEM_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtMultimedia/private/qaudiohelpers_p.h>
#include <QtMultimedia/private/qtmm_rtsan_support_p.h>
#include <QtMultimedia/qaudio.h>
#include <QtMultimedia/qaudiodevice.h>
#include <QtMultimedia/qaudioformat.h>
#include <QtMultimedia/qtmultimediaglobal.h>
#include <QtCore/private/qglobal_p.h>
#include <QtCore/qelapsedtimer.h>
#include <QtCore/qspan.h>
#include <optional>
#include <array>
#include <variant>
#include <stdlib.h>
#if __has_include(<alloca.h>)
# include <alloca.h>
#endif
#if __has_include(<malloc.h>)
# include <malloc.h>
#endif
#if defined(Q_CC_MSVC) && !defined(alloca)
# define alloca _alloca
#endif
QT_BEGIN_NAMESPACE
class QIODevice;
class QAudioSink;
class QAudioSource;
namespace QtMultimediaPrivate {
using QtAudioPrivate::AudioSinkCallbackType;
using QtAudioPrivate::AudioSourceCallbackType;
#if __cpp_lib_move_only_function
template <typename SampleType>
using AudioSinkMoveOnlyCallbackType = std::move_only_function<void(QSpan<SampleType>)>;
template <typename SampleType>
using AudioSourceMoveOnlyCallbackType = std::move_only_function<void(QSpan<const SampleType>)>;
using AudioSinkMoveOnlyCallback =
std::variant<AudioSinkMoveOnlyCallbackType<float>, AudioSinkMoveOnlyCallbackType<uint8_t>,
AudioSinkMoveOnlyCallbackType<int16_t>,
AudioSinkMoveOnlyCallbackType<int32_t>>;
using AudioSourceMoveOnlyCallback = std::variant<
AudioSourceMoveOnlyCallbackType<float>, AudioSourceMoveOnlyCallbackType<uint8_t>,
AudioSourceMoveOnlyCallbackType<int16_t>, AudioSourceMoveOnlyCallbackType<int32_t>>;
template <typename V1, typename V2>
struct variant_cat;
template <typename... Ts1, typename... Ts2>
struct variant_cat<std::variant<Ts1...>, std::variant<Ts2...>>
{
using type = std::variant<Ts1..., Ts2...>;
};
template <typename V1, typename V2>
using variant_cat_t = typename variant_cat<V1, V2>::type;
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
enum class AudioEndpointRole : uint8_t
{
MediaPlayback,
SoundEffect,
Accessibility,
Other,
};
///////////////////////////////////////////////////////////////////////////////////////////////////
template <typename>
struct GetSampleTypeImpl;
template <>
struct GetSampleTypeImpl<float>
{
using type = float;
static constexpr QAudioFormat::SampleFormat sample_format = QAudioFormat::SampleFormat::Float;
};
template <>
struct GetSampleTypeImpl<int32_t>
{
using type = int32_t;
static constexpr QAudioFormat::SampleFormat sample_format = QAudioFormat::SampleFormat::Int32;
};
template <>
struct GetSampleTypeImpl<int16_t>
{
using type = int16_t;
static constexpr QAudioFormat::SampleFormat sample_format = QAudioFormat::SampleFormat::Int16;
};
template <>
struct GetSampleTypeImpl<uint8_t>
{
using type = uint8_t;
static constexpr QAudioFormat::SampleFormat sample_format = QAudioFormat::SampleFormat::UInt8;
};
template <typename T>
struct GetSampleTypeImpl<AudioSinkCallbackType<T>> : GetSampleTypeImpl<T>
{
};
template <typename T>
struct GetSampleTypeImpl<AudioSourceCallbackType<T>> : GetSampleTypeImpl<T>
{
};
#if __cpp_lib_move_only_function
template <typename T>
struct GetSampleTypeImpl<AudioSinkMoveOnlyCallbackType<T>> : GetSampleTypeImpl<T>
{
};
template <typename T>
struct GetSampleTypeImpl<AudioSourceMoveOnlyCallbackType<T>> : GetSampleTypeImpl<T>
{
};
#endif
template <typename SampleTypeOrCallbackType>
using GetSampleType = typename GetSampleTypeImpl<SampleTypeOrCallbackType>::type;
template <typename SampleTypeOrCallbackType>
static constexpr QAudioFormat::SampleFormat getSampleFormat()
{
return GetSampleTypeImpl<SampleTypeOrCallbackType>::sample_format;
}
#if __cpp_lib_move_only_function
using AudioSinkCallback =
variant_cat_t<QtAudioPrivate::AudioSinkCallback, AudioSinkMoveOnlyCallback>;
using AudioSourceCallback =
variant_cat_t<QtAudioPrivate::AudioSourceCallback, AudioSourceMoveOnlyCallback>;
#else
using AudioSinkCallback = QtAudioPrivate::AudioSinkCallback;
using AudioSourceCallback = QtAudioPrivate::AudioSourceCallback;
#endif
template <typename Callback>
inline AudioSinkCallback asAudioSinkCallback(Callback &&cb)
{
if constexpr (std::is_same_v<Callback, AudioSinkCallback>) {
return std::forward<Callback>(cb);
} else {
return std::visit([](auto &&arg) -> AudioSinkCallback {
return arg;
}, std::forward<Callback>(cb));
}
}
template <typename Callback>
inline AudioSourceCallback asAudioSourceCallback(Callback &&cb)
{
if constexpr (std::is_same_v<Callback, AudioSourceCallback>) {
return std::forward<Callback>(cb);
} else {
return std::visit([](auto &&arg) -> AudioSourceCallback {
return arg;
}, std::forward<Callback>(cb));
}
}
template <typename AnyAudioCallback>
constexpr bool validateAudioCallbackImpl(const AnyAudioCallback &audioCallback,
const QAudioFormat &format)
{
bool isNonNullFunction = std::visit([](const auto &cb) {
return bool(cb);
}, audioCallback);
if (!isNonNullFunction)
return false;
bool hasCorrectFormat = std::visit([&](const auto &cb) {
return getSampleFormat<std::decay_t<decltype(cb)>>() == format.sampleFormat();
}, audioCallback);
return hasCorrectFormat;
}
constexpr bool validateAudioCallback(const AudioSinkCallback &audioCallback,
const QAudioFormat &format)
{
return validateAudioCallbackImpl(audioCallback, format);
}
constexpr bool validateAudioCallback(const AudioSourceCallback &audioCallback,
const QAudioFormat &format)
{
return validateAudioCallbackImpl(audioCallback, format);
}
// if the requested buffer is reasonably small (64kb, big enougth for 16 channels, 1024 frames,
// float32) we can use a stack-allocated temporary buffer.
// otherwise we allocate a heap buffer.
template <size_t limit = 1024 * 64, typename Functor>
inline auto withTemporaryBuffer(size_t bufferSize, Functor &&f) noexcept Q_DECL_NONBLOCKING_FUNCTION
{
if (bufferSize <= limit) Q_LIKELY_BRANCH {
#ifdef alloca
std::byte *stackBuffer = reinterpret_cast<std::byte *>(alloca(bufferSize));
auto stackBufferSpan = QSpan<std::byte>{
stackBuffer,
qsizetype(bufferSize),
};
#else
std::array<std::byte, limit> stackArray;
auto stackBufferSpan = QSpan<std::byte>{
stackArray,
qsizetype(bufferSize),
};
#endif
return f(stackBufferSpan);
} else {
QtPrivate::ScopedRTSanDisabler allowAllocations;
auto heapBuffer = q20::make_unique_for_overwrite<std::byte[]>(bufferSize);
auto heapBufferSpan = QSpan<std::byte>{
heapBuffer.get(),
qsizetype(bufferSize),
};
return f(heapBufferSpan);
}
}
namespace impl {
template <bool IsSink>
inline void
runAudioCallback(std::conditional_t<IsSink, AudioSinkCallback, AudioSourceCallback> &audioCallback,
QSpan<std::conditional_t<IsSink, std::byte, const std::byte>> hostBuffer,
const QAudioFormat &format)
{
Q_ASSERT(!hostBuffer.empty());
bool callbackIsValid = validateAudioCallback(audioCallback, format);
Q_PRESUME(callbackIsValid);
int numberOfSamples =
format.framesForBytes(qint32(hostBuffer.size())) * format.channelCount();
std::visit([&](auto &callback) {
using FunctorType = std::decay_t<decltype(callback)>;
Q_ASSERT(getSampleFormat<FunctorType>() == format.sampleFormat());
using SampleType = GetSampleType<FunctorType>;
bool audioCallbackIsValid = bool(callback);
Q_PRESUME(audioCallbackIsValid);
using HostBufferType = std::conditional_t<IsSink, SampleType, const SampleType>;
auto buffer = QSpan<HostBufferType>{
reinterpret_cast<HostBufferType *>(hostBuffer.data()),
numberOfSamples,
};
callback(buffer);
}, audioCallback);
}
} // namespace impl
inline void runAudioCallback(AudioSinkCallback &audioCallback, QSpan<std::byte> hostBuffer,
const QAudioFormat &format, float volume)
{
impl::runAudioCallback<true>(audioCallback, hostBuffer, format);
QAudioHelperInternal::applyVolume(volume, format, hostBuffer, hostBuffer);
}
inline void runAudioCallback(AudioSinkCallback &audioCallback, QSpan<std::byte> hostBuffer,
const QAudioFormat &applicationFormat, float volume,
const QAudioFormat &hostFormat)
{
using namespace QAudioHelperInternal;
const int32_t numberOfFrames =
hostFormat.framesForBytes(qint32(hostBuffer.size()));
const int32_t applicationBufferSize = applicationFormat.bytesForFrames(numberOfFrames);
withTemporaryBuffer(applicationBufferSize, [&](QSpan<std::byte> tempBuffer) {
runAudioCallback(audioCallback, tempBuffer, applicationFormat, volume);
convertSampleFormat(tempBuffer, toNativeSampleFormat(applicationFormat.sampleFormat()),
hostBuffer, toNativeSampleFormat(hostFormat.sampleFormat()));
});
}
inline void runAudioCallback(AudioSinkCallback &audioCallback, QSpan<std::byte> hostBuffer,
const QAudioFormat &applicationFormat, float volume,
QAudioHelperInternal::NativeSampleFormat nativeHostFormat)
{
using namespace QAudioHelperInternal;
const int32_t numberOfFrames = int32_t(
hostBuffer.size() / (bytesPerSample(nativeHostFormat) * applicationFormat.channelCount()));
const int32_t applicationBufferSize = applicationFormat.bytesForFrames(numberOfFrames);
withTemporaryBuffer(applicationBufferSize, [&](QSpan<std::byte> tempBuffer) {
runAudioCallback(audioCallback, tempBuffer, applicationFormat, volume);
convertSampleFormat(tempBuffer, toNativeSampleFormat(applicationFormat.sampleFormat()),
hostBuffer, nativeHostFormat);
});
}
// NB: we we provide two overloads for running audio callbacks based on the host buffer:
// * if the host buffer is immutable, we need to apply the volume on a temporary buffer
// * if the host buffer is mutable, we can apply the volume in-place (currently unused)
inline void runAudioCallback(AudioSourceCallback &audioCallback, QSpan<const std::byte> hostBuffer,
const QAudioFormat &format, float volume)
{
if (volume == 1.0f) {
impl::runAudioCallback<false>(audioCallback, hostBuffer, format);
} else {
withTemporaryBuffer(hostBuffer.size(), [&](QSpan<std::byte> tempBuffer) {
QAudioHelperInternal::applyVolume(volume, format, hostBuffer, tempBuffer);
impl::runAudioCallback<false>(audioCallback, tempBuffer, format);
});
}
}
inline void runAudioCallback(AudioSourceCallback &audioCallback, QSpan<std::byte> hostBuffer,
const QAudioFormat &format, float volume)
{
QAudioHelperInternal::applyVolume(volume, format, hostBuffer, hostBuffer);
impl::runAudioCallback<false>(audioCallback, hostBuffer, format);
}
template <typename HostBufferType>
inline void runAudioCallback(AudioSourceCallback &audioCallback, QSpan<HostBufferType> hostBuffer,
const QAudioFormat &applicationFormat, float volume,
const QAudioFormat &hostFormat)
{
static_assert(std::is_same_v<HostBufferType, std::byte>
|| std::is_same_v<HostBufferType, const std::byte>);
using namespace QAudioHelperInternal;
const int32_t numberOfFrames =
hostFormat.framesForBytes(qint32(hostBuffer.size()));
const int32_t applicationBufferSize = applicationFormat.bytesForFrames(numberOfFrames);
withTemporaryBuffer(applicationBufferSize, [&](QSpan<std::byte> tempBuffer) {
convertSampleFormat(hostBuffer, toNativeSampleFormat(hostFormat.sampleFormat()), tempBuffer,
toNativeSampleFormat(applicationFormat.sampleFormat()));
runAudioCallback(audioCallback, tempBuffer, applicationFormat, volume);
});
}
template <typename HostBufferType>
inline void runAudioCallback(AudioSourceCallback &audioCallback, QSpan<HostBufferType> hostBuffer,
const QAudioFormat &applicationFormat, float volume,
QAudioHelperInternal::NativeSampleFormat nativeHostFormat)
{
static_assert(std::is_same_v<HostBufferType, std::byte>
|| std::is_same_v<HostBufferType, const std::byte>);
using namespace QAudioHelperInternal;
const int32_t numberOfFrames = int32_t(
hostBuffer.size() / (bytesPerSample(nativeHostFormat) * applicationFormat.channelCount()));
const int32_t applicationBufferSize = applicationFormat.bytesForFrames(numberOfFrames);
withTemporaryBuffer(applicationBufferSize, [&](QSpan<std::byte> tempBuffer) {
convertSampleFormat(hostBuffer, nativeHostFormat, tempBuffer,
toNativeSampleFormat(applicationFormat.sampleFormat()));
runAudioCallback(audioCallback, tempBuffer, applicationFormat, volume);
});
}
///////////////////////////////////////////////////////////////////////////////////////////////////
enum class NativePeriodFrames : uint32_t {};
} // namespace QtMultimediaPrivate
class Q_MULTIMEDIA_EXPORT QPlatformAudioEndpointBase : public QObject
{
Q_OBJECT
public:
using NativePeriodFrames = QtMultimediaPrivate::NativePeriodFrames;
explicit QPlatformAudioEndpointBase(QAudioDevice, const QAudioFormat &, QObject *parent);
~QPlatformAudioEndpointBase() override;
// LATER: can we devirtualize these functions
QAudio::Error error() const { return m_error; }
virtual QAudio::State state() const { return m_inferredState; }
virtual void setError(QAudio::Error);
virtual bool isFormatSupported(const QAudioFormat &format) const;
QAudioFormat format() const { return m_format; }
virtual void setVolume(float volume) { m_volume = volume; }
float volume() const { return m_volume; }
Q_SIGNALS:
void stateChanged(QtAudio::State);
protected:
enum class EmitStateSignal : uint8_t
{
True,
False,
};
void updateStreamState(QAudio::State);
void updateStreamIdle(bool idle, EmitStateSignal = EmitStateSignal::True);
const QAudioDevice m_audioDevice;
const QAudioFormat m_format;
private:
void inferState();
QAudio::State m_streamState = QAudio::StoppedState;
QAudio::State m_inferredState = QAudio::StoppedState;
QAudio::Error m_error{};
bool m_streamIsIdle = false;
float m_volume{ 1.f };
};
class Q_MULTIMEDIA_EXPORT QPlatformAudioSink : public QPlatformAudioEndpointBase
{
Q_OBJECT
public:
explicit QPlatformAudioSink(QAudioDevice, const QAudioFormat &, QObject *parent);
~QPlatformAudioSink() override;
virtual void start(QIODevice *device) = 0;
virtual QIODevice* start() = 0;
virtual void stop() = 0;
virtual void reset() = 0;
virtual void suspend() = 0;
virtual void resume() = 0;
virtual qsizetype bytesFree() const = 0;
virtual void setBufferSize(qsizetype value) = 0;
virtual qsizetype bufferSize() const = 0;
virtual void setNativePeriodFrames(std::optional<NativePeriodFrames>) { }
virtual std::optional<NativePeriodFrames> nativePeriodFrames() { return std::nullopt; }
virtual qint64 processedUSecs() const = 0;
using AudioCallback = QtMultimediaPrivate::AudioSinkCallback;
virtual void start(AudioCallback &&) { }
virtual bool hasCallbackAPI() { return false; }
QElapsedTimer elapsedTime;
static QPlatformAudioSink *get(const QAudioSink &);
using AudioEndpointRole = QtMultimediaPrivate::AudioEndpointRole;
virtual void setRole(AudioEndpointRole) { }
};
class Q_MULTIMEDIA_EXPORT QPlatformAudioSource : public QPlatformAudioEndpointBase
{
Q_OBJECT
public:
explicit QPlatformAudioSource(QAudioDevice, const QAudioFormat &, QObject *parent);
~QPlatformAudioSource() override;
virtual void start(QIODevice *device) = 0;
virtual QIODevice* start() = 0;
virtual void stop() = 0;
virtual void reset() = 0;
virtual void suspend() = 0;
virtual void resume() = 0;
virtual qsizetype bytesReady() const = 0;
virtual void setBufferSize(qsizetype value) = 0;
virtual void setNativePeriodFrames(std::optional<NativePeriodFrames>) { }
virtual std::optional<NativePeriodFrames> nativePeriodFrames() { return std::nullopt; }
virtual qsizetype bufferSize() const = 0;
virtual qint64 processedUSecs() const = 0;
using AudioCallback = QtMultimediaPrivate::AudioSourceCallback;
virtual void start(AudioCallback &&) { }
virtual bool hasCallbackAPI() { return false; }
QElapsedTimer elapsedTime;
static QPlatformAudioSource *get(const QAudioSource &);
};
// forward declarations
namespace QtMultimediaPrivate {
class QPlatformAudioSinkStream;
class QPlatformAudioSourceStream;
} // namespace QtMultimediaPrivate
QT_END_NAMESPACE
#endif // QAUDIOSYSTEM_H