| 1 | // Copyright (C) 2022 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | #ifndef QAUDIOSYSTEM_H |
| 5 | #define QAUDIOSYSTEM_H |
| 6 | |
| 7 | // |
| 8 | // W A R N I N G |
| 9 | // ------------- |
| 10 | // |
| 11 | // This file is not part of the Qt API. It exists purely as an |
| 12 | // implementation detail. This header file may change from version to |
| 13 | // version without notice, or even be removed. |
| 14 | // |
| 15 | // We mean it. |
| 16 | // |
| 17 | |
| 18 | #include <QtMultimedia/qtmultimediaglobal.h> |
| 19 | |
| 20 | #include <QtMultimedia/qaudio.h> |
| 21 | #include <QtMultimedia/qaudiodevice.h> |
| 22 | #include <QtMultimedia/qaudioformat.h> |
| 23 | #include <QtMultimedia/private/qaudiohelpers_p.h> |
| 24 | #include <QtMultimedia/private/qaudio_rtsan_support_p.h> |
| 25 | #include <QtMultimedia/private/qmultimedia_assume_p.h> |
| 26 | |
| 27 | #include <QtCore/qelapsedtimer.h> |
| 28 | #include <QtCore/qspan.h> |
| 29 | #include <QtCore/private/qglobal_p.h> |
| 30 | |
| 31 | #include <array> |
| 32 | #include <functional> |
| 33 | #include <variant> |
| 34 | |
| 35 | QT_BEGIN_NAMESPACE |
| 36 | |
| 37 | class QIODevice; |
| 38 | class QAudioSink; |
| 39 | class QAudioSource; |
| 40 | |
| 41 | namespace QtMultimediaPrivate { |
| 42 | |
| 43 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
| 44 | |
| 45 | enum class AudioEndpointRole : uint8_t |
| 46 | { |
| 47 | MediaPlayback, |
| 48 | SoundEffect, |
| 49 | Accessibility, |
| 50 | Other, |
| 51 | }; |
| 52 | |
| 53 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
| 54 | |
| 55 | template <typename SampleType> |
| 56 | using AudioSinkCallbackType = std::function<void(QSpan<SampleType>)>; |
| 57 | |
| 58 | template <typename SampleType> |
| 59 | using AudioSourceCallbackType = std::function<void(QSpan<const SampleType>)>; |
| 60 | |
| 61 | #if __cpp_lib_move_only_function |
| 62 | template <typename SampleType> |
| 63 | using AudioSinkMoveOnlyCallbackType = std::move_only_function<void(QSpan<SampleType>)>; |
| 64 | |
| 65 | template <typename SampleType> |
| 66 | using AudioSourceMoveOnlyCallbackType = std::move_only_function<void(QSpan<const SampleType>)>; |
| 67 | #endif |
| 68 | |
| 69 | template <typename> |
| 70 | struct GetSampleTypeImpl; |
| 71 | |
| 72 | template <> |
| 73 | struct GetSampleTypeImpl<float> |
| 74 | { |
| 75 | using type = float; |
| 76 | static constexpr QAudioFormat::SampleFormat sample_format = QAudioFormat::SampleFormat::Float; |
| 77 | }; |
| 78 | |
| 79 | template <> |
| 80 | struct GetSampleTypeImpl<int32_t> |
| 81 | { |
| 82 | using type = int32_t; |
| 83 | static constexpr QAudioFormat::SampleFormat sample_format = QAudioFormat::SampleFormat::Int32; |
| 84 | }; |
| 85 | template <> |
| 86 | struct GetSampleTypeImpl<int16_t> |
| 87 | { |
| 88 | using type = int16_t; |
| 89 | static constexpr QAudioFormat::SampleFormat sample_format = QAudioFormat::SampleFormat::Int16; |
| 90 | }; |
| 91 | |
| 92 | template <> |
| 93 | struct GetSampleTypeImpl<uint8_t> |
| 94 | { |
| 95 | using type = uint8_t; |
| 96 | static constexpr QAudioFormat::SampleFormat sample_format = QAudioFormat::SampleFormat::UInt8; |
| 97 | }; |
| 98 | |
| 99 | template <typename T> |
| 100 | struct GetSampleTypeImpl<AudioSinkCallbackType<T>> : GetSampleTypeImpl<T> |
| 101 | { |
| 102 | }; |
| 103 | |
| 104 | template <typename T> |
| 105 | struct GetSampleTypeImpl<AudioSourceCallbackType<T>> : GetSampleTypeImpl<T> |
| 106 | { |
| 107 | }; |
| 108 | |
| 109 | #if __cpp_lib_move_only_function |
| 110 | |
| 111 | template <typename T> |
| 112 | struct GetSampleTypeImpl<AudioSinkMoveOnlyCallbackType<T>> : GetSampleTypeImpl<T> |
| 113 | { |
| 114 | }; |
| 115 | |
| 116 | template <typename T> |
| 117 | struct GetSampleTypeImpl<AudioSourceMoveOnlyCallbackType<T>> : GetSampleTypeImpl<T> |
| 118 | { |
| 119 | }; |
| 120 | |
| 121 | #endif |
| 122 | |
| 123 | template <typename SampleTypeOrCallbackType> |
| 124 | using GetSampleType = typename GetSampleTypeImpl<SampleTypeOrCallbackType>::type; |
| 125 | |
| 126 | template <typename SampleTypeOrCallbackType> |
| 127 | static constexpr QAudioFormat::SampleFormat getSampleFormat() |
| 128 | { |
| 129 | return GetSampleTypeImpl<SampleTypeOrCallbackType>::sample_format; |
| 130 | } |
| 131 | |
| 132 | #if __cpp_lib_move_only_function |
| 133 | using AudioSinkCallback = |
| 134 | std::variant<AudioSinkMoveOnlyCallbackType<float>, AudioSinkMoveOnlyCallbackType<uint8_t>, |
| 135 | AudioSinkMoveOnlyCallbackType<int16_t>, |
| 136 | AudioSinkMoveOnlyCallbackType<int32_t>>; |
| 137 | using AudioSourceCallback = std::variant< |
| 138 | AudioSourceMoveOnlyCallbackType<float>, AudioSourceMoveOnlyCallbackType<uint8_t>, |
| 139 | AudioSourceMoveOnlyCallbackType<int16_t>, AudioSourceMoveOnlyCallbackType<int32_t>>; |
| 140 | #else |
| 141 | using AudioSinkCallback = |
| 142 | std::variant<AudioSinkCallbackType<float>, AudioSinkCallbackType<uint8_t>, |
| 143 | AudioSinkCallbackType<int16_t>, AudioSinkCallbackType<int32_t>>; |
| 144 | using AudioSourceCallback = |
| 145 | std::variant<AudioSourceCallbackType<float>, AudioSourceCallbackType<uint8_t>, |
| 146 | AudioSourceCallbackType<int16_t>, AudioSourceCallbackType<int32_t>>; |
| 147 | |
| 148 | #endif |
| 149 | |
| 150 | template <typename AnyAudioCallback> |
| 151 | constexpr bool validateAudioCallbackImpl(const AnyAudioCallback &audioCallback, |
| 152 | const QAudioFormat &format) |
| 153 | { |
| 154 | bool isNonNullFunction = std::visit([](const auto &cb) { |
| 155 | return bool(cb); |
| 156 | }, audioCallback); |
| 157 | |
| 158 | if (!isNonNullFunction) |
| 159 | return false; |
| 160 | |
| 161 | bool hasCorrectFormat = std::visit([&](const auto &cb) { |
| 162 | return getSampleFormat<std::decay_t<decltype(cb)>>() == format.sampleFormat(); |
| 163 | }, audioCallback); |
| 164 | |
| 165 | return hasCorrectFormat; |
| 166 | } |
| 167 | |
| 168 | constexpr bool validateAudioCallback(const AudioSinkCallback &audioCallback, |
| 169 | const QAudioFormat &format) |
| 170 | { |
| 171 | return validateAudioCallbackImpl(audioCallback, format); |
| 172 | } |
| 173 | |
| 174 | constexpr bool validateAudioCallback(const AudioSourceCallback &audioCallback, |
| 175 | const QAudioFormat &format) |
| 176 | { |
| 177 | return validateAudioCallbackImpl(audioCallback, format); |
| 178 | } |
| 179 | |
| 180 | template <bool IsSink> |
| 181 | inline void |
| 182 | runAudioCallback(std::conditional_t<IsSink, AudioSinkCallback, AudioSourceCallback> &audioCallback, |
| 183 | QSpan<std::conditional_t<IsSink, std::byte, const std::byte>> hostBuffer, |
| 184 | const QAudioFormat &format) |
| 185 | { |
| 186 | Q_ASSERT(!hostBuffer.empty()); |
| 187 | |
| 188 | bool callbackIsValid = validateAudioCallback(audioCallback, format); |
| 189 | QT_MM_ASSUME(callbackIsValid); |
| 190 | |
| 191 | int numberOfSamples = format.framesForBytes(byteCount: hostBuffer.size()) * format.channelCount(); |
| 192 | |
| 193 | std::visit([&](auto &callback) { |
| 194 | using FunctorType = std::decay_t<decltype(callback)>; |
| 195 | Q_ASSERT(getSampleFormat<FunctorType>() == format.sampleFormat()); |
| 196 | |
| 197 | using SampleType = GetSampleType<FunctorType>; |
| 198 | |
| 199 | bool audioCallbackIsValid = bool(callback); |
| 200 | QT_MM_ASSUME(audioCallbackIsValid); |
| 201 | using HostBufferType = std::conditional_t<IsSink, SampleType, const SampleType>; |
| 202 | |
| 203 | auto buffer = QSpan<HostBufferType>{ |
| 204 | reinterpret_cast<HostBufferType *>(hostBuffer.data()), |
| 205 | numberOfSamples, |
| 206 | }; |
| 207 | callback(buffer); |
| 208 | }, audioCallback); |
| 209 | } |
| 210 | |
| 211 | inline void runAudioCallback(AudioSinkCallback &audioCallback, QSpan<std::byte> hostBuffer, |
| 212 | const QAudioFormat &format, float volume) |
| 213 | { |
| 214 | runAudioCallback<true>(audioCallback, hostBuffer, format); |
| 215 | QAudioHelperInternal::applyVolume(volume, format, source: hostBuffer, destination: hostBuffer); |
| 216 | } |
| 217 | |
| 218 | // NB: we we provide two overloads for running audio callbacks based on the host buffer: |
| 219 | // * if the host buffer is immutable, we need to apply the volume on a temporary buffer |
| 220 | // * if the host buffer is mutable, we can apply the volume in-place (currently unused) |
| 221 | inline void runAudioCallback(AudioSourceCallback &audioCallback, QSpan<const std::byte> hostBuffer, |
| 222 | const QAudioFormat &format, float volume) |
| 223 | { |
| 224 | if (volume == 1.0f) { |
| 225 | runAudioCallback<false>(audioCallback, hostBuffer, format); |
| 226 | } else { |
| 227 | // if the host buffer is reasonably small (64kb, big enougth for 16 channels, 1024 frames, |
| 228 | // float32) we can use a stack-allocated temporary buffer. |
| 229 | // otherwise we allocate a heap buffer. |
| 230 | |
| 231 | constexpr qsizetype sizeEstimate = 1024 * 16 * sizeof(float); |
| 232 | if (hostBuffer.size() <= sizeEstimate) { |
| 233 | std::array<std::byte, sizeEstimate> stackBuffer; |
| 234 | QSpan<std::byte> stackBufferSpan{ |
| 235 | stackBuffer.data(), |
| 236 | hostBuffer.size(), |
| 237 | }; |
| 238 | |
| 239 | QAudioHelperInternal::applyVolume(volume, format, source: hostBuffer, destination: stackBufferSpan); |
| 240 | runAudioCallback<false>(audioCallback, hostBuffer: stackBufferSpan, format); |
| 241 | } else { |
| 242 | QtPrivate::ScopedRTSanDisabler allowAllocations; |
| 243 | |
| 244 | auto buffer = q20::make_unique_for_overwrite<std::byte[]>(n: hostBuffer.size()); |
| 245 | auto heapBufferSpan = QSpan{ |
| 246 | buffer.get(), |
| 247 | hostBuffer.size(), |
| 248 | }; |
| 249 | QAudioHelperInternal::applyVolume(volume, format, source: hostBuffer, destination: heapBufferSpan); |
| 250 | runAudioCallback<false>(audioCallback, hostBuffer: heapBufferSpan, format); |
| 251 | } |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | inline void runAudioCallback(AudioSourceCallback &audioCallback, QSpan<std::byte> hostBuffer, |
| 256 | const QAudioFormat &format, float volume) |
| 257 | { |
| 258 | QAudioHelperInternal::applyVolume(volume, format, source: hostBuffer, destination: hostBuffer); |
| 259 | runAudioCallback<false>(audioCallback, hostBuffer, format); |
| 260 | } |
| 261 | |
| 262 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
| 263 | |
| 264 | } // namespace QtMultimediaPrivate |
| 265 | |
| 266 | class Q_MULTIMEDIA_EXPORT QPlatformAudioEndpointBase : public QObject |
| 267 | { |
| 268 | Q_OBJECT |
| 269 | |
| 270 | public: |
| 271 | explicit QPlatformAudioEndpointBase(QAudioDevice, const QAudioFormat &, QObject *parent); |
| 272 | |
| 273 | // LATER: can we devirtualize these functions |
| 274 | QAudio::Error error() const { return m_error; } |
| 275 | virtual QAudio::State state() const { return m_inferredState; } |
| 276 | virtual void setError(QAudio::Error); |
| 277 | |
| 278 | virtual bool isFormatSupported(const QAudioFormat &format) const; |
| 279 | QAudioFormat format() const { return m_format; } |
| 280 | |
| 281 | virtual void setVolume(float volume) { m_volume = volume; } |
| 282 | float volume() const { return m_volume; } |
| 283 | |
| 284 | Q_SIGNALS: |
| 285 | void stateChanged(QtAudio::State); |
| 286 | |
| 287 | protected: |
| 288 | enum class EmitStateSignal : uint8_t |
| 289 | { |
| 290 | True, |
| 291 | False, |
| 292 | }; |
| 293 | |
| 294 | void updateStreamState(QAudio::State); |
| 295 | void updateStreamIdle(bool idle, EmitStateSignal = EmitStateSignal::True); |
| 296 | |
| 297 | const QAudioDevice m_audioDevice; |
| 298 | const QAudioFormat m_format; |
| 299 | |
| 300 | private: |
| 301 | void inferState(); |
| 302 | |
| 303 | QAudio::State m_streamState = QAudio::StoppedState; |
| 304 | QAudio::State m_inferredState = QAudio::StoppedState; |
| 305 | QAudio::Error m_error{}; |
| 306 | bool m_streamIsIdle = false; |
| 307 | |
| 308 | float m_volume{ 1.f }; |
| 309 | }; |
| 310 | |
| 311 | class Q_MULTIMEDIA_EXPORT QPlatformAudioSink : public QPlatformAudioEndpointBase |
| 312 | { |
| 313 | public: |
| 314 | explicit QPlatformAudioSink(QAudioDevice, const QAudioFormat &, QObject *parent); |
| 315 | virtual void start(QIODevice *device) = 0; |
| 316 | virtual QIODevice* start() = 0; |
| 317 | virtual void stop() = 0; |
| 318 | virtual void reset() = 0; |
| 319 | virtual void suspend() = 0; |
| 320 | virtual void resume() = 0; |
| 321 | virtual qsizetype bytesFree() const = 0; |
| 322 | virtual void setBufferSize(qsizetype value) = 0; |
| 323 | virtual qsizetype bufferSize() const = 0; |
| 324 | virtual void setHardwareBufferFrames(int32_t) { } |
| 325 | virtual int32_t hardwareBufferFrames() { return -1; } |
| 326 | virtual qint64 processedUSecs() const = 0; |
| 327 | |
| 328 | using AudioCallback = QtMultimediaPrivate::AudioSinkCallback; |
| 329 | |
| 330 | virtual void start(AudioCallback &&) { } |
| 331 | virtual bool hasCallbackAPI() { return false; } |
| 332 | |
| 333 | QElapsedTimer elapsedTime; |
| 334 | |
| 335 | static QPlatformAudioSink *get(const QAudioSink &); |
| 336 | |
| 337 | using AudioEndpointRole = QtMultimediaPrivate::AudioEndpointRole; |
| 338 | virtual void setRole(AudioEndpointRole) { } |
| 339 | }; |
| 340 | |
| 341 | class Q_MULTIMEDIA_EXPORT QPlatformAudioSource : public QPlatformAudioEndpointBase |
| 342 | { |
| 343 | public: |
| 344 | explicit QPlatformAudioSource(QAudioDevice, const QAudioFormat &, QObject *parent); |
| 345 | virtual void start(QIODevice *device) = 0; |
| 346 | virtual QIODevice* start() = 0; |
| 347 | virtual void stop() = 0; |
| 348 | virtual void reset() = 0; |
| 349 | virtual void suspend() = 0; |
| 350 | virtual void resume() = 0; |
| 351 | virtual qsizetype bytesReady() const = 0; |
| 352 | virtual void setBufferSize(qsizetype value) = 0; |
| 353 | virtual void setHardwareBufferFrames(int32_t) { } |
| 354 | virtual int32_t hardwareBufferFrames() { return -1; } |
| 355 | virtual qsizetype bufferSize() const = 0; |
| 356 | virtual qint64 processedUSecs() const = 0; |
| 357 | |
| 358 | using AudioCallback = QtMultimediaPrivate::AudioSourceCallback; |
| 359 | |
| 360 | virtual void start(AudioCallback &&) { } |
| 361 | virtual bool hasCallbackAPI() { return false; } |
| 362 | |
| 363 | QElapsedTimer elapsedTime; |
| 364 | |
| 365 | static QPlatformAudioSource *get(const QAudioSource &); |
| 366 | }; |
| 367 | |
| 368 | // forward declarations |
| 369 | namespace QtMultimediaPrivate { |
| 370 | class QPlatformAudioSinkStream; |
| 371 | class QPlatformAudioSourceStream; |
| 372 | } // namespace QtMultimediaPrivate |
| 373 | |
| 374 | QT_END_NAMESPACE |
| 375 | |
| 376 | #endif // QAUDIOSYSTEM_H |
| 377 | |