| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- #include "CPulseAudioPlaybackRecorder.h"
- #include "External/Android/fft.h"
- #include "WallpaperEngine/Logging/CLog.h"
- #include <cmath>
- #include <cstring>
- #include <glm/common.hpp>
- namespace WallpaperEngine::Audio::Drivers::Recorders {
- float movetowards (float current, float target, float maxDelta) {
- if (abs (target - current) <= maxDelta)
- return target;
- return current + glm::sign (target - current) * maxDelta;
- }
- void pa_stream_notify_cb (pa_stream* stream, void* /*userdata*/) {
- switch (pa_stream_get_state (stream)) {
- case PA_STREAM_FAILED: sLog.error ("Cannot open stream for capture. Audio processing is disabled"); break;
- case PA_STREAM_READY: sLog.debug ("Capture stream ready"); break;
- }
- }
- void pa_stream_read_cb (pa_stream* stream, const size_t /*nbytes*/, void* userdata) {
- auto* recorder = static_cast<CPulseAudioPlaybackRecorder*> (userdata);
- // Careful when to pa_stream_peek() and pa_stream_drop()!
- // c.f. https://www.freedesktop.org/software/pulseaudio/doxygen/stream_8h.html#ac2838c449cde56e169224d7fe3d00824
- const void* data = nullptr;
- size_t currentSize;
- if (pa_stream_peek (stream, &data, ¤tSize) != 0) {
- sLog.error ("Failed to peek at stream data...");
- return;
- }
- if (data == nullptr && currentSize == 0) {
- // No data in the buffer, ignore.
- return;
- }
- if (data == nullptr && currentSize > 0) {
- // Hole in the buffer. We must drop it.
- if (pa_stream_drop (stream) != 0) {
- sLog.error ("Failed to drop a hole while capturing!");
- return;
- }
- } else if (currentSize > 0 && data) {
- size_t dataToCopy = std::min (currentSize, WAVE_BUFFER_SIZE - recorder->currentWritePointer);
- memcpy (&recorder->audio_buffer_tmp [recorder->currentWritePointer], data, dataToCopy * sizeof (uint8_t));
- recorder->currentWritePointer += dataToCopy;
- if (recorder->currentWritePointer == WAVE_BUFFER_SIZE) {
- // copy to the final buffer
- memcpy (recorder->audio_buffer, recorder->audio_buffer_tmp, WAVE_BUFFER_SIZE * sizeof (uint8_t));
- // reset the write pointer
- recorder->currentWritePointer = 0;
- recorder->fullframeReady = true;
- }
- // any data read left?
- if (dataToCopy < currentSize) {
- while ((currentSize - dataToCopy) > WAVE_BUFFER_SIZE)
- dataToCopy += WAVE_BUFFER_SIZE; // there's more than one full frame available, skip it entirely
- // data pending, keep it in the buffer
- memcpy (recorder->audio_buffer_tmp, data + dataToCopy, (currentSize - dataToCopy) * sizeof (uint8_t));
- recorder->currentWritePointer = currentSize - dataToCopy;
- }
- }
- if (pa_stream_drop (stream) != 0) {
- sLog.error ("Failed to drop data after peeking");
- }
- }
- void pa_server_info_cb (pa_context* ctx, const pa_server_info* info, void* userdata) {
- auto* recorder = static_cast<CPulseAudioPlaybackRecorder*> (userdata);
- pa_sample_spec spec;
- spec.format = PA_SAMPLE_U8;
- spec.rate = 44100;
- spec.channels = 1;
- if (recorder->getCaptureStream ()) {
- pa_stream_unref (recorder->getCaptureStream ());
- // get rid of the reference just in case
- recorder->setCaptureStream (nullptr);
- }
- pa_stream* captureStream = pa_stream_new (ctx, "output monitor", &spec, nullptr);
- // store the stream first, if the record start fails there'll still be a reference to it
- // so it can be free'd later
- recorder->setCaptureStream (captureStream),
- pa_stream_set_state_callback (captureStream, &pa_stream_notify_cb, userdata);
- pa_stream_set_read_callback (captureStream, &pa_stream_read_cb, userdata);
- std::string monitor_name (info->default_sink_name);
- monitor_name += ".monitor";
- if (pa_stream_connect_record (captureStream, monitor_name.c_str (), nullptr, PA_STREAM_NOFLAGS) != 0) {
- sLog.error ("Failed to connect to input for recording");
- }
- }
- void pa_context_subscribe_cb (pa_context* ctx, pa_subscription_event_type_t t, uint32_t idx, void* userdata) {
- // sink changes mean re-take the stream
- pa_context_get_server_info (ctx, &pa_server_info_cb, userdata);
- }
- void pa_context_notify_cb (pa_context* ctx, void* userdata) {
- switch (pa_context_get_state (ctx)) {
- case PA_CONTEXT_READY: {
- // set callback
- pa_context_set_subscribe_callback (ctx, pa_context_subscribe_cb, userdata);
- // set events mask and enable event callback.
- pa_operation* o = pa_context_subscribe (
- ctx, static_cast<pa_subscription_mask_t> (PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE),
- nullptr, nullptr);
- if (o)
- pa_operation_unref (o);
- // context being ready means to fetch the sink too
- pa_context_get_server_info (ctx, &pa_server_info_cb, userdata);
- break;
- }
- case PA_CONTEXT_FAILED:
- sLog.error ("PulseAudio context initialization failed. Audio processing is disabled");
- break;
- }
- }
- CPulseAudioPlaybackRecorder::CPulseAudioPlaybackRecorder () : m_captureStream (nullptr) {
- this->m_mainloop = pa_mainloop_new ();
- this->m_mainloopApi = pa_mainloop_get_api (this->m_mainloop);
- this->m_context = pa_context_new (this->m_mainloopApi, "wallpaperengine-audioprocessing");
- pa_context_set_state_callback (this->m_context, &pa_context_notify_cb, this);
- if (pa_context_connect (this->m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0) {
- sLog.error ("PulseAudio connection failed! Audio processing is disabled");
- return;
- }
- // wait until the context is ready
- while (pa_context_get_state (this->m_context) != PA_CONTEXT_READY)
- pa_mainloop_iterate (this->m_mainloop, 1, nullptr);
- }
- CPulseAudioPlaybackRecorder::~CPulseAudioPlaybackRecorder () {
- pa_context_disconnect (this->m_context);
- pa_mainloop_free (this->m_mainloop);
- }
- pa_stream* CPulseAudioPlaybackRecorder::getCaptureStream () {
- return this->m_captureStream;
- }
- void CPulseAudioPlaybackRecorder::setCaptureStream (pa_stream* stream) {
- this->m_captureStream = stream;
- }
- void CPulseAudioPlaybackRecorder::update () {
- pa_mainloop_iterate (this->m_mainloop, 0, nullptr);
- // interpolate current values to the destination
- for (int i = 0; i < 64; i++) {
- this->audio64 [i] = movetowards (this->audio64 [i], fft_destination64 [i], 0.1f);
- if (i >= 32)
- continue;
- this->audio32 [i] = movetowards (this->audio32 [i], fft_destination32 [i], 0.1f);
- if (i >= 16)
- continue;
- this->audio16 [i] = movetowards (this->audio16 [i], fft_destination16 [i], 0.1f);
- }
- if (!this->fullframeReady)
- return;
- this->fullframeReady = false;
- External::Android::doFft (audio_fft, audio_buffer);
- for (int i = 0; i < 64; i++) {
- const int paramInt = (i + 2) * 2;
- float f1 = audio_fft [paramInt];
- float f2 = audio_fft [paramInt + 1];
- f2 = f1 * f1 + f2 * f2;
- f1 = 0.0F;
- if (f2 > 0.0F)
- f1 = 0.35F * static_cast<float> (log10 (f2));
- this->fft_destination64 [i] =
- fmin (1.0F, f1 * static_cast<float> (2.0f - pow (M_E, (1.0F - i / 63.0F) * 1.0f - 0.5f)));
- this->fft_destination32 [i >> 1] =
- fmin (1.0F, f1 * static_cast<float> (2.0f - pow (M_E, (1.0F - i / 31.0F) * 1.0f - 0.5f)));
- this->fft_destination16 [i >> 2] =
- fmin (1.0F, f1 * static_cast<float> (2.0f - pow (M_E, (1.0F - i / 15.0F) * 1.0f - 0.5f)));
- }
- }
- } // namespace WallpaperEngine::Audio::Drivers::Recorders
|