Added sound playback detection to mute when something else plays audio

Signed-off-by: Alexis Maiquez <almamu@almamu.com>
This commit is contained in:
Alexis Maiquez 2023-03-24 02:36:42 +01:00
parent cc7ec0561d
commit 978f56cdca
16 changed files with 283 additions and 25 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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)

View File

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

View File

@ -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");

View File

@ -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;
/**

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
};
}

View File

@ -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;
}
}

View File

@ -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;
};
}

View File

@ -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;
}
}

View File

@ -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;
};
}
}

View File

@ -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 ()
{

View File

@ -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 */

View File

@ -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:
/**