PlaybackStreamPulseAudio.cpp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. /*
  2. * Copyright (c) 2023, Gregory Bertilson <zaggy1024@gmail.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "PlaybackStreamPulseAudio.h"
  7. #include <LibCore/ThreadedPromise.h>
  8. namespace Audio {
  9. #define TRY_OR_EXIT_THREAD(expression) \
  10. ({ \
  11. auto&& __temporary_result = (expression); \
  12. if (__temporary_result.is_error()) [[unlikely]] { \
  13. warnln("Failure in PulseAudio control thread: {}", __temporary_result.error().string_literal()); \
  14. internal_state->exit(); \
  15. return 1; \
  16. } \
  17. __temporary_result.release_value(); \
  18. })
  19. ErrorOr<NonnullRefPtr<PlaybackStream>> PlaybackStreamPulseAudio::create(OutputState initial_state, u32 sample_rate, u8 channels, u32 target_latency_ms, AudioDataRequestCallback&& data_request_callback)
  20. {
  21. VERIFY(data_request_callback);
  22. // Create an internal state for the control thread to hold on to.
  23. auto internal_state = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) InternalState()));
  24. auto playback_stream = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) PlaybackStreamPulseAudio(internal_state)));
  25. // Create the control thread and start it.
  26. auto thread = TRY(Threading::Thread::try_create([=, data_request_callback = move(data_request_callback)]() mutable {
  27. auto context = TRY_OR_EXIT_THREAD(PulseAudioContext::instance());
  28. internal_state->set_stream(TRY_OR_EXIT_THREAD(context->create_stream(initial_state, sample_rate, channels, target_latency_ms, [data_request_callback = move(data_request_callback)](PulseAudioStream&, Bytes buffer, size_t sample_count) {
  29. return data_request_callback(buffer, PcmSampleFormat::Float32, sample_count);
  30. })));
  31. // PulseAudio retains the last volume it sets for an application. We want to consistently
  32. // start at 100% volume instead.
  33. TRY_OR_EXIT_THREAD(internal_state->stream()->set_volume(1.0));
  34. internal_state->thread_loop();
  35. return 0;
  36. },
  37. "Audio::PlaybackStream"sv));
  38. thread->start();
  39. thread->detach();
  40. return playback_stream;
  41. }
  42. PlaybackStreamPulseAudio::PlaybackStreamPulseAudio(NonnullRefPtr<InternalState> state)
  43. : m_state(move(state))
  44. {
  45. }
  46. PlaybackStreamPulseAudio::~PlaybackStreamPulseAudio()
  47. {
  48. m_state->exit();
  49. }
  50. #define TRY_OR_REJECT(expression, ...) \
  51. ({ \
  52. auto&& __temporary_result = (expression); \
  53. if (__temporary_result.is_error()) [[unlikely]] { \
  54. promise->reject(__temporary_result.release_error()); \
  55. return __VA_ARGS__; \
  56. } \
  57. __temporary_result.release_value(); \
  58. })
  59. void PlaybackStreamPulseAudio::set_underrun_callback(Function<void()> callback)
  60. {
  61. m_state->enqueue([this, callback = move(callback)]() mutable {
  62. m_state->stream()->set_underrun_callback(move(callback));
  63. });
  64. }
  65. NonnullRefPtr<Core::ThreadedPromise<Duration>> PlaybackStreamPulseAudio::resume()
  66. {
  67. auto promise = Core::ThreadedPromise<Duration>::create();
  68. TRY_OR_REJECT(m_state->check_is_running(), promise);
  69. m_state->enqueue([this, promise]() {
  70. TRY_OR_REJECT(m_state->stream()->resume());
  71. promise->resolve(TRY_OR_REJECT(m_state->stream()->total_time_played()));
  72. });
  73. return promise;
  74. }
  75. NonnullRefPtr<Core::ThreadedPromise<void>> PlaybackStreamPulseAudio::drain_buffer_and_suspend()
  76. {
  77. auto promise = Core::ThreadedPromise<void>::create();
  78. TRY_OR_REJECT(m_state->check_is_running(), promise);
  79. m_state->enqueue([this, promise]() {
  80. TRY_OR_REJECT(m_state->stream()->drain_and_suspend());
  81. promise->resolve();
  82. });
  83. return promise;
  84. }
  85. NonnullRefPtr<Core::ThreadedPromise<void>> PlaybackStreamPulseAudio::discard_buffer_and_suspend()
  86. {
  87. auto promise = Core::ThreadedPromise<void>::create();
  88. TRY_OR_REJECT(m_state->check_is_running(), promise);
  89. m_state->enqueue([this, promise]() {
  90. TRY_OR_REJECT(m_state->stream()->flush_and_suspend());
  91. promise->resolve();
  92. });
  93. return promise;
  94. }
  95. ErrorOr<Duration> PlaybackStreamPulseAudio::total_time_played()
  96. {
  97. if (m_state->stream() != nullptr)
  98. return m_state->stream()->total_time_played();
  99. return Duration::zero();
  100. }
  101. NonnullRefPtr<Core::ThreadedPromise<void>> PlaybackStreamPulseAudio::set_volume(double volume)
  102. {
  103. auto promise = Core::ThreadedPromise<void>::create();
  104. TRY_OR_REJECT(m_state->check_is_running(), promise);
  105. m_state->enqueue([this, promise, volume]() {
  106. TRY_OR_REJECT(m_state->stream()->set_volume(volume));
  107. promise->resolve();
  108. });
  109. return promise;
  110. }
  111. ErrorOr<void> PlaybackStreamPulseAudio::InternalState::check_is_running()
  112. {
  113. if (m_exit)
  114. return Error::from_string_literal("PulseAudio control thread loop is not running");
  115. return {};
  116. }
  117. void PlaybackStreamPulseAudio::InternalState::set_stream(NonnullRefPtr<PulseAudioStream> const& stream)
  118. {
  119. m_stream = stream;
  120. }
  121. RefPtr<PulseAudioStream> PlaybackStreamPulseAudio::InternalState::stream()
  122. {
  123. return m_stream;
  124. }
  125. void PlaybackStreamPulseAudio::InternalState::enqueue(Function<void()>&& task)
  126. {
  127. Threading::MutexLocker locker { m_mutex };
  128. m_tasks.enqueue(forward<Function<void()>>(task));
  129. m_wake_condition.signal();
  130. }
  131. void PlaybackStreamPulseAudio::InternalState::thread_loop()
  132. {
  133. while (true) {
  134. auto task = [this]() -> Function<void()> {
  135. Threading::MutexLocker locker { m_mutex };
  136. while (m_tasks.is_empty() && !m_exit)
  137. m_wake_condition.wait();
  138. if (m_exit)
  139. return nullptr;
  140. return m_tasks.dequeue();
  141. }();
  142. if (!task) {
  143. VERIFY(m_exit);
  144. break;
  145. }
  146. task();
  147. }
  148. }
  149. void PlaybackStreamPulseAudio::InternalState::exit()
  150. {
  151. m_exit = true;
  152. m_wake_condition.signal();
  153. }
  154. }