From 46686ec556683a70341fef2ea2191913589223be Mon Sep 17 00:00:00 2001 From: fryshorts Date: Thu, 20 Nov 2014 21:08:26 +0100 Subject: [PATCH 1/4] libobs: Add volume meter object This adds a volume meter object to libobs that can be used by the GUI or plugins to convert the raw audio level data from sources to values that can easily be used to display the audio data. The volume meter object will use the same mapping functions as the fader object to map dB levels to a scale. --- libobs/obs-audio-controls.c | 180 ++++++++++++++++++++++++++++++++++++ libobs/obs-audio-controls.h | 47 ++++++++++ libobs/obs.h | 2 + 3 files changed, 229 insertions(+) diff --git a/libobs/obs-audio-controls.c b/libobs/obs-audio-controls.c index 08b825938..1c70f2df2 100644 --- a/libobs/obs-audio-controls.c +++ b/libobs/obs-audio-controls.c @@ -46,11 +46,27 @@ struct obs_fader { bool ignore_next_signal; }; +struct obs_volmeter { + pthread_mutex_t mutex; + signal_handler_t *signals; + obs_fader_conversion_t pos_to_db; + obs_fader_conversion_t db_to_pos; + obs_source_t *source; + enum obs_fader_type type; + float cur_db; +}; + static const char *fader_signals[] = { "void volume_changed(ptr fader, float db)", NULL }; +static const char *volmeter_signals[] = { + "void levels_updated(ptr volmeter, float level, " + "float magnitude, float peak)", + NULL +}; + static inline float mul_to_db(const float mul) { return (mul == 0.0f) ? -INFINITY : 20.0f * log10f(mul); @@ -96,6 +112,24 @@ static void signal_volume_changed(signal_handler_t *sh, calldata_free(&data); } +static void signal_levels_updated(signal_handler_t *sh, + struct obs_volmeter *volmeter, + const float level, const float magnitude, const float peak) +{ + struct calldata data; + + calldata_init(&data); + + calldata_set_ptr (&data, "volmeter", volmeter); + calldata_set_float(&data, "level", level); + calldata_set_float(&data, "magnitude", magnitude); + calldata_set_float(&data, "peak", peak); + + signal_handler_signal(sh, "levels_updated", &data); + + calldata_free(&data); +} + static void fader_source_volume_changed(void *vptr, calldata_t *calldata) { struct obs_fader *fader = (struct obs_fader *) vptr; @@ -118,6 +152,18 @@ static void fader_source_volume_changed(void *vptr, calldata_t *calldata) signal_volume_changed(sh, fader, db); } +static void volmeter_source_volume_changed(void *vptr, calldata_t *calldata) +{ + struct obs_volmeter *volmeter = (struct obs_volmeter *) vptr; + + pthread_mutex_lock(&volmeter->mutex); + + float mul = (float) calldata_float(calldata, "volume"); + volmeter->cur_db = mul_to_db(mul); + + pthread_mutex_unlock(&volmeter->mutex); +} + static void fader_source_destroyed(void *vptr, calldata_t *calldata) { UNUSED_PARAMETER(calldata); @@ -126,6 +172,37 @@ static void fader_source_destroyed(void *vptr, calldata_t *calldata) obs_fader_detach_source(fader); } +static void volmeter_source_volume_levels(void *vptr, calldata_t *calldata) +{ + struct obs_volmeter *volmeter = (struct obs_volmeter *) vptr; + + pthread_mutex_lock(&volmeter->mutex); + + float mul = db_to_mul(volmeter->cur_db); + + float level = (float) calldata_float(calldata, "level"); + float magnitude = (float) calldata_float(calldata, "magnitude"); + float peak = (float) calldata_float(calldata, "peak"); + + level = volmeter->db_to_pos(mul_to_db(level * mul)); + magnitude = volmeter->db_to_pos(mul_to_db(magnitude * mul)); + peak = volmeter->db_to_pos(mul_to_db(peak * mul)); + + signal_handler_t *sh = volmeter->signals; + + pthread_mutex_unlock(&volmeter->mutex); + + signal_levels_updated(sh, volmeter, level, magnitude, peak); +} + +static void volmeter_source_destroyed(void *vptr, calldata_t *calldata) +{ + UNUSED_PARAMETER(calldata); + struct obs_volmeter *volmeter = (struct obs_volmeter *) vptr; + + obs_volmeter_detach_source(volmeter); +} + obs_fader_t *obs_fader_create(enum obs_fader_type type) { struct obs_fader *fader = bzalloc(sizeof(struct obs_fader)); @@ -309,3 +386,106 @@ signal_handler_t *obs_fader_get_signal_handler(obs_fader_t *fader) return (fader) ? fader->signals : NULL; } +obs_volmeter_t *obs_volmeter_create(enum obs_fader_type type) +{ + struct obs_volmeter *volmeter = bzalloc(sizeof(struct obs_volmeter)); + if (!volmeter) + return NULL; + + pthread_mutex_init_value(&volmeter->mutex); + if (pthread_mutex_init(&volmeter->mutex, NULL) != 0) + goto fail; + volmeter->signals = signal_handler_create(); + if (!volmeter->signals) + goto fail; + if (!signal_handler_add_array(volmeter->signals, volmeter_signals)) + goto fail; + + /* set conversion functions */ + switch(type) { + case OBS_FADER_CUBIC: + volmeter->pos_to_db = cubic_def_to_db; + volmeter->db_to_pos = cubic_db_to_def; + break; + default: + goto fail; + break; + } + volmeter->type = type; + + return volmeter; +fail: + obs_volmeter_destroy(volmeter); + return NULL; +} + +void obs_volmeter_destroy(obs_volmeter_t *volmeter) +{ + if (!volmeter) + return; + + obs_volmeter_detach_source(volmeter); + signal_handler_destroy(volmeter->signals); + pthread_mutex_destroy(&volmeter->mutex); + + bfree(volmeter); +} + +bool obs_volmeter_attach_source(obs_volmeter_t *volmeter, obs_source_t *source) +{ + signal_handler_t *sh; + + if (!volmeter || !source) + return false; + + obs_volmeter_detach_source(volmeter); + + pthread_mutex_lock(&volmeter->mutex); + + sh = obs_source_get_signal_handler(source); + signal_handler_connect(sh, "volume", + volmeter_source_volume_changed, volmeter); + signal_handler_connect(sh, "volume_level", + volmeter_source_volume_levels, volmeter); + signal_handler_connect(sh, "destroy", + volmeter_source_destroyed, volmeter); + + volmeter->source = source; + volmeter->cur_db = mul_to_db(obs_source_get_volume(source)); + + pthread_mutex_unlock(&volmeter->mutex); + + return true; +} + +void obs_volmeter_detach_source(obs_volmeter_t *volmeter) +{ + signal_handler_t *sh; + + if (!volmeter) + return; + + pthread_mutex_lock(&volmeter->mutex); + + if (!volmeter->source) + goto exit; + + sh = obs_source_get_signal_handler(volmeter->source); + signal_handler_disconnect(sh, "volume", + volmeter_source_volume_changed, volmeter); + signal_handler_disconnect(sh, "volume_level", + volmeter_source_volume_levels, volmeter); + signal_handler_disconnect(sh, "destroy", + volmeter_source_destroyed, volmeter); + + volmeter->source = NULL; + +exit: + pthread_mutex_unlock(&volmeter->mutex); +} + +signal_handler_t *obs_volmeter_get_signal_handler(obs_volmeter_t *volmeter) +{ + return (volmeter) ? volmeter->signals : NULL; +} + diff --git a/libobs/obs-audio-controls.h b/libobs/obs-audio-controls.h index e0eeefe9a..bead3dc45 100644 --- a/libobs/obs-audio-controls.h +++ b/libobs/obs-audio-controls.h @@ -137,6 +137,53 @@ EXPORT void obs_fader_detach_source(obs_fader_t *fader); */ EXPORT signal_handler_t *obs_fader_get_signal_handler(obs_fader_t *fader); +/** + * @brief Create a volume meter + * @param type the mapping type to use for the volume meter + * @return pointer to the volume meter object + * + * A volume meter object is used to prepare the sound levels reported by audio + * sources for display in a GUI. + * It will automatically take source volume into account and map the levels + * to a range [0.0f, 1.0f]. + */ +EXPORT obs_volmeter_t *obs_volmeter_create(enum obs_fader_type type); + +/** + * @brief Destroy a volume meter + * @param volmeter pointer to the volmeter object + * + * Destroy the volume meter and free all related data + */ +EXPORT void obs_volmeter_destroy(obs_volmeter_t *volmeter); + +/** + * @brief Attach the volume meter to a source + * @param volmeter pointer to the volume meter object + * @param source pointer to the source object + * @return true on success + * + * When the volume meter is attached to a source it will start to listen to + * volume updates on the source and after preparing the data emit its own + * signal. + */ +EXPORT bool obs_volmeter_attach_source(obs_volmeter_t *volmeter, + obs_source_t *source); + +/** + * @brief Detach the volume meter from the currently attached source + * @param volmeter pointer to the volume meter object + */ +EXPORT void obs_volmeter_detach_source(obs_volmeter_t *volmeter); + +/** + * @brief Get signal handler for the volume meter object + * @param volmeter pointer to the volume meter object + * @return signal handler + */ +EXPORT signal_handler_t *obs_volmeter_get_signal_handler( + obs_volmeter_t *volmeter); + #ifdef __cplusplus } #endif diff --git a/libobs/obs.h b/libobs/obs.h index 04a7b4738..7f7388bb4 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -48,6 +48,7 @@ struct obs_encoder; struct obs_service; struct obs_module; struct obs_fader; +struct obs_volmeter; typedef struct obs_display obs_display_t; typedef struct obs_view obs_view_t; @@ -59,6 +60,7 @@ 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; +typedef struct obs_volmeter obs_volmeter_t; #include "obs-source.h" #include "obs-encoder.h" From 119d77e1763a3c75ed50523549d02696b919964c Mon Sep 17 00:00:00 2001 From: fryshorts Date: Sun, 23 Nov 2014 22:42:37 +0100 Subject: [PATCH 2/4] libobs: Add IEC 60-268-18 compliant fader type This adds a new fader type that implements a position/dB mapping as specified in IEC 60-268-18. --- libobs/obs-audio-controls.c | 68 +++++++++++++++++++++++++++++++++++++ libobs/obs-audio-controls.h | 22 +++++++++++- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/libobs/obs-audio-controls.c b/libobs/obs-audio-controls.c index 1c70f2df2..2dce536f7 100644 --- a/libobs/obs-audio-controls.c +++ b/libobs/obs-audio-controls.c @@ -97,6 +97,64 @@ static float cubic_db_to_def(const float db) return cbrtf(db_to_mul(db)); } +static float iec_def_to_db(const float def) +{ + if (def == 1.0f) + return 0.0f; + else if (def <= 0.0f) + return -INFINITY; + + float db; + + if (def >= 0.75f) + db = (def - 1.0f) / 0.25f * 9.0f; + else if (def >= 0.5f) + db = (def - 0.75f) / 0.25f * 11.0f - 9.0f; + else if (def >= 0.3f) + db = (def - 0.5f) / 0.2f * 10.0f - 20.0f; + else if (def >= 0.15f) + db = (def - 0.3f) / 0.15f * 10.0f - 30.0f; + else if (def >= 0.075f) + db = (def - 0.15f) / 0.075f * 10.0f - 40.0f; + else if (def >= 0.025f) + db = (def - 0.075f) / 0.05f * 10.0f - 50.0f; + else if (def >= 0.001f) + db = (def - 0.025f) / 0.025f * 90.0f - 60.0f; + else + db = -INFINITY; + + return db; +} + +static float iec_db_to_def(const float db) +{ + if (db == 0.0f) + return 1.0f; + else if (db == -INFINITY) + return 0.0f; + + float def; + + if (db >= -9.0f) + def = (db + 9.0f) / 9.0f * 0.25f + 0.75f; + else if (db >= -20.0f) + def = (db + 20.0f) / 11.0f * 0.25f + 0.5f; + else if (db >= -30.0f) + def = (db + 30.0f) / 10.0f * 0.2f + 0.3f; + else if (db >= -40.0f) + def = (db + 40.0f) / 10.0f * 0.15f + 0.15f; + else if (db >= -50.0f) + def = (db + 50.0f) / 10.0f * 0.075f + 0.075f; + else if (db >= -60.0f) + def = (db + 60.0f) / 10.0f * 0.05f + 0.025f; + else if (db >= -114.0f) + def = (db + 150.0f) / 90.0f * 0.025f; + else + def = 0.0f; + + return def; +} + static void signal_volume_changed(signal_handler_t *sh, struct obs_fader *fader, const float db) { @@ -225,6 +283,12 @@ obs_fader_t *obs_fader_create(enum obs_fader_type type) fader->max_db = 0.0f; fader->min_db = -INFINITY; break; + case OBS_FADER_IEC: + fader->def_to_db = iec_def_to_db; + fader->db_to_def = iec_db_to_def; + fader->max_db = 0.0f; + fader->min_db = -INFINITY; + break; default: goto fail; break; @@ -407,6 +471,10 @@ obs_volmeter_t *obs_volmeter_create(enum obs_fader_type type) volmeter->pos_to_db = cubic_def_to_db; volmeter->db_to_pos = cubic_db_to_def; break; + case OBS_FADER_IEC: + volmeter->pos_to_db = iec_def_to_db; + volmeter->db_to_pos = iec_db_to_def; + break; default: goto fail; break; diff --git a/libobs/obs-audio-controls.h b/libobs/obs-audio-controls.h index bead3dc45..4f23395b6 100644 --- a/libobs/obs-audio-controls.h +++ b/libobs/obs-audio-controls.h @@ -41,7 +41,27 @@ enum obs_fader_type { * results while being quite performant. * The input value is mapped to mul values with the simple formula x^3. */ - OBS_FADER_CUBIC + OBS_FADER_CUBIC, + /** + * @brief A fader compliant to IEC 60-268-18 + * + * This type of fader has several segments with different slopes that + * map deflection linearly to dB values. The segments are defined as + * in the following table: + * + @code + Deflection | Volume + ------------------------------------------ + [ 100 %, 75 % ] | [ 0 dB, -9 dB ] + [ 75 %, 50 % ] | [ -9 dB, -20 dB ] + [ 50 %, 30 % ] | [ -20 dB, -30 dB ] + [ 30 %, 15 % ] | [ -30 dB, -40 dB ] + [ 15 %, 7.5 % ] | [ -40 dB, -50 dB ] + [ 7.5 %, 2.5 % ] | [ -50 dB, -60 dB ] + [ 2.5 %, 0 % ] | [ -60 dB, -inf dB ] + @endcode + */ + OBS_FADER_IEC }; /** From 1b034569f03bfd7025952dba5343459a5299772f Mon Sep 17 00:00:00 2001 From: fryshorts Date: Thu, 27 Nov 2014 21:01:26 +0100 Subject: [PATCH 3/4] libobs: Add Logarithmic fader type This adds the conversion function used by the old obs based on a logarithmic scale. --- libobs/obs-audio-controls.c | 40 +++++++++++++++++++++++++++++++++++++ libobs/obs-audio-controls.h | 6 +++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/libobs/obs-audio-controls.c b/libobs/obs-audio-controls.c index 2dce536f7..101b5b311 100644 --- a/libobs/obs-audio-controls.c +++ b/libobs/obs-audio-controls.c @@ -155,6 +155,36 @@ static float iec_db_to_def(const float db) return def; } +#define LOG_OFFSET_DB 6.0f +#define LOG_RANGE_DB 96.0f +/* equals -log10f(LOG_OFFSET_DB) */ +#define LOG_OFFSET_VAL -0.77815125038364363f +/* equals -log10f(-LOG_RANGE_DB + LOG_OFFSET_DB) */ +#define LOG_RANGE_VAL -2.00860017176191756f + +static float log_def_to_db(const float def) +{ + if (def >= 1.0f) + return 0.0f; + else if (def <= 0.0f) + return -INFINITY; + + return -(LOG_RANGE_DB + LOG_OFFSET_DB) * powf( + (LOG_RANGE_DB + LOG_OFFSET_DB) / LOG_OFFSET_DB, -def) + + LOG_OFFSET_DB; +} + +static float log_db_to_def(const float db) +{ + if (db >= 0.0f) + return 1.0f; + else if (db <= -96.0f) + return 0.0f; + + return (-log10f(-db + LOG_OFFSET_DB) - LOG_RANGE_VAL) + / (LOG_OFFSET_VAL - LOG_RANGE_VAL); +} + static void signal_volume_changed(signal_handler_t *sh, struct obs_fader *fader, const float db) { @@ -289,6 +319,12 @@ obs_fader_t *obs_fader_create(enum obs_fader_type type) fader->max_db = 0.0f; fader->min_db = -INFINITY; break; + case OBS_FADER_LOG: + fader->def_to_db = log_def_to_db; + fader->db_to_def = log_db_to_def; + fader->max_db = 0.0f; + fader->min_db = -96.0f; + break; default: goto fail; break; @@ -475,6 +511,10 @@ obs_volmeter_t *obs_volmeter_create(enum obs_fader_type type) volmeter->pos_to_db = iec_def_to_db; volmeter->db_to_pos = iec_db_to_def; break; + case OBS_FADER_LOG: + volmeter->pos_to_db = log_def_to_db; + volmeter->db_to_pos = log_db_to_def; + break; default: goto fail; break; diff --git a/libobs/obs-audio-controls.h b/libobs/obs-audio-controls.h index 4f23395b6..ed5a5b798 100644 --- a/libobs/obs-audio-controls.h +++ b/libobs/obs-audio-controls.h @@ -61,7 +61,11 @@ enum obs_fader_type { [ 2.5 %, 0 % ] | [ -60 dB, -inf dB ] @endcode */ - OBS_FADER_IEC + OBS_FADER_IEC, + /** + * @brief Logarithmic fader + */ + OBS_FADER_LOG }; /** From 014a33380776270754d3d73fadd2f3156ffa0a6f Mon Sep 17 00:00:00 2001 From: fryshorts Date: Sun, 23 Nov 2014 21:34:15 +0100 Subject: [PATCH 4/4] obs/libobs: Use new volume meter api This replaces the old code for the audio meter that was using calculations in two different places with the new audio meter api. The source signal will now emit simple levels instead of dB values, in order to avoid dB conversion and calculation in the source. The GUI on the other hand now expects simple position values from the volume meter api with no knowledge about dB calculus either. That way all code that handles those conversions is in one place, with the option to easily add new mappings that can be used everywhere. --- libobs/obs-source.c | 13 ++----------- obs/volume-control.cpp | 42 ++++++++++-------------------------------- obs/volume-control.hpp | 1 + 3 files changed, 13 insertions(+), 43 deletions(-) diff --git a/libobs/obs-source.c b/libobs/obs-source.c index c45dd99fb..37df484f1 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -627,15 +627,6 @@ static inline void handle_ts_jump(obs_source_t *source, uint64_t expected, reset_audio_timing(source, ts, os_time); } -#define VOL_MIN -96.0f -#define VOL_MAX 0.0f - -static inline float to_db(float val) -{ - float db = 20.0f * log10f(val); - return isfinite(db) ? db : VOL_MIN; -} - static void calc_volume_levels(struct obs_source *source, float *array, size_t frames, float volume) { @@ -665,8 +656,8 @@ static void calc_volume_levels(struct obs_source *source, float *array, UNUSED_PARAMETER(volume); - rms_val = to_db(sqrtf(sum_val / (float)count)); - max_val = to_db(sqrtf(max_val)); + rms_val = sqrtf(sum_val / (float)count); + max_val = sqrtf(max_val); if (max_val > source->vol_max) source->vol_max = max_val; diff --git a/obs/volume-control.cpp b/obs/volume-control.cpp index e14d9f10d..76fb1a5f7 100644 --- a/obs/volume-control.cpp +++ b/obs/volume-control.cpp @@ -12,31 +12,8 @@ using namespace std; -#define VOL_MIN -96.0f -#define VOL_MAX 0.0f - -/* - VOL_MIN_LOG = DBToLog(VOL_MIN) - VOL_MAX_LOG = DBToLog(VOL_MAX) - ... just in case someone wants to use a smaller scale - */ - -#define VOL_MIN_LOG -2.0086001717619175 -#define VOL_MAX_LOG -0.77815125038364363 - #define UPDATE_INTERVAL_MS 50 -static inline float DBToLog(float db) -{ - return -log10f(0.0f - (db - 6.0f)); -} - -static inline float DBToLinear(float db_full) -{ - float db = fmaxf(fminf(db_full, VOL_MAX), VOL_MIN); - return (DBToLog(db) - VOL_MIN_LOG) / (VOL_MAX_LOG - VOL_MIN_LOG); -} - void VolControl::OBSVolumeChanged(void *data, calldata_t *calldata) { Q_UNUSED(calldata); @@ -73,11 +50,8 @@ void VolControl::VolumeLevel(float mag, float peak, float peakHold) /* only update after a certain amount of time */ if ((curMeterTime - lastMeterTime) > UPDATE_INTERVAL_MS) { - float vol = (float)slider->value() * 0.01f; lastMeterTime = curMeterTime; - volMeter->setLevels(DBToLinear(mag) * vol, - DBToLinear(peak) * vol, - DBToLinear(peakHold) * vol); + volMeter->setLevels(mag, peak, peakHold); } } @@ -103,7 +77,8 @@ VolControl::VolControl(OBSSource source_) lastMeterTime (0), levelTotal (0.0f), levelCount (0.0f), - obs_fader (obs_fader_create(OBS_FADER_CUBIC)) + obs_fader (obs_fader_create(OBS_FADER_CUBIC)), + obs_volmeter (obs_volmeter_create(OBS_FADER_LOG)) { QVBoxLayout *mainLayout = new QVBoxLayout(); QHBoxLayout *textLayout = new QHBoxLayout(); @@ -141,13 +116,15 @@ VolControl::VolControl(OBSSource source_) 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); + signal_handler_connect(obs_volmeter_get_signal_handler(obs_volmeter), + "levels_updated", OBSVolumeLevel, this); QWidget::connect(slider, SIGNAL(valueChanged(int)), this, SLOT(SliderChanged(int))); obs_fader_attach_source(obs_fader, source); + obs_volmeter_attach_source(obs_volmeter, source); + /* Call volume changed once to init the slider position and label */ VolumeChanged(); } @@ -157,10 +134,11 @@ VolControl::~VolControl() 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); + signal_handler_disconnect(obs_volmeter_get_signal_handler(obs_volmeter), + "levels_updated", OBSVolumeLevel, this); obs_fader_destroy(obs_fader); + obs_volmeter_destroy(obs_volmeter); } VolumeMeter::VolumeMeter(QWidget *parent) diff --git a/obs/volume-control.hpp b/obs/volume-control.hpp index 87c03ee57..c43324694 100644 --- a/obs/volume-control.hpp +++ b/obs/volume-control.hpp @@ -36,6 +36,7 @@ private: float levelTotal; float levelCount; obs_fader_t *obs_fader; + obs_volmeter_t *obs_volmeter; static void OBSVolumeChanged(void *param, calldata_t *calldata); static void OBSVolumeLevel(void *data, calldata_t *calldata);