/* * Copyright (C) 2012, 2013 Canonical, Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "volumepeakdetector.h" static void stream_read_callback(pa_stream * /*s*/, size_t /*length*/, void *userdata) { PulseAudioVolumePeakDetector *peakDetector = static_cast(userdata); peakDetector->processData(); } static void context_state_callback(pa_context *c, void *userdata) { PulseAudioVolumePeakDetector *peakDetector = static_cast(userdata); switch (pa_context_get_state(c)) { case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; case PA_CONTEXT_READY: peakDetector->startStream(); break; case PA_CONTEXT_TERMINATED: case PA_CONTEXT_FAILED: default: peakDetector->quit(); } } PulseAudioVolumePeakDetector::PulseAudioVolumePeakDetector() : m_accumulatedValue(0) , m_nAccumulatedValues(0) , m_accumulatedValuesLimit(1) , m_context(NULL) , m_mainloop_api(NULL) , m_stream(NULL) { } void PulseAudioVolumePeakDetector::start() { pa_mainloop *mainloop = NULL; /* Set up a new main loop */ mainloop = pa_mainloop_new(); if (!mainloop) return; m_mainloop_api = pa_mainloop_get_api(mainloop); /* Create a new connection context */ m_context = pa_context_new(m_mainloop_api, NULL); if (!m_context) { goto quit; } pa_context_set_state_callback(m_context, context_state_callback, this); /* Connect the context */ if (pa_context_connect(m_context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { goto quit; } /* Run the main loop */ if (pa_mainloop_run(mainloop, NULL) < 0) { goto quit; } quit: if (m_stream) pa_stream_unref(m_stream); if (m_context) pa_context_unref(m_context); if (mainloop) { pa_signal_done(); pa_mainloop_free(mainloop); } QThread::currentThread()->quit(); } int PulseAudioVolumePeakDetector::nAccumulatedValuesLimit() const { return m_accumulatedValuesLimit; } void PulseAudioVolumePeakDetector::setNAccumulatedValuesLimit(int limit) { m_accumulatedValuesLimit = limit; } // FIXME 16000 is hardcoded because julius needs it // and the hardware/driver/pulse something gets confused // if we use different rates // We would be happier with a much smaller value like 100 static uint voice_needed_rate = 16000; void PulseAudioVolumePeakDetector::startStream() { pa_buffer_attr buffer_attr; pa_proplist *proplist = pa_proplist_new(); // FIXME 16000 is hardcoded because julius needs it // and the hardware/driver/pulse something gets confused // if we use different rates // We would be happier with a much smaller value like 100 pa_sample_spec sample_spec; sample_spec.format = PA_SAMPLE_FLOAT32; sample_spec.rate = 16000; sample_spec.channels = 1; pa_channel_map channel_map; pa_channel_map_init_extend(&channel_map, sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); pa_proplist_sets(proplist, PA_PROP_MEDIA_NAME, "HUD Peak Detector"); m_stream = pa_stream_new_with_proplist(m_context, NULL, &sample_spec, &channel_map, proplist); if (!m_stream) { pa_proplist_free(proplist); quit(); return; } pa_proplist_free(proplist); pa_stream_set_read_callback(m_stream, stream_read_callback, this); memset(&buffer_attr, 0, sizeof(buffer_attr)); buffer_attr.maxlength = (uint32_t) -1; buffer_attr.fragsize = sizeof(float); if (pa_stream_connect_record(m_stream, NULL, &buffer_attr, (pa_stream_flags_t)(PA_STREAM_PEAK_DETECT | PA_STREAM_ADJUST_LATENCY)) < 0) { quit(); } } void PulseAudioVolumePeakDetector::processData() { while (pa_stream_readable_size(m_stream) > 0) { const void *data; size_t length; if (pa_stream_peek(m_stream, &data, &length) < 0) { quit(); return; } const float *values = (float*)data; for (size_t i = 0; i < length / sizeof(float); ++i) { float value = values[i]; if (value < 0) value = 0; if (value > 1) value = 1; m_nAccumulatedValues++; m_accumulatedValue += value; if (m_nAccumulatedValues == m_accumulatedValuesLimit) { Q_EMIT newPeak(m_accumulatedValue / m_nAccumulatedValues); m_nAccumulatedValues = 0; m_accumulatedValue = 0; } } pa_stream_drop(m_stream); } } void PulseAudioVolumePeakDetector::quit() { m_mainloop_api->quit(m_mainloop_api, 0); } /* VolumePeakDetector */ VolumePeakDetector::VolumePeakDetector() { QObject::connect(&m_thread, SIGNAL(started()), &m_volumeDetector, SLOT(start())); QObject::connect(&m_volumeDetector, SIGNAL(newPeak(float)), this, SIGNAL(newPeak(float))); m_volumeDetector.moveToThread(&m_thread); } bool VolumePeakDetector::enabled() const { return m_thread.isRunning(); } int VolumePeakDetector::desiredInterval() const { return m_volumeDetector.nAccumulatedValuesLimit() * 1000 / voice_needed_rate; } void VolumePeakDetector::setDesiredInterval(int interval) { m_volumeDetector.setNAccumulatedValuesLimit(voice_needed_rate * interval / 1000); } void VolumePeakDetector::setEnabled(int enabled) { if (enabled) { if (!m_thread.isRunning()) { m_thread.start(); } } else { if (m_thread.isRunning()) { m_volumeDetector.quit(); } } }