]> git.seodisparate.com - MeterForPulseAudio/commitdiff
Add pulseaudio api usage (untested)
authorStephen Seo <seo.disparate@gmail.com>
Sat, 16 Jun 2018 13:13:20 +0000 (22:13 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Sat, 16 Jun 2018 13:13:20 +0000 (22:13 +0900)
.gitmodules
CMakeLists.txt
GameDevTools [new submodule]
src/MfPA/Meter.cpp [new file with mode: 0644]
src/MfPA/Meter.hpp [new file with mode: 0644]

index 0e559350ec979ac614e2ad5c0d23f3a9aeba27f3..6763934695c3b325d86f9f83bf82364ce3e7ec5e 100644 (file)
@@ -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
index 64245c8c815c32fd0fcd40062353c485da9666d8..22274ed50bddb6b58b4d9ccaac0b6ed2c4a1c288 100644 (file)
@@ -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 (submodule)
index 0000000..a676d1d
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit a676d1dc03553c8daed5fa10bd9ae5f453dfaf0d
diff --git a/src/MfPA/Meter.cpp b/src/MfPA/Meter.cpp
new file mode 100644 (file)
index 0000000..d802e25
--- /dev/null
@@ -0,0 +1,242 @@
+#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()
+{
+}
diff --git a/src/MfPA/Meter.hpp b/src/MfPA/Meter.hpp
new file mode 100644 (file)
index 0000000..5c71152
--- /dev/null
@@ -0,0 +1,77 @@
+#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