/* * Copyright (c) 2023, Tim Flynn * Copyright (c) 2023, Andrew Kaster * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include namespace Ladybird { static constexpr u32 UPDATE_RATE_MS = 10; struct AudioTask { enum class Type { Stop, Play, Pause, Seek, Volume, RecreateAudioDevice, }; Type type; Optional data {}; }; using AudioTaskQueue = Core::SharedSingleProducerCircularQueue; class AudioThread final : public QThread { // We have to use QThread, otherwise internal Qt media QTimer objects do not work. Q_OBJECT public: static ErrorOr> create(NonnullRefPtr loader); ErrorOr stop(); Duration duration() const { return m_duration; } ErrorOr queue_task(AudioTask task); Q_SIGNALS: void playback_position_updated(Duration); private: AudioThread(NonnullRefPtr loader, AudioTaskQueue task_queue); enum class Paused { Yes, No, }; void run() override; ErrorOr play_next_samples(QAudioSink& audio_output, QIODevice& io_device); void enqueue_samples(QAudioSink const& audio_output, QIODevice& io_device, FixedArray samples); template void write_sample(FixedMemoryStream& stream, float sample) { // The values that need to be written to the stream vary depending on the output channel format, and isn't // particularly well documented. The value derivations performed below were adapted from a Qt example: // https://code.qt.io/cgit/qt/qtmultimedia.git/tree/examples/multimedia/audiooutput/audiooutput.cpp?h=6.4.2#n46 LittleEndian pcm; if constexpr (IsSame) pcm = static_cast((sample + 1.0f) / 2 * NumericLimits::max()); else if constexpr (IsSame) pcm = static_cast(sample * NumericLimits::max()); else if constexpr (IsSame) pcm = static_cast(sample * NumericLimits::max()); else if constexpr (IsSame) pcm = sample; else static_assert(DependentFalse); MUST(stream.write_value(pcm)); } NonnullRefPtr m_loader; AudioTaskQueue m_task_queue; QByteArray m_sample_buffer; Duration m_duration; Duration m_position; }; }