#include #include #include #include AudioGrabber::AudioGrabber() = default; AudioGrabber::~AudioGrabber() = default; bool AudioGrabber::read(stereoSample *buffer, uint32_t bufferSize) { auto buffer_size_bytes = static_cast(sizeof(stereoSample) * bufferSize); if (m_pulseaudioSimple == nullptr) { openPulseaudioSource(static_cast(buffer_size_bytes)); } if (m_pulseaudioSimple != nullptr) { memset(buffer, 0, buffer_size_bytes); int32_t error_code; auto return_code = pa_simple_read(m_pulseaudioSimple, buffer, buffer_size_bytes, &error_code); if (return_code < 0) { WARN("Could not finish reading pulse Audio stream buffer\n bytes read: %d buffer\n size: %d", return_code, buffer_size_bytes) // zero out buffer memset(buffer, 0, buffer_size_bytes); pa_simple_free(m_pulseaudioSimple); m_pulseaudioSimple = nullptr; return false; } // Success fully read entire buffer return true; } return false; } void AudioGrabber::populateDefaultSourceName() { pa_mainloop_api *mainloop_api; pa_context *pulseaudio_context; m_pulseaudioMainloop = pa_mainloop_new(); mainloop_api = pa_mainloop_get_api(m_pulseaudioMainloop); pulseaudio_context = pa_context_new(mainloop_api, "VulcanoLE device list"); pa_context_connect(pulseaudio_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr); pa_context_set_state_callback(pulseaudio_context, pulseaudioContextStateCallback, reinterpret_cast(this)); int ret; if (pa_mainloop_run(m_pulseaudioMainloop, &ret) < 0) { ERR("Could not open pulseaudio mainloop to find default device name: %d", ret) } } bool AudioGrabber::openPulseaudioSource(uint32_t maxBufferSize) { int32_t error_code = 0; static const pa_sample_spec sample_spec = { PA_SAMPLE_FLOAT32NE, sampleRate, channels }; static const pa_buffer_attr buffer_attr = { maxBufferSize, 0, 0, 0, (maxBufferSize / 2) }; populateDefaultSourceName(); if (!m_PulseaudioDefaultSourceName.empty()) { m_pulseaudioSimple = pa_simple_new(nullptr, recStreamName, PA_STREAM_RECORD, m_PulseaudioDefaultSourceName.c_str(), recStreamDescription, &sample_spec, nullptr, &buffer_attr, &error_code); } if (m_pulseaudioSimple == nullptr) { m_pulseaudioSimple = pa_simple_new(nullptr, recStreamName, PA_STREAM_RECORD, nullptr, recStreamDescription, &sample_spec, nullptr, &buffer_attr, &error_code); } if (m_pulseaudioSimple == nullptr) { m_pulseaudioSimple = pa_simple_new(nullptr, recStreamName, PA_STREAM_RECORD, "0", recStreamDescription, &sample_spec, nullptr, &buffer_attr, &error_code); } if (m_pulseaudioSimple != nullptr) { return true; } ERR("Could not open pulseaudio source: %s", pa_strerror(error_code)) return false; } void AudioGrabber::pulseaudioContextStateCallback(pa_context *c, void *userdata) { switch (pa_context_get_state(c)) { case PA_CONTEXT_UNCONNECTED: case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; case PA_CONTEXT_READY: { pa_operation_unref(pa_context_get_server_info( c, pulseaudioServerInfoCallback, userdata)); break; } case PA_CONTEXT_FAILED: case PA_CONTEXT_TERMINATED: auto *src = reinterpret_cast(userdata); pa_mainloop_quit(src->m_pulseaudioMainloop, 0); break; } } void AudioGrabber::pulseaudioServerInfoCallback(pa_context *context, const pa_server_info *i, void *userdata) { if (i != nullptr) { auto *src = reinterpret_cast(userdata); std::string name = i->default_sink_name; name.append(defaultMonitorPostfix); src->m_PulseaudioDefaultSourceName = name; // stop mainloop after finding default name pa_mainloop_quit(src->m_pulseaudioMainloop, 0); } } AudioGrabber *AudioGrabber::createAudioGrabber() { auto *grabber = new AudioGrabber(); return grabber; } void AudioGrabber::init() { m_buffer = static_cast(calloc(BUFFER_SIZE, sizeof(stereoSample))); if (env != nullptr) m_scale = env->getAsDouble("audio_scale", 1.0); DBG("SET Audio Scale: %.3f", m_scale) loudness = { 0.0, 0.0 }; } void AudioGrabber::calculateRMS(stereoSample *pFrame) { float squareL = 0, meanL; float squareR = 0, meanR; for (int i = 0; i < BUFFER_SIZE; i++) { squareL += std::pow(pFrame[0].l, 2); squareR += std::pow(pFrame[0].r, 2); } meanL = (squareL / (float) (BUFFER_SIZE)); meanR = (squareR / (float) (BUFFER_SIZE)); loudness = { std::sqrt(meanL), std::sqrt(meanR) }; } void AudioGrabber::calculatePEAK(stereoSample *pFrame) { stereoSampleFrame max = { 0, 0 }; for (int i = 0; i < BUFFER_SIZE; i++) { float left = std::abs(pFrame[0].l); float right = std::abs(pFrame[0].r); if (left > max.l) max.l = left; if (right > max.r) max.r = right; } loudness = max; } stereoSampleFrame AudioGrabber::getLoudness() { std::unique_lock lck(m_mtx); return loudness; } bool AudioGrabber::work() { std::unique_lock lck(m_mtx); if (this->read(m_buffer, BUFFER_SIZE)) { switch (requestMode) { case ReqMode::FFT: // FFT get's better results with maybe a bit scaling.. for RMS and PEAK this gets "worse" for (int i = 0; i < BUFFER_SIZE; ++i) { m_buffer[i].l *= m_scale; m_buffer[i].r *= m_scale; } fft.process(m_buffer); break; case ReqMode::RMS: calculateRMS(m_buffer); break; case ReqMode::PEAK: calculatePEAK(m_buffer); break; default: fft.process(m_buffer); calculateRMS(m_buffer); calculatePEAK(m_buffer); } return true; } else { DBG("Wait for Data") return false; } }