CPulseAudioPlaybackRecorder.cpp 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. #include "CPulseAudioPlaybackRecorder.h"
  2. #include "External/Android/fft.h"
  3. #include "WallpaperEngine/Logging/CLog.h"
  4. #include <cmath>
  5. #include <cstring>
  6. #include <glm/common.hpp>
  7. namespace WallpaperEngine::Audio::Drivers::Recorders {
  8. float movetowards (float current, float target, float maxDelta) {
  9. if (abs (target - current) <= maxDelta)
  10. return target;
  11. return current + glm::sign (target - current) * maxDelta;
  12. }
  13. void pa_stream_notify_cb (pa_stream* stream, void* /*userdata*/) {
  14. switch (pa_stream_get_state (stream)) {
  15. case PA_STREAM_FAILED: sLog.error ("Cannot open stream for capture. Audio processing is disabled"); break;
  16. case PA_STREAM_READY: sLog.debug ("Capture stream ready"); break;
  17. }
  18. }
  19. void pa_stream_read_cb (pa_stream* stream, const size_t /*nbytes*/, void* userdata) {
  20. auto* recorder = static_cast<CPulseAudioPlaybackRecorder*> (userdata);
  21. // Careful when to pa_stream_peek() and pa_stream_drop()!
  22. // c.f. https://www.freedesktop.org/software/pulseaudio/doxygen/stream_8h.html#ac2838c449cde56e169224d7fe3d00824
  23. const void* data = nullptr;
  24. size_t currentSize;
  25. if (pa_stream_peek (stream, &data, &currentSize) != 0) {
  26. sLog.error ("Failed to peek at stream data...");
  27. return;
  28. }
  29. if (data == nullptr && currentSize == 0) {
  30. // No data in the buffer, ignore.
  31. return;
  32. }
  33. if (data == nullptr && currentSize > 0) {
  34. // Hole in the buffer. We must drop it.
  35. if (pa_stream_drop (stream) != 0) {
  36. sLog.error ("Failed to drop a hole while capturing!");
  37. return;
  38. }
  39. } else if (currentSize > 0 && data) {
  40. size_t dataToCopy = std::min (currentSize, WAVE_BUFFER_SIZE - recorder->currentWritePointer);
  41. memcpy (&recorder->audio_buffer_tmp [recorder->currentWritePointer], data, dataToCopy * sizeof (uint8_t));
  42. recorder->currentWritePointer += dataToCopy;
  43. if (recorder->currentWritePointer == WAVE_BUFFER_SIZE) {
  44. // copy to the final buffer
  45. memcpy (recorder->audio_buffer, recorder->audio_buffer_tmp, WAVE_BUFFER_SIZE * sizeof (uint8_t));
  46. // reset the write pointer
  47. recorder->currentWritePointer = 0;
  48. recorder->fullframeReady = true;
  49. }
  50. // any data read left?
  51. if (dataToCopy < currentSize) {
  52. while ((currentSize - dataToCopy) > WAVE_BUFFER_SIZE)
  53. dataToCopy += WAVE_BUFFER_SIZE; // there's more than one full frame available, skip it entirely
  54. // data pending, keep it in the buffer
  55. memcpy (recorder->audio_buffer_tmp, data + dataToCopy, (currentSize - dataToCopy) * sizeof (uint8_t));
  56. recorder->currentWritePointer = currentSize - dataToCopy;
  57. }
  58. }
  59. if (pa_stream_drop (stream) != 0) {
  60. sLog.error ("Failed to drop data after peeking");
  61. }
  62. }
  63. void pa_server_info_cb (pa_context* ctx, const pa_server_info* info, void* userdata) {
  64. auto* recorder = static_cast<CPulseAudioPlaybackRecorder*> (userdata);
  65. pa_sample_spec spec;
  66. spec.format = PA_SAMPLE_U8;
  67. spec.rate = 44100;
  68. spec.channels = 1;
  69. if (recorder->getCaptureStream ()) {
  70. pa_stream_unref (recorder->getCaptureStream ());
  71. // get rid of the reference just in case
  72. recorder->setCaptureStream (nullptr);
  73. }
  74. pa_stream* captureStream = pa_stream_new (ctx, "output monitor", &spec, nullptr);
  75. // store the stream first, if the record start fails there'll still be a reference to it
  76. // so it can be free'd later
  77. recorder->setCaptureStream (captureStream),
  78. pa_stream_set_state_callback (captureStream, &pa_stream_notify_cb, userdata);
  79. pa_stream_set_read_callback (captureStream, &pa_stream_read_cb, userdata);
  80. std::string monitor_name (info->default_sink_name);
  81. monitor_name += ".monitor";
  82. if (pa_stream_connect_record (captureStream, monitor_name.c_str (), nullptr, PA_STREAM_NOFLAGS) != 0) {
  83. sLog.error ("Failed to connect to input for recording");
  84. }
  85. }
  86. void pa_context_subscribe_cb (pa_context* ctx, pa_subscription_event_type_t t, uint32_t idx, void* userdata) {
  87. // sink changes mean re-take the stream
  88. pa_context_get_server_info (ctx, &pa_server_info_cb, userdata);
  89. }
  90. void pa_context_notify_cb (pa_context* ctx, void* userdata) {
  91. switch (pa_context_get_state (ctx)) {
  92. case PA_CONTEXT_READY: {
  93. // set callback
  94. pa_context_set_subscribe_callback (ctx, pa_context_subscribe_cb, userdata);
  95. // set events mask and enable event callback.
  96. pa_operation* o = pa_context_subscribe (
  97. ctx, static_cast<pa_subscription_mask_t> (PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE),
  98. nullptr, nullptr);
  99. if (o)
  100. pa_operation_unref (o);
  101. // context being ready means to fetch the sink too
  102. pa_context_get_server_info (ctx, &pa_server_info_cb, userdata);
  103. break;
  104. }
  105. case PA_CONTEXT_FAILED:
  106. sLog.error ("PulseAudio context initialization failed. Audio processing is disabled");
  107. break;
  108. }
  109. }
  110. CPulseAudioPlaybackRecorder::CPulseAudioPlaybackRecorder () : m_captureStream (nullptr) {
  111. this->m_mainloop = pa_mainloop_new ();
  112. this->m_mainloopApi = pa_mainloop_get_api (this->m_mainloop);
  113. this->m_context = pa_context_new (this->m_mainloopApi, "wallpaperengine-audioprocessing");
  114. pa_context_set_state_callback (this->m_context, &pa_context_notify_cb, this);
  115. if (pa_context_connect (this->m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0) {
  116. sLog.error ("PulseAudio connection failed! Audio processing is disabled");
  117. return;
  118. }
  119. // wait until the context is ready
  120. while (pa_context_get_state (this->m_context) != PA_CONTEXT_READY)
  121. pa_mainloop_iterate (this->m_mainloop, 1, nullptr);
  122. }
  123. CPulseAudioPlaybackRecorder::~CPulseAudioPlaybackRecorder () {
  124. pa_context_disconnect (this->m_context);
  125. pa_mainloop_free (this->m_mainloop);
  126. }
  127. pa_stream* CPulseAudioPlaybackRecorder::getCaptureStream () {
  128. return this->m_captureStream;
  129. }
  130. void CPulseAudioPlaybackRecorder::setCaptureStream (pa_stream* stream) {
  131. this->m_captureStream = stream;
  132. }
  133. void CPulseAudioPlaybackRecorder::update () {
  134. pa_mainloop_iterate (this->m_mainloop, 0, nullptr);
  135. // interpolate current values to the destination
  136. for (int i = 0; i < 64; i++) {
  137. this->audio64 [i] = movetowards (this->audio64 [i], fft_destination64 [i], 0.1f);
  138. if (i >= 32)
  139. continue;
  140. this->audio32 [i] = movetowards (this->audio32 [i], fft_destination32 [i], 0.1f);
  141. if (i >= 16)
  142. continue;
  143. this->audio16 [i] = movetowards (this->audio16 [i], fft_destination16 [i], 0.1f);
  144. }
  145. if (!this->fullframeReady)
  146. return;
  147. this->fullframeReady = false;
  148. External::Android::doFft (audio_fft, audio_buffer);
  149. for (int i = 0; i < 64; i++) {
  150. const int paramInt = (i + 2) * 2;
  151. float f1 = audio_fft [paramInt];
  152. float f2 = audio_fft [paramInt + 1];
  153. f2 = f1 * f1 + f2 * f2;
  154. f1 = 0.0F;
  155. if (f2 > 0.0F)
  156. f1 = 0.35F * static_cast<float> (log10 (f2));
  157. this->fft_destination64 [i] =
  158. fmin (1.0F, f1 * static_cast<float> (2.0f - pow (M_E, (1.0F - i / 63.0F) * 1.0f - 0.5f)));
  159. this->fft_destination32 [i >> 1] =
  160. fmin (1.0F, f1 * static_cast<float> (2.0f - pow (M_E, (1.0F - i / 31.0F) * 1.0f - 0.5f)));
  161. this->fft_destination16 [i >> 2] =
  162. fmin (1.0F, f1 * static_cast<float> (2.0f - pow (M_E, (1.0F - i / 15.0F) * 1.0f - 0.5f)));
  163. }
  164. }
  165. } // namespace WallpaperEngine::Audio::Drivers::Recorders