From 8dcbd77bf25e80ec46b8192c22b7532794350a67 Mon Sep 17 00:00:00 2001 From: fryshorts Date: Wed, 19 Nov 2014 00:50:32 +0100 Subject: [PATCH] libobs: Add audio control functions This adds a new library of audio control functions mainly for the use in GUIS. For now it includes an implementation of a software fader that can be attached to sources in order to easily control the volume. The fader can translate between fader-position, volume in dB and multiplier with a configurable mapping function. Currently only a cubic mapping (mul = fader_pos ^ 3) is included, but different mappings can easily be added. Due to libobs saving/restoring the source volume from the multiplier, the volume levels for existing source will stay the same, and live changing of the mapping will work without changing the source volume. --- libobs/CMakeLists.txt | 2 + libobs/obs-audio-controls.c | 300 ++++++++++++++++++++++++++++++++++++ libobs/obs-audio-controls.h | 142 +++++++++++++++++ libobs/obs.h | 3 + 4 files changed, 447 insertions(+) create mode 100644 libobs/obs-audio-controls.c create mode 100644 libobs/obs-audio-controls.h diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index 2f5120f05..e92a7607f 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -190,6 +190,7 @@ set(libobs_util_HEADERS set(libobs_libobs_SOURCES ${libobs_PLATFORM_SOURCES} + obs-audio-controls.c obs-avc.c obs-encoder.c obs-service.c @@ -204,6 +205,7 @@ set(libobs_libobs_SOURCES obs-scene.c obs-video.c) set(libobs_libobs_HEADERS + obs-audio-controls.h obs-defs.h obs-avc.h obs-encoder.h diff --git a/libobs/obs-audio-controls.c b/libobs/obs-audio-controls.c new file mode 100644 index 000000000..d69adf214 --- /dev/null +++ b/libobs/obs-audio-controls.c @@ -0,0 +1,300 @@ +/* +Copyright (C) 2014 by Leonhard Oelke + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include + +#include "util/threading.h" +#include "util/bmem.h" +#include "obs.h" +#include "obs-internal.h" + +#include "obs-audio-controls.h" + +typedef float (*obs_fader_conversion_t)(const float val); + +struct obs_fader { + pthread_mutex_t mutex; + signal_handler_t *signals; + obs_fader_conversion_t def_to_db; + obs_fader_conversion_t db_to_def; + obs_source_t *source; + enum obs_fader_type type; + float max_db; + float min_db; + float cur_db; + bool ignore_next_signal; +}; + +static const char *fader_signals[] = { + "void volume_changed(ptr fader, float db)", + NULL +}; + +static inline float mul_to_db(const float mul) +{ + return (mul == 0.0f) ? -INFINITY : 20.0f * log10f(mul); +} + +static inline float db_to_mul(const float db) +{ + return (db == -INFINITY) ? 0.0f : powf(10.0f, db / 20.0f); +} + +static float cubic_def_to_db(const float def) +{ + if (def == 1.0f) + return 0.0f; + else if (def <= 0.0f) + return -INFINITY; + + return mul_to_db(def * def * def); +} + +static float cubic_db_to_def(const float db) +{ + if (db == 0.0f) + return 1.0f; + else if (db == -INFINITY) + return 0.0f; + + return cbrtf(db_to_mul(db)); +} + +static void signal_volume_changed(signal_handler_t *sh, + struct obs_fader *fader, const float db) +{ + struct calldata data; + + calldata_init(&data); + + calldata_set_ptr (&data, "fader", fader); + calldata_set_float(&data, "db", db); + + signal_handler_signal(sh, "volume_changed", &data); + + calldata_free(&data); +} + +static void fader_source_volume_changed(void *vptr, calldata_t *calldata) +{ + struct obs_fader *fader = (struct obs_fader *) vptr; + + pthread_mutex_lock(&fader->mutex); + + if (fader->ignore_next_signal) { + fader->ignore_next_signal = false; + pthread_mutex_unlock(&fader->mutex); + return; + } + + signal_handler_t *sh = fader->signals; + const float mul = calldata_float(calldata, "volume"); + const float db = mul_to_db(mul); + fader->cur_db = db; + + pthread_mutex_unlock(&fader->mutex); + + signal_volume_changed(sh, fader, db); +} + +static void fader_source_destroyed(void *vptr, calldata_t *calldata) +{ + UNUSED_PARAMETER(calldata); + struct obs_fader *fader = (struct obs_fader *) vptr; + + obs_fader_detach_source(fader); +} + +obs_fader_t *obs_fader_create(enum obs_fader_type type) +{ + struct obs_fader *fader = bzalloc(sizeof(struct obs_fader)); + if (!fader) + return NULL; + + pthread_mutex_init_value(&fader->mutex); + if (pthread_mutex_init(&fader->mutex, NULL) != 0) + goto fail; + fader->signals = signal_handler_create(); + if (!fader->signals) + goto fail; + if (!signal_handler_add_array(fader->signals, fader_signals)) + goto fail; + + switch(type) { + case OBS_FADER_CUBIC: + fader->def_to_db = cubic_def_to_db; + fader->db_to_def = cubic_db_to_def; + fader->max_db = 0.0f; + fader->min_db = -INFINITY; + break; + default: + goto fail; + break; + } + fader->type = type; + + return fader; +fail: + obs_fader_destroy(fader); + return NULL; +} + +void obs_fader_destroy(obs_fader_t *fader) +{ + if (!fader) + return; + + obs_fader_detach_source(fader); + signal_handler_destroy(fader->signals); + pthread_mutex_destroy(&fader->mutex); + + bfree(fader); +} + +bool obs_fader_set_db(obs_fader_t *fader, const float db) +{ + if (!fader) + return false; + + pthread_mutex_lock(&fader->mutex); + + bool clamped = false; + fader->cur_db = db; + + if (fader->cur_db > fader->max_db) { + fader->cur_db = fader->max_db; + clamped = true; + } + if (fader->cur_db < fader->min_db) { + fader->cur_db = -INFINITY; + clamped = true; + } + + fader->ignore_next_signal = true; + obs_source_t *src = fader->source; + const float mul = db_to_mul(fader->cur_db); + + pthread_mutex_unlock(&fader->mutex); + + if (src) + obs_source_set_volume(src, mul); + + return !clamped; +} + +float obs_fader_get_db(obs_fader_t *fader) +{ + if (!fader) + return 0.0f; + + pthread_mutex_lock(&fader->mutex); + const float db = fader->cur_db; + pthread_mutex_unlock(&fader->mutex); + + return db; +} + +bool obs_fader_set_deflection(obs_fader_t *fader, const float def) +{ + if (!fader) + return false; + + return obs_fader_set_db(fader, fader->def_to_db(def)); +} + +float obs_fader_get_deflection(obs_fader_t *fader) +{ + if (!fader) + return 0.0f; + + pthread_mutex_lock(&fader->mutex); + const float def = fader->db_to_def(fader->cur_db); + pthread_mutex_unlock(&fader->mutex); + + return def; +} + +bool obs_fader_set_mul(obs_fader_t *fader, const float mul) +{ + if (!fader) + return false; + + return obs_fader_set_db(fader, mul_to_db(mul)); +} + +float obs_fader_get_mul(obs_fader_t *fader) +{ + if (!fader) + return 0.0f; + + pthread_mutex_lock(&fader->mutex); + const float mul = db_to_mul(fader->cur_db); + pthread_mutex_unlock(&fader->mutex); + + return mul; +} + +bool obs_fader_attach_source(obs_fader_t *fader, obs_source_t *source) +{ + if (!fader || !source) + return false; + + obs_fader_detach_source(fader); + + pthread_mutex_lock(&fader->mutex); + + signal_handler_t *sh = obs_source_get_signal_handler(source); + signal_handler_connect(sh, "volume", + fader_source_volume_changed, fader); + signal_handler_connect(sh, "destroy", + fader_source_destroyed, fader); + + fader->source = source; + fader->cur_db = mul_to_db(obs_source_get_volume(source)); + + pthread_mutex_unlock(&fader->mutex); + + return true; +} + +void obs_fader_detach_source(obs_fader_t *fader) +{ + if (!fader) + return; + + pthread_mutex_lock(&fader->mutex); + + if (!fader->source) + goto exit; + + signal_handler_t *sh = obs_source_get_signal_handler(fader->source); + signal_handler_disconnect(sh, "volume", + fader_source_volume_changed, fader); + signal_handler_disconnect(sh, "destroy", + fader_source_destroyed, fader); + + fader->source = NULL; + +exit: + pthread_mutex_unlock(&fader->mutex); +} + +signal_handler_t *obs_fader_get_signal_handler(obs_fader_t *fader) +{ + return (fader) ? fader->signals : NULL; +} + diff --git a/libobs/obs-audio-controls.h b/libobs/obs-audio-controls.h new file mode 100644 index 000000000..e0eeefe9a --- /dev/null +++ b/libobs/obs-audio-controls.h @@ -0,0 +1,142 @@ +/* +Copyright (C) 2014 by Leonhard Oelke + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#pragma once + +#include "obs.h" + +/** + * @file + * @brief header for audio controls + * + * @brief Audio controls for use in GUIs + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Fader types + */ +enum obs_fader_type { + /** + * @brief A simple cubic fader for controlling audio levels + * + * This is a very common type of software fader since it yields good + * results while being quite performant. + * The input value is mapped to mul values with the simple formula x^3. + */ + OBS_FADER_CUBIC +}; + +/** + * @brief Create a fader + * @param type the type of the fader + * @return pointer to the fader object + * + * A fader object is used to map input values from a gui element to dB and + * subsequently multiplier values used by libobs to mix audio. + * The current "position" of the fader is internally stored as dB value. + */ +EXPORT obs_fader_t *obs_fader_create(enum obs_fader_type type); + +/** + * @brief Destroy a fader + * @param fader pointer to the fader object + * + * Destroy the fader and free all related data + */ +EXPORT void obs_fader_destroy(obs_fader_t *fader); + +/** + * @brief Set the fader dB value + * @param fader pointer to the fader object + * @param db new dB value + * @return true if value was set without clamping + */ +EXPORT bool obs_fader_set_db(obs_fader_t *fader, const float db); + +/** + * @brief Get the current fader dB value + * @param fader pointer to the fader object + * @return current fader dB value + */ +EXPORT float obs_fader_get_db(obs_fader_t *fader); + +/** + * @brief Set the fader value from deflection + * @param fader pointer to the fader object + * @param def new deflection + * @return true if value was set without clamping + * + * This sets the new fader value from the supplied deflection, in case the + * resulting value was clamped due to limits this function will return false. + * The deflection is typically in the range [0.0, 1.0] but may be higher in + * order to provide some amplification. In order for this to work the high dB + * limit has to be set. + */ +EXPORT bool obs_fader_set_deflection(obs_fader_t *fader, const float def); + +/** + * @brief Get the current fader deflection + * @param fader pointer to the fader object + * @return current fader deflection + */ +EXPORT float obs_fader_get_deflection(obs_fader_t *fader); + +/** + * @brief Set the fader value from multiplier + * @param fader pointer to the fader object + * @return true if the value was set without clamping + */ +EXPORT bool obs_fader_set_mul(obs_fader_t *fader, const float mul); + +/** + * @brief Get the current fader multiplier value + * @param fader pointer to the fader object + * @return current fader multiplier + */ +EXPORT float obs_fader_get_mul(obs_fader_t *fader); + +/** + * @brief Attach the fader to a source + * @param fader pointer to the fader object + * @param source pointer to the source object + * @return true on success + * + * When the fader is attached to a source it will automatically sync it's state + * to the volume of the source. + */ +EXPORT bool obs_fader_attach_source(obs_fader_t *fader, obs_source_t *source); + +/** + * @brief Detach the fader from the currently attached source + * @param fader pointer to the fader object + */ +EXPORT void obs_fader_detach_source(obs_fader_t *fader); + +/** + * @brief Get signal handler for the fader + * @param fader pointer to the fader object + * @return signal handler + */ +EXPORT signal_handler_t *obs_fader_get_signal_handler(obs_fader_t *fader); + +#ifdef __cplusplus +} +#endif diff --git a/libobs/obs.h b/libobs/obs.h index f5d88aa1d..04a7b4738 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -47,6 +47,7 @@ struct obs_output; struct obs_encoder; struct obs_service; struct obs_module; +struct obs_fader; typedef struct obs_display obs_display_t; typedef struct obs_view obs_view_t; @@ -57,11 +58,13 @@ typedef struct obs_output obs_output_t; typedef struct obs_encoder obs_encoder_t; typedef struct obs_service obs_service_t; typedef struct obs_module obs_module_t; +typedef struct obs_fader obs_fader_t; #include "obs-source.h" #include "obs-encoder.h" #include "obs-output.h" #include "obs-service.h" +#include "obs-audio-controls.h" /** * @file