CPulseAudioPlaybackRecorder.cpp 8.3 KB

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