mirror of
https://github.com/obsproject/obs-studio.git
synced 2026-09-24 23:31:36 +08:00
Merge pull request #304 from fryshorts/obs-audio-controls
Add advanced audio controls
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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
|
||||
@@ -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
|
||||
|
||||
+20
-26
@@ -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<VolControl*>(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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user