From 8dcbd77bf25e80ec46b8192c22b7532794350a67 Mon Sep 17 00:00:00 2001 From: fryshorts Date: Wed, 19 Nov 2014 00:50:32 +0100 Subject: [PATCH 1/2] 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 From 3fea6997a78027e0fa8647a9a5e5fd63bc9301e1 Mon Sep 17 00:00:00 2001 From: fryshorts Date: Wed, 19 Nov 2014 16:41:07 +0100 Subject: [PATCH 2/2] obs: Use new fader from libobs for volume control This replaces the code for volume control with a newer version that uses the new fader implementation in order to control the fader/slider position and source volume. The volume label will now indicate the gain in dB instead of percent, mainly because it looks cool and is easy to do. Due to libobs saving the multiplier option for the source instead of the slider/fader positon, existing volume levels will (mostly) stay the same with only the slider beeing at a different position. This is of course within the resolution of the slider (100 steps). --- obs/volume-control.cpp | 46 ++++++++++++++++++------------------------ obs/volume-control.hpp | 4 ++-- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/obs/volume-control.cpp b/obs/volume-control.cpp index d00aa3b31..e14d9f10d 100644 --- a/obs/volume-control.cpp +++ b/obs/volume-control.cpp @@ -39,10 +39,10 @@ static inline float DBToLinear(float db_full) void VolControl::OBSVolumeChanged(void *data, calldata_t *calldata) { + Q_UNUSED(calldata); VolControl *volControl = static_cast(data); - int vol = (int)(calldata_float(calldata, "volume") * 100.0f + 0.5f); - QMetaObject::invokeMethod(volControl, "VolumeChanged", Q_ARG(int, vol)); + QMetaObject::invokeMethod(volControl, "VolumeChanged"); } void VolControl::OBSVolumeLevel(void *data, calldata_t *calldata) @@ -58,11 +58,9 @@ void VolControl::OBSVolumeLevel(void *data, calldata_t *calldata) Q_ARG(float, peakHold)); } -void VolControl::VolumeChanged(int vol) +void VolControl::VolumeChanged() { - signalChanged = false; - slider->setValue(vol); - signalChanged = true; + slider->setValue((int) (obs_fader_get_deflection(obs_fader) * 100.0f)); } void VolControl::VolumeLevel(float mag, float peak, float peakHold) @@ -85,17 +83,9 @@ void VolControl::VolumeLevel(float mag, float peak, float peakHold) void VolControl::SliderChanged(int vol) { - if (signalChanged) { - signal_handler_disconnect(obs_source_get_signal_handler(source), - "volume", OBSVolumeChanged, this); - - obs_source_set_volume(source, float(vol)*0.01f); - - signal_handler_connect(obs_source_get_signal_handler(source), - "volume", OBSVolumeChanged, this); - } - - volLabel->setText(QString::number(vol)); + obs_fader_set_deflection(obs_fader, float(vol) * 0.01f); + volLabel->setText(QString::number(obs_fader_get_db(obs_fader), 'f', 1) + .append(" dB")); } QString VolControl::GetName() const @@ -110,14 +100,13 @@ void VolControl::SetName(const QString &newName) VolControl::VolControl(OBSSource source_) : source (source_), - signalChanged (true), lastMeterTime (0), levelTotal (0.0f), - levelCount (0.0f) + levelCount (0.0f), + obs_fader (obs_fader_create(OBS_FADER_CUBIC)) { QVBoxLayout *mainLayout = new QVBoxLayout(); QHBoxLayout *textLayout = new QHBoxLayout(); - int vol = int(obs_source_get_volume(source) * 100.0f); nameLabel = new QLabel(); volLabel = new QLabel(); @@ -129,11 +118,10 @@ VolControl::VolControl(OBSSource source_) nameLabel->setText(obs_source_get_name(source)); nameLabel->setFont(font); - volLabel->setText(QString::number(vol)); volLabel->setFont(font); slider->setMinimum(0); slider->setMaximum(100); - slider->setValue(vol); + // slider->setMaximumHeight(13); textLayout->setContentsMargins(0, 0, 0, 0); @@ -150,23 +138,29 @@ VolControl::VolControl(OBSSource source_) setLayout(mainLayout); - signal_handler_connect(obs_source_get_signal_handler(source), - "volume", OBSVolumeChanged, this); + signal_handler_connect(obs_fader_get_signal_handler(obs_fader), + "volume_changed", OBSVolumeChanged, this); signal_handler_connect(obs_source_get_signal_handler(source), "volume_level", OBSVolumeLevel, this); QWidget::connect(slider, SIGNAL(valueChanged(int)), this, SLOT(SliderChanged(int))); + + obs_fader_attach_source(obs_fader, source); + /* Call volume changed once to init the slider position and label */ + VolumeChanged(); } VolControl::~VolControl() { - signal_handler_disconnect(obs_source_get_signal_handler(source), - "volume", OBSVolumeChanged, this); + signal_handler_disconnect(obs_fader_get_signal_handler(obs_fader), + "volume_changed", OBSVolumeChanged, this); signal_handler_disconnect(obs_source_get_signal_handler(source), "volume_level", OBSVolumeLevel, this); + + obs_fader_destroy(obs_fader); } VolumeMeter::VolumeMeter(QWidget *parent) diff --git a/obs/volume-control.hpp b/obs/volume-control.hpp index 0ded938a3..87c03ee57 100644 --- a/obs/volume-control.hpp +++ b/obs/volume-control.hpp @@ -32,16 +32,16 @@ private: QLabel *volLabel; VolumeMeter *volMeter; QSlider *slider; - bool signalChanged; uint64_t lastMeterTime; float levelTotal; float levelCount; + obs_fader_t *obs_fader; static void OBSVolumeChanged(void *param, calldata_t *calldata); static void OBSVolumeLevel(void *data, calldata_t *calldata); private slots: - void VolumeChanged(int vol); + void VolumeChanged(); void VolumeLevel(float mag, float peak, float peakHold); void SliderChanged(int vol);