199 lines
6.7 KiB
C++
199 lines
6.7 KiB
C++
#include <cstring>
|
|
#include <cmath>
|
|
#include <VulcanoLE/Audio/AudioGrabber.h>
|
|
#include <VUtils/Logging.h>
|
|
|
|
|
|
AudioGrabber::AudioGrabber() = default;
|
|
AudioGrabber::~AudioGrabber() = default;
|
|
|
|
bool AudioGrabber::read(stereoSample *buffer, uint32_t bufferSize) {
|
|
auto buffer_size_bytes = static_cast<size_t>(sizeof(stereoSample) * bufferSize);
|
|
if (m_pulseaudioSimple == nullptr) {
|
|
openPulseaudioSource(static_cast<uint32_t>(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<void *>(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<AudioGrabber *>(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<AudioGrabber *>(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<stereoSample *>(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<std::mutex> lck(m_mtx);
|
|
return loudness;
|
|
}
|
|
|
|
bool AudioGrabber::work() {
|
|
std::unique_lock<std::mutex> 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;
|
|
}
|
|
}
|