Quellcode durchsuchen

Added sound playback detection to mute when something else plays audio

Signed-off-by: Alexis Maiquez <almamu@almamu.com>
Alexis Maiquez vor 3 Jahren
Ursprung
Commit
978f56cdca

+ 1 - 1
.github/workflows/cmake.yml

@@ -21,7 +21,7 @@ jobs:
     - uses: actions/checkout@v3
 
     - name: Install dependencies
-      run: sudo apt-get update && sudo apt-get -y install libxrandr-dev libfreeimage-dev libxinerama-dev libxcursor-dev libxi-dev libgl-dev libglew-dev freeglut3-dev libsdl2-dev liblz4-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libxxf86vm-dev libglm-dev libglfw3-dev libmpv-dev mpv libmpv1
+      run: sudo apt-get update && sudo apt-get -y install libxrandr-dev libfreeimage-dev libxinerama-dev libxcursor-dev libxi-dev libgl-dev libglew-dev freeglut3-dev libsdl2-dev liblz4-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libxxf86vm-dev libglm-dev libglfw3-dev libmpv-dev mpv libmpv1 libpulse-dev libpulse0
 
     - name: Configure CMake
       # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.

+ 8 - 0
CMakeLists.txt

@@ -26,6 +26,7 @@ find_package(MPV REQUIRED)
 find_package(LZ4 REQUIRED)
 find_package(FFMPEG REQUIRED)
 find_package(FreeImage REQUIRED)
+find_package(PulseAudio REQUIRED)
 
 include_directories(
     ${MPV_INCLUDE_DIR}
@@ -36,6 +37,7 @@ include_directories(
     ${SDL2_INCLUDE_DIRS}
     ${FFMPEG_INCLUDE_DIR}
     ${FREEIMAGE_INCLUDE_DIR}
+    ${PULSEAUDIO_INCLUDE_DIR}
     src
     include)
 
@@ -75,6 +77,11 @@ add_executable(
     src/WallpaperEngine/Core/Core.h
     src/WallpaperEngine/Core/Core.cpp
 
+    src/WallpaperEngine/Audio/Detectors/CAudioPlayingDetector.cpp
+    src/WallpaperEngine/Audio/Detectors/CAudioPlayingDetector.h
+    src/WallpaperEngine/Audio/Detectors/CPulseAudioPlayingDetector.cpp
+    src/WallpaperEngine/Audio/Detectors/CPulseAudioPlayingDetector.cpp
+
     src/WallpaperEngine/Audio/Drivers/CAudioDriver.cpp
     src/WallpaperEngine/Audio/Drivers/CAudioDriver.h
     src/WallpaperEngine/Audio/Drivers/CSDLAudioDriver.cpp
@@ -266,6 +273,7 @@ target_link_libraries(linux-wallpaperengine
     ${FFMPEG_LIBRARIES}
     ${FREEIMAGE_LIBRARIES}
     ${MPV_LIBRARY}
+    ${PULSEAUDIO_LIBRARY}
     glfw)
 
 file(CREATE_LINK linux-wallpaperengine wallengine SYMBOLIC)

+ 71 - 0
CMakeModules/FindPulseAudio.cmake

@@ -0,0 +1,71 @@
+# Try to find the PulseAudio library
+#
+# Once done this will define:
+#
+#  PULSEAUDIO_FOUND - system has the PulseAudio library
+#  PULSEAUDIO_INCLUDE_DIR - the PulseAudio include directory
+#  PULSEAUDIO_LIBRARY - the libraries needed to use PulseAudio
+#  PULSEAUDIO_MAINLOOP_LIBRARY - the libraries needed to use PulsAudio Mainloop
+#
+# The minimum required version of PulseAudio can be specified using the
+# standard syntax, e.g. find_package(PulseAudio 1.0)
+
+# Copyright (c) 2008, Matthias Kretz, <kretz@kde.org>
+# Copyright (c) 2009, Marcus Hufgard, <Marcus.Hufgard@hufgard.de>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+# Support PULSEAUDIO_MINIMUM_VERSION for compatibility:
+if(NOT PulseAudio_FIND_VERSION)
+  set(PulseAudio_FIND_VERSION "${PULSEAUDIO_MINIMUM_VERSION}")
+endif(NOT PulseAudio_FIND_VERSION)
+
+# the minimum version of PulseAudio we require
+if(NOT PulseAudio_FIND_VERSION)
+  set(PulseAudio_FIND_VERSION "0.9.9")
+endif(NOT PulseAudio_FIND_VERSION)
+
+if (NOT WIN32)
+   include(FindPkgConfig)
+   pkg_check_modules(PC_PULSEAUDIO QUIET libpulse>=${PulseAudio_FIND_VERSION})
+   pkg_check_modules(PC_PULSEAUDIO_MAINLOOP QUIET libpulse-mainloop-glib)
+endif (NOT WIN32)
+
+find_path(PULSEAUDIO_INCLUDE_DIR pulse/pulseaudio.h
+   HINTS
+   ${PC_PULSEAUDIO_INCLUDEDIR}
+   ${PC_PULSEAUDIO_INCLUDE_DIRS}
+   )
+
+find_library(PULSEAUDIO_LIBRARY NAMES pulse libpulse
+   HINTS
+   ${PC_PULSEAUDIO_LIBDIR}
+   ${PC_PULSEAUDIO_LIBRARY_DIRS}
+   )
+
+find_library(PULSEAUDIO_MAINLOOP_LIBRARY NAMES pulse-mainloop pulse-mainloop-glib libpulse-mainloop-glib
+   HINTS
+   ${PC_PULSEAUDIO_LIBDIR}
+   ${PC_PULSEAUDIO_LIBRARY_DIRS}
+   )
+
+# Store the version number in the cache, so we don't have to search every time again:
+if (PULSEAUDIO_INCLUDE_DIR  AND NOT  PULSEAUDIO_VERSION)
+
+   # get PulseAudio's version from its version.h, and compare it with our minimum version
+   file(STRINGS "${PULSEAUDIO_INCLUDE_DIR}/pulse/version.h" pulse_version_h
+        REGEX ".*pa_get_headers_version\\(\\).*"
+        )
+   string(REGEX REPLACE ".*pa_get_headers_version\\(\\)\ \\(\"([0-9]+\\.[0-9]+\\.[0-9]+)[^\"]*\"\\).*" "\\1"
+                         _PULSEAUDIO_VERSION "${pulse_version_h}")
+
+   set(PULSEAUDIO_VERSION "${_PULSEAUDIO_VERSION}" CACHE STRING "Version number of PulseAudio" FORCE)
+endif (PULSEAUDIO_INCLUDE_DIR  AND NOT  PULSEAUDIO_VERSION)
+
+# Use the new extended syntax of find_package_handle_standard_args(), which also handles version checking:
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(PulseAudio REQUIRED_VARS PULSEAUDIO_LIBRARY PULSEAUDIO_INCLUDE_DIR
+                                             VERSION_VAR PULSEAUDIO_VERSION )
+
+mark_as_advanced(PULSEAUDIO_INCLUDE_DIR PULSEAUDIO_LIBRARY PULSEAUDIO_MAINLOOP_LIBRARY)

+ 1 - 0
README.md

@@ -34,6 +34,7 @@ Wallpaper Engine is a software designed by [Kristjan Skutta](https://store.steam
 - GLUT
 - FreeImage
 - MPV
+- PulseAudio
 
 ## Commands
 ```

+ 9 - 3
src/WallpaperEngine/Application/CApplicationContext.cpp

@@ -25,6 +25,7 @@ struct option long_options[] = {
     { "screenshot", required_argument,   nullptr, 'c' },
     { "list-properties", no_argument,    nullptr, 'l' },
     { "set-property", required_argument, nullptr, 'o' },
+    { "noautomute", no_argument,         nullptr, 'm' },
     { nullptr, 0,                        nullptr, 0 }
 };
 
@@ -66,7 +67,8 @@ CApplicationContext::CApplicationContext (int argc, char* argv[])
         .audio =
         {
             .enabled = true,
-            .volume = 127,
+            .volume = 15,
+            .automute = true
         },
         .screenshot =
         {
@@ -80,7 +82,7 @@ CApplicationContext::CApplicationContext (int argc, char* argv[])
 
     std::string lastScreen;
 
-    while ((c = getopt_long (argc, argv, "b:r:p:d:shf:a:w:", long_options, nullptr)) != -1)
+    while ((c = getopt_long (argc, argv, "b:r:p:d:shf:a:w:m", long_options, nullptr)) != -1)
     {
         switch (c)
         {
@@ -170,9 +172,12 @@ CApplicationContext::CApplicationContext (int argc, char* argv[])
 
         case 'c':
             this->settings.screenshot.take = true;
-                this->settings.screenshot.path = stringPathFixes (optarg);
+            this->settings.screenshot.path = stringPathFixes (optarg);
             break;
 
+        case 'm':
+            this->settings.audio.automute = false;
+            break;
         default:
             sLog.out ("Default on path parsing: ", optarg);
             break;
@@ -259,6 +264,7 @@ void CApplicationContext::printHelp (const char* route)
     sLog.out ("options:");
     sLog.out ("\t--silent\t\t\t\t\tMutes all the sound the wallpaper might produce");
     sLog.out ("\t--volume <amount>\t\t\tSets the volume for all the sounds in the background");
+    sLog.out ("\t--noautomute\t\t\t\tDisables the automute when an app is playing sound");
     sLog.out ("\t--screen-root <screen name>\tDisplay as screen's background");
     sLog.out ("\t--window <geometry>\tRuns in window mode, geometry has to be XxYxWxH and sets the position and size of the window");
     sLog.out ("\t--fps <maximum-fps>\t\t\tLimits the FPS to the given number, useful to keep battery consumption low");

+ 2 - 0
src/WallpaperEngine/Application/CApplicationContext.h

@@ -78,6 +78,8 @@ namespace WallpaperEngine::Application
                 bool enabled;
                 /** Sound volume (0-128) */
                 int volume;
+                /** If the audio must be muted if something else is playing sound */
+                bool automute;
             } audio;
 
             /**

+ 4 - 1
src/WallpaperEngine/Application/CWallpaperApplication.cpp

@@ -11,6 +11,7 @@
 #include "WallpaperEngine/Render/Drivers/Output/CX11Output.h"
 #include "WallpaperEngine/Application/CApplicationState.h"
 #include "WallpaperEngine/Render/Drivers/Detectors/CX11FullScreenDetector.h"
+#include "WallpaperEngine/Audio/Detectors/CPulseAudioPlayingDetector.h"
 
 #include <unistd.h>
 
@@ -265,8 +266,10 @@ namespace WallpaperEngine::Application
 
     void CWallpaperApplication::show ()
     {
+        // audio playing detector
+        WallpaperEngine::Audio::Detectors::CPulseAudioPlayingDetector audioDetector (this->m_context);
         // initialize sdl audio driver
-        WallpaperEngine::Audio::Drivers::CSDLAudioDriver audioDriver (this->m_context);
+        WallpaperEngine::Audio::Drivers::CSDLAudioDriver audioDriver (this->m_context, audioDetector);
         // initialize audio context
         WallpaperEngine::Audio::CAudioContext audioContext (audioDriver);
         // initialize OpenGL driver

+ 14 - 0
src/WallpaperEngine/Audio/Detectors/CAudioPlayingDetector.cpp

@@ -0,0 +1,14 @@
+#include "CAudioPlayingDetector.h"
+
+namespace WallpaperEngine::Audio::Detectors
+{
+    CAudioPlayingDetector::CAudioPlayingDetector (Application::CApplicationContext& appContext) :
+        m_applicationContext (appContext)
+    {
+    }
+
+    Application::CApplicationContext& CAudioPlayingDetector::getApplicationContext ()
+    {
+        return this->m_applicationContext;
+    }
+}

+ 29 - 0
src/WallpaperEngine/Audio/Detectors/CAudioPlayingDetector.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include "WallpaperEngine/Application/CApplicationContext.h"
+
+namespace WallpaperEngine::Application
+{
+    class CApplicationContext;
+}
+
+namespace WallpaperEngine::Audio::Detectors
+{
+    class CAudioPlayingDetector
+    {
+    public:
+        CAudioPlayingDetector (Application::CApplicationContext& appContext);
+
+        /**
+         * @return If any kind of sound is currently playing on the default audio device
+         */
+        virtual bool anythingPlaying () = 0;
+        /**
+         * @return The application context using this detector
+         */
+        [[nodiscard]] Application::CApplicationContext& getApplicationContext ();
+
+    private:
+        Application::CApplicationContext& m_applicationContext;
+    };
+}

+ 87 - 0
src/WallpaperEngine/Audio/Detectors/CPulseAudioPlayingDetector.cpp

@@ -0,0 +1,87 @@
+#include "CPulseAudioPlayingDetector.h"
+#include "WallpaperEngine/Logging/CLog.h"
+
+namespace WallpaperEngine::Audio::Detectors
+{
+    void sinkInputInfoCallback (pa_context* context, const pa_sink_input_info* info, int eol, void* userdata)
+    {
+        auto* detector = static_cast <CPulseAudioPlayingDetector*> (userdata);
+
+        if (info == nullptr)
+            return;
+
+        if (info->proplist == nullptr)
+            return;
+
+        // get processid
+        const char* value = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_PROCESS_ID);
+
+        if (atoi (value) != getpid () && pa_cvolume_avg (&info->volume) != PA_VOLUME_MUTED)
+            detector->setIsPlaying (true);
+    }
+
+    void defaultSinkInfoCallback (pa_context* context, const pa_server_info* info, void* userdata)
+    {
+        if (info == nullptr)
+            return;
+
+        pa_operation* op = pa_context_get_sink_input_info_list (context, sinkInputInfoCallback, userdata);
+
+        pa_operation_unref (op);
+    }
+
+    CPulseAudioPlayingDetector::CPulseAudioPlayingDetector (Application::CApplicationContext& appContext) :
+        CAudioPlayingDetector (appContext),
+        m_mainloop (nullptr),
+        m_mainloopApi (nullptr),
+        m_context (nullptr),
+        m_isPlaying (false)
+    {
+        this->m_mainloop = pa_mainloop_new ();
+        this->m_mainloopApi = pa_mainloop_get_api (this->m_mainloop);
+        this->m_context = pa_context_new (this->m_mainloopApi, "wallpaperengine");
+
+        pa_context_connect (this->m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr);
+
+        // lock until pulseaudio allows connection
+        while (pa_context_get_state (this->m_context) != PA_CONTEXT_READY)
+            pa_mainloop_iterate (this->m_mainloop, 1, nullptr);
+    }
+
+    CPulseAudioPlayingDetector::~CPulseAudioPlayingDetector ()
+    {
+        if (this->m_context)
+        {
+            pa_context_disconnect (this->m_context);
+            pa_context_unref (this->m_context);
+        }
+
+        if (this->m_mainloop)
+            pa_mainloop_free (this->m_mainloop);
+    }
+
+    void CPulseAudioPlayingDetector::setIsPlaying (bool newState)
+    {
+        this->m_isPlaying = newState;
+    }
+
+    bool CPulseAudioPlayingDetector::anythingPlaying ()
+    {
+        if (!this->getApplicationContext ().settings.audio.automute)
+            return false;
+
+        // reset playing state
+        this->setIsPlaying (false);
+
+        // start discovery of sinks
+        pa_operation* op = pa_context_get_server_info (this->m_context, defaultSinkInfoCallback, (void*) this);
+
+        // wait until all the operations are done
+        while (pa_operation_get_state (op) == PA_OPERATION_RUNNING)
+            pa_mainloop_iterate (this->m_mainloop, 1, nullptr);
+
+        pa_operation_unref (op);
+
+        return this->m_isPlaying;
+    }
+}

+ 27 - 0
src/WallpaperEngine/Audio/Detectors/CPulseAudioPlayingDetector.h

@@ -0,0 +1,27 @@
+#pragma once
+
+#include <condition_variable>
+#include <mutex>
+#include "CAudioPlayingDetector.h"
+#include <pulse/pulseaudio.h>
+
+namespace WallpaperEngine::Audio::Detectors
+{
+    class CPulseAudioPlayingDetector : public CAudioPlayingDetector
+    {
+    public:
+        explicit CPulseAudioPlayingDetector (Application::CApplicationContext& appContext);
+        ~CPulseAudioPlayingDetector ();
+
+        [[nodiscard]] bool anythingPlaying () override;
+        void setIsPlaying (bool newState);
+
+    private:
+        bool m_isPlaying;
+
+        pa_mainloop* m_mainloop;
+        pa_mainloop_api* m_mainloopApi;
+        pa_context* m_context;
+        pa_operation* m_operation;
+    };
+}

+ 7 - 2
src/WallpaperEngine/Audio/Drivers/CAudioDriver.cpp

@@ -2,8 +2,9 @@
 
 namespace WallpaperEngine::Audio::Drivers
 {
-    CAudioDriver::CAudioDriver (Application::CApplicationContext& applicationContext) :
-        m_applicationContext (applicationContext)
+    CAudioDriver::CAudioDriver (Application::CApplicationContext& applicationContext, Detectors::CAudioPlayingDetector& detector) :
+        m_applicationContext (applicationContext),
+        m_detector (detector)
     {
     }
 
@@ -11,4 +12,8 @@ namespace WallpaperEngine::Audio::Drivers
     {
         return this->m_applicationContext;
     }
+    Detectors::CAudioPlayingDetector& CAudioDriver::getAudioDetector ()
+    {
+        return this->m_detector;
+    }
 }

+ 13 - 12
src/WallpaperEngine/Audio/Drivers/CAudioDriver.h

@@ -2,30 +2,26 @@
 
 #include <vector>
 
+#include "WallpaperEngine/Audio/Detectors/CAudioPlayingDetector.h"
 #include "WallpaperEngine/Application/CApplicationContext.h"
 #include "WallpaperEngine/Audio/CAudioStream.h"
 
-namespace WallpaperEngine::Audio
-{
-    class CAudioStream;
-}
-
-namespace WallpaperEngine::Application
-{
-    class CApplicationContext;
-}
-
 namespace WallpaperEngine
 {
     namespace Application
     {
-
+        class CApplicationContext;
     }
 
     namespace Audio
     {
         class CAudioStream;
 
+        namespace Detectors
+        {
+            class CAudioPlayingDetector;
+        }
+
         namespace Drivers
         {
             /**
@@ -34,7 +30,7 @@ namespace WallpaperEngine
             class CAudioDriver
             {
             public:
-                explicit CAudioDriver (Application::CApplicationContext& applicationContext);
+                explicit CAudioDriver (Application::CApplicationContext& applicationContext, Detectors::CAudioPlayingDetector& detector);
 
                 /**
                  * Registers the given stream in the driver for playing
@@ -61,9 +57,14 @@ namespace WallpaperEngine
                  * @return The application context under which the audio driver is initialized
                  */
                 Application::CApplicationContext& getApplicationContext ();
+                /**
+                 * @return The audio playing detector to use to stop playing sound when something else starts playing
+                 */
+                [[nodiscard]] Detectors::CAudioPlayingDetector& getAudioDetector ();
 
             private:
                 Application::CApplicationContext& m_applicationContext;
+                Detectors::CAudioPlayingDetector& m_detector;
             };
         }
     }

+ 6 - 2
src/WallpaperEngine/Audio/Drivers/CSDLAudioDriver.cpp

@@ -13,6 +13,10 @@ void audio_callback (void* userdata, uint8_t* streamData, int length)
 
     memset (streamData, 0, length);
 
+    // if audio is playing do not do anything here!
+    if (driver->getAudioDetector ().anythingPlaying ())
+        return;
+
     for (const auto& buffer : driver->getStreams ())
     {
         uint8_t* streamDataPointer = streamData;
@@ -70,8 +74,8 @@ void audio_callback (void* userdata, uint8_t* streamData, int length)
     }
 }
 
-CSDLAudioDriver::CSDLAudioDriver (Application::CApplicationContext& applicationContext) :
-    CAudioDriver (applicationContext),
+CSDLAudioDriver::CSDLAudioDriver (Application::CApplicationContext& applicationContext, Detectors::CAudioPlayingDetector& detector) :
+    CAudioDriver (applicationContext, detector),
     m_initialized (false),
     m_audioSpec ()
 {

+ 1 - 1
src/WallpaperEngine/Audio/Drivers/CSDLAudioDriver.h

@@ -29,7 +29,7 @@ namespace WallpaperEngine::Audio::Drivers
     class CSDLAudioDriver : public CAudioDriver
     {
     public:
-        CSDLAudioDriver (Application::CApplicationContext& applicationContext);
+        CSDLAudioDriver (Application::CApplicationContext& applicationContext, Detectors::CAudioPlayingDetector& detector);
         ~CSDLAudioDriver ();
 
         /** @inheritdoc */

+ 3 - 3
src/WallpaperEngine/Render/Shaders/Compiler.h

@@ -83,15 +83,15 @@ namespace WallpaperEngine::Render::Shaders
         /**
          * @return The list of parameters available for this shader with their default values
          */
-        const std::vector <Variables::CShaderVariable*>& getParameters () const;
+        [[nodiscard]] const std::vector <Variables::CShaderVariable*>& getParameters () const;
         /**
          * @return The list of combos available for this shader after compilation
          */
-        std::map <std::string, int>* getCombos () const;
+        [[nodiscard]] std::map <std::string, int>* getCombos () const;
         /**
          * @return The list of textures inferred from the shader's code
          */
-        const std::map <int, std::string>& getTextures () const;
+        [[nodiscard]] const std::map <int, std::string>& getTextures () const;
 
     private:
         /**