project(MeterForPulseAudio VERSION 1.0)
set(MeterForPulseAudio_SOURCES
+ GameDevTools/src/GDT/GameLoop.cpp
src/Main.cpp
+ src/MfPA/Meter.cpp
)
+# check if submodules are loaded
+if(NOT EXISTS "${CMAKE_SOURCE_DIR}/AnotherDangParser/src")
+ message(FATAL_ERROR "AnotherDangParser submodule not loaded! Please run "
+ "'git submodule update --init --recursive'!")
+elseif(NOT EXISTS "${CMAKE_SOURCE_DIR}/GameDevTools/src")
+ message(FATAL_ERROR "GameDevTools submodule not loaded! Please run "
+ "'git submodule update --init --recursive'!")
+endif()
+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic")
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -D NDEBUG")
target_link_libraries(MeterForPulseAudio PUBLIC ${PULSEAUDIO_LIBRARY})
# add sub-project AnotherDangParser
-if(NOT EXISTS "${CMAKE_SOURCE_DIR}/AnotherDangParser/src")
- message(FATAL_ERROR "AnotherDangParser submodule not loaded! Please run "
- "'git submodule update --init --recursive'!")
-endif()
add_subdirectory(AnotherDangParser)
target_include_directories(MeterForPulseAudio PUBLIC AnotherDangParser/src)
target_link_libraries(MeterForPulseAudio PUBLIC AnotherDangParser)
+# add parts of sub-project GameDevTools
+target_include_directories(MeterForPulseAudio PUBLIC GameDevTools/src)
+
install(TARGETS MeterForPulseAudio
RUNTIME DESTINATION bin
ARCHIVE DESTINATION lib
--- /dev/null
+#include "Meter.hpp"
+
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+
+#include <GDT/GameLoop.hpp>
+
+MfPA::Meter::Meter(const char* sinkOrSourceName, bool isSink) :
+currentState(WAITING),
+isMonitoringSink(isSink),
+sinkOrSourceName(sinkOrSourceName),
+stream(nullptr),
+runFlag(true)
+{
+ setenv("PULSE_PROP_application.name", "Meter for PulseAudio", 1);
+ setenv("PULSE_PROP_application.icon_name", "multimedia-volume-control", 1);
+
+ pa_context_set_state_callback(
+ context,
+ MfPA::Meter::get_context_callback,
+ this);
+ pa_context_connect(context, nullptr, PA_CONTEXT_NOFLAGS, nullptr);
+}
+
+MfPA::Meter::~Meter()
+{
+ if(stream)
+ {
+ pa_stream_disconnect(stream);
+ pa_stream_unref(stream);
+ }
+}
+
+void MfPA::Meter::get_context_callback(pa_context* c, void* userdata)
+{
+ MfPA::Meter* meter = (MfPA::Meter*) userdata;
+ switch(pa_context_get_state(c))
+ {
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ meter->currentState = MfPA::Meter::WAITING;
+ break;
+ case PA_CONTEXT_READY:
+ meter->currentState = MfPA::Meter::PROCESSING;
+ if(meter->sinkOrSourceName[0] == '\0')
+ {
+ // sinkOrSourceName not provided, get default
+ pa_operation_unref(pa_context_get_server_info(
+ c,
+ MfPA::Meter::get_defaults_callback,
+ userdata));
+ }
+ else if(meter->isMonitoringSink)
+ {
+ // sink provided, getting info on sink
+ pa_operation_unref(pa_context_get_sink_info_by_name(
+ c,
+ meter->sinkOrSourceName,
+ MfPA::Meter::get_sink_info_callback,
+ userdata));
+ }
+ else
+ {
+ // source provided, getting info on source
+ pa_operation_unref(pa_context_get_source_info_by_name(
+ c,
+ meter->sinkOrSourceName,
+ MfPA::Meter::get_source_info_callback,
+ userdata));
+ }
+ break;
+ case PA_CONTEXT_FAILED:
+ meter->currentState = MfPA::Meter::FAILED;
+ break;
+ case PA_CONTEXT_TERMINATED:
+ meter->currentState = MfPA::Meter::TERMINATED;
+ break;
+ }
+}
+
+void MfPA::Meter::get_defaults_callback(
+ pa_context* c,
+ const pa_server_info* i,
+ void* userdata)
+{
+ MfPA::Meter* meter = (MfPA::Meter*) userdata;
+ if(meter->isMonitoringSink)
+ {
+ meter->sinkOrSourceName = i->default_sink_name;
+ // get sink info
+ pa_operation_unref(pa_context_get_sink_info_by_name(
+ c,
+ meter->sinkOrSourceName,
+ MfPA::Meter::get_sink_info_callback,
+ userdata));
+ }
+ else
+ {
+ meter->sinkOrSourceName = i->default_source_name;
+ // get source info
+ pa_operation_unref(pa_context_get_source_info_by_name(
+ c,
+ meter->sinkOrSourceName,
+ MfPA::Meter::get_source_info_callback,
+ userdata));
+ }
+}
+
+void MfPA::Meter::get_sink_info_callback(
+ pa_context* c,
+ const pa_sink_info* i,
+ int eol,
+ void* userdata)
+{
+ MfPA::Meter* meter = (MfPA::Meter*) userdata;
+ if(eol != PA_OK)
+ {
+ meter->currentState = MfPA::Meter::FAILED;
+ std::cerr << pa_strerror(eol) << std::endl;
+ return;
+ }
+ // get monitor-source of sink
+ pa_operation_unref(pa_context_get_source_info_by_name(
+ c,
+ i->monitor_source_name,
+ MfPA::Meter::get_source_info_callback,
+ userdata));
+}
+
+void MfPA::Meter::get_source_info_callback(
+ pa_context* c,
+ const pa_source_info* i,
+ int eol,
+ void* userdata)
+{
+ MfPA::Meter* meter = (MfPA::Meter*) userdata;
+ if(eol != PA_OK)
+ {
+ meter->currentState = MfPA::Meter::FAILED;
+ std::cerr << pa_strerror(eol) << std::endl;
+ return;
+ }
+
+ pa_sample_spec sampleSpec;
+ sampleSpec.format = PA_SAMPLE_FLOAT32LE;
+ sampleSpec.rate = i->sample_spec.rate;
+ sampleSpec.channels = i->sample_spec.channels;
+ meter->stream = pa_stream_new(
+ c,
+ "Meter for PulseAudio stream",
+ &sampleSpec,
+ &i->channel_map);
+ pa_stream_set_state_callback(
+ meter->stream,
+ MfPA::Meter::get_stream_state_callback,
+ userdata);
+ pa_stream_set_read_callback(
+ meter->stream,
+ MfPA::Meter::get_stream_data_callback,
+ userdata);
+ pa_stream_connect_record(
+ meter->stream,
+ i->name,
+ nullptr,
+ PA_STREAM_PEAK_DETECT);
+}
+
+void MfPA::Meter::get_stream_state_callback(pa_stream* s, void* userdata)
+{
+ MfPA::Meter* meter = (MfPA::Meter*) userdata;
+ switch(pa_stream_get_state(s))
+ {
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ break;
+ case PA_STREAM_READY:
+ meter->currentState = MfPA::Meter::READY;
+ break;
+ case PA_STREAM_FAILED:
+ meter->currentState = MfPA::Meter::FAILED;
+ std::cerr << "ERROR: Failed to get stream";
+ break;
+ case PA_STREAM_TERMINATED:
+ meter->currentState = MfPA::Meter::TERMINATED;
+ break;
+ }
+}
+
+void MfPA::Meter::get_stream_data_callback(
+ pa_stream* s,
+ size_t nbytes,
+ void* userdata)
+{
+ MfPA::Meter* meter = (MfPA::Meter*) userdata;
+
+ const void* data;
+ if(pa_stream_peek(s, &data, &nbytes) < 0)
+ {
+ std::cerr << pa_strerror(pa_context_errno(meter->context))
+ << std::endl;
+ return;
+ }
+
+ std::vector<float> v(nbytes/sizeof(float));
+ memcpy(v.data(), data, nbytes);
+
+ {
+ std::lock_guard<std::mutex> lock(meter->queueMutex);
+ meter->sampleQueue.push(std::move(v));
+ }
+
+ pa_stream_drop(s);
+}
+
+void MfPA::Meter::startMainLoop()
+{
+ GDT::IntervalBasedGameLoop(
+ &runFlag,
+ [this] (float dt) {
+ update(dt);
+ },
+ [this] () {
+ draw();
+ },
+ 60,
+ 1.0f / 120.0f);
+}
+
+void MfPA::Meter::update(float dt)
+{
+ if(currentState == TERMINATED || currentState == FAILED)
+ {
+ runFlag = false;
+ }
+}
+
+void MfPA::Meter::draw()
+{
+}
--- /dev/null
+#ifndef METER_FOR_PULSEAUDIO_HPP
+#define METER_FOR_PULSEAUDIO_HPP
+
+#include <pulse/pulseaudio.h>
+
+#include <mutex>
+#include <queue>
+#include <vector>
+
+namespace MfPA
+{
+
+class Meter
+{
+public:
+ Meter(const char* sinkOrSourceName = "", bool isSink = true);
+ ~Meter();
+
+ // callbacks required by pulseaudio
+
+ // used for pa_context_set_state_callback
+ static void get_context_callback(pa_context* c, void* userdata);
+ // used for pa_context_get_server_info
+ static void get_defaults_callback(
+ pa_context* c,
+ const pa_server_info* i,
+ void* userdata);
+ // used for pa_context_get_sink_info_by_name
+ static void get_sink_info_callback(
+ pa_context* c,
+ const pa_sink_info* i,
+ int eol,
+ void* userdata);
+ // used for pa_context_get_source_info_by_name
+ static void get_source_info_callback(
+ pa_context* c,
+ const pa_source_info* i,
+ int eol,
+ void* userdata);
+ // used for pa_stream_set_state_callback
+ static void get_stream_state_callback(pa_stream* s, void* userdata);
+ // used for pa_stream_set_read_callback
+ static void get_stream_data_callback(
+ pa_stream* s,
+ size_t nbytes,
+ void* userdata);
+
+ void startMainLoop();
+
+private:
+ enum CurrentState
+ {
+ WAITING,
+ READY,
+ FAILED,
+ TERMINATED,
+ PROCESSING
+ };
+ CurrentState currentState;
+ bool isMonitoringSink;
+ const char* sinkOrSourceName;
+
+ pa_context* context;
+ pa_stream* stream;
+
+ std::mutex queueMutex;
+ std::queue<std::vector<float>> sampleQueue;
+
+ bool runFlag;
+
+ void update(float dt);
+ void draw();
+
+};
+
+} // namespace MfPA
+#endif