From 302e2f99f63aa2d0ee88fe42486b55130478ed72 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Sat, 16 Jun 2018 22:13:20 +0900 Subject: [PATCH] Add pulseaudio api usage (untested) --- .gitmodules | 3 + CMakeLists.txt | 18 +++- GameDevTools | 1 + src/MfPA/Meter.cpp | 242 +++++++++++++++++++++++++++++++++++++++++++++ src/MfPA/Meter.hpp | 77 +++++++++++++++ 5 files changed, 337 insertions(+), 4 deletions(-) create mode 160000 GameDevTools create mode 100644 src/MfPA/Meter.cpp create mode 100644 src/MfPA/Meter.hpp diff --git a/.gitmodules b/.gitmodules index 0e55935..6763934 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "AnotherDangParser"] path = AnotherDangParser url = https://github.com/Stephen-Seo/AnotherDangParser.git +[submodule "GameDevTools"] + path = GameDevTools + url = https://github.com/Stephen-Seo/GameDevTools.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 64245c8..22274ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,9 +3,20 @@ cmake_minimum_required(VERSION 3.0) 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") @@ -38,14 +49,13 @@ target_include_directories(MeterForPulseAudio PUBLIC ${PULSEAUDIO_INCLUDE_DIR}) 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 diff --git a/GameDevTools b/GameDevTools new file mode 160000 index 0000000..a676d1d --- /dev/null +++ b/GameDevTools @@ -0,0 +1 @@ +Subproject commit a676d1dc03553c8daed5fa10bd9ae5f453dfaf0d diff --git a/src/MfPA/Meter.cpp b/src/MfPA/Meter.cpp new file mode 100644 index 0000000..d802e25 --- /dev/null +++ b/src/MfPA/Meter.cpp @@ -0,0 +1,242 @@ +#include "Meter.hpp" + +#include +#include +#include + +#include + +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 v(nbytes/sizeof(float)); + memcpy(v.data(), data, nbytes); + + { + std::lock_guard 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() +{ +} diff --git a/src/MfPA/Meter.hpp b/src/MfPA/Meter.hpp new file mode 100644 index 0000000..5c71152 --- /dev/null +++ b/src/MfPA/Meter.hpp @@ -0,0 +1,77 @@ +#ifndef METER_FOR_PULSEAUDIO_HPP +#define METER_FOR_PULSEAUDIO_HPP + +#include + +#include +#include +#include + +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> sampleQueue; + + bool runFlag; + + void update(float dt); + void draw(); + +}; + +} // namespace MfPA +#endif -- 2.49.0