0
0
mirror of https://github.com/obsproject/obs-studio.git synced 2024-09-19 20:32:15 +02:00

plugins: add back short-circuiting logic to compression

In short, when we have more than one sidechain source, if one has
already triggered compression, we don't need to keep the other one's
checking.

clang format all changes

update away from old QTStr following rebase
This commit is contained in:
Avuxo 2024-08-15 08:37:20 +09:00
parent 32996b58c9
commit be0242dfbf
4 changed files with 254 additions and 242 deletions

View File

@ -78,7 +78,7 @@ enum obs_editable_list_type {
OBS_EDITABLE_LIST_TYPE_STRINGS,
OBS_EDITABLE_LIST_TYPE_FILES,
OBS_EDITABLE_LIST_TYPE_FILES_AND_URLS,
OBS_EDITABLE_LIST_TYPE_SOURCES,
OBS_EDITABLE_LIST_TYPE_SOURCES,
};
enum obs_path_type {

View File

@ -61,14 +61,14 @@
/* -------------------------------------------------------- */
struct sidechain {
pthread_mutex_t mutex;
struct deque data[MAX_AUDIO_CHANNELS];
float *buf[MAX_AUDIO_CHANNELS];
char *name;
obs_weak_source_t *weak_ref;
uint64_t check_time;
size_t max_frames;
size_t num_channels;
pthread_mutex_t mutex;
struct deque data[MAX_AUDIO_CHANNELS];
float *buf[MAX_AUDIO_CHANNELS];
char *name;
obs_weak_source_t *weak_ref;
uint64_t check_time;
size_t max_frames;
size_t num_channels;
};
struct compressor_data {
@ -88,43 +88,42 @@ struct compressor_data {
float slope;
pthread_mutex_t sidechain_update_mutex;
size_t max_sidechain_frames;
DARRAY(struct sidechain) sidechains;
DARRAY(struct sidechain) sidechains;
};
/* -------------------------------------------------------- */
static inline void get_sidechain_data(struct compressor_data *cd,
const uint32_t num_samples)
{
size_t data_size = cd->envelope_buf_len * sizeof(float);
if (!data_size)
return;
for (size_t ix = 0; ix < cd->sidechains.num; ix += 1) {
struct sidechain *sidechain = &cd->sidechains.array[ix];
pthread_mutex_lock(&sidechain->mutex);
if (sidechain->max_frames < num_samples)
sidechain->max_frames = num_samples;
if (sidechain->data[0].size < data_size) {
for (size_t i = 0; i < cd->num_channels; i++) {
memset(sidechain->buf[i], 0, data_size);
}
pthread_mutex_unlock(&sidechain->mutex);
continue;
}
for (size_t i = 0; i < cd->num_channels; i++)
deque_pop_front(&sidechain->data[i], sidechain->buf[i],
data_size);
pthread_mutex_unlock(&sidechain->mutex);
}
for (size_t ix = 0; ix < cd->sidechains.num; ix += 1) {
struct sidechain *sidechain = &cd->sidechains.array[ix];
pthread_mutex_lock(&sidechain->mutex);
if (sidechain->max_frames < num_samples)
sidechain->max_frames = num_samples;
if (sidechain->data[0].size < data_size) {
for (size_t i = 0; i < cd->num_channels; i++) {
memset(sidechain->buf[i], 0, data_size);
}
pthread_mutex_unlock(&sidechain->mutex);
continue;
}
for (size_t i = 0; i < cd->num_channels; i++)
deque_pop_front(&sidechain->data[i], sidechain->buf[i],
data_size);
pthread_mutex_unlock(&sidechain->mutex);
}
}
static void resize_env_buffer(struct compressor_data *cd, size_t len)
@ -132,16 +131,16 @@ static void resize_env_buffer(struct compressor_data *cd, size_t len)
cd->envelope_buf_len = len;
cd->envelope_buf = brealloc(cd->envelope_buf, len * sizeof(float));
for (size_t ix = 0; ix < cd->sidechains.num; ix += 1) {
struct sidechain *sidechain = &cd->sidechains.array[ix];
pthread_mutex_lock(&sidechain->mutex);
for (size_t i = 0; i < cd->num_channels; i++) {
sidechain->buf[i] =
brealloc(sidechain->buf[i], len * sizeof(float));
}
pthread_mutex_unlock(&sidechain->mutex);
}
for (size_t ix = 0; ix < cd->sidechains.num; ix += 1) {
struct sidechain *sidechain = &cd->sidechains.array[ix];
pthread_mutex_lock(&sidechain->mutex);
for (size_t i = 0; i < cd->num_channels; i++) {
sidechain->buf[i] = brealloc(sidechain->buf[i],
len * sizeof(float));
}
pthread_mutex_unlock(&sidechain->mutex);
}
}
static inline float gain_coefficient(uint32_t sample_rate, float time)
@ -158,7 +157,7 @@ static const char *compressor_name(void *unused)
static void sidechain_capture(void *param, obs_source_t *source,
const struct audio_data *audio_data, bool muted)
{
struct sidechain *sidechain = param;
struct sidechain *sidechain = param;
UNUSED_PARAMETER(source);
@ -197,37 +196,40 @@ unlock:
pthread_mutex_unlock(&sidechain->mutex);
}
static void clear_sidechains(struct compressor_data *cd) {
size_t sidechain_count = cd->sidechains.num;
for (size_t ix = 0; ix < sidechain_count; ix += 1) {
struct sidechain *sidechain = &cd->sidechains.array[ix];
obs_source_t *old_source = obs_weak_source_get_source(sidechain->weak_ref);
if (old_source) {
obs_source_remove_audio_capture_callback(old_source, sidechain_capture, sidechain);
obs_source_release(old_source);
}
if (sidechain->weak_ref) {
obs_weak_source_release(sidechain->weak_ref);
sidechain->weak_ref = NULL;
}
if (sidechain->name) {
bfree(sidechain->name);
sidechain->name = NULL;
}
for (size_t ix = 0; ix < MAX_AUDIO_CHANNELS; ix += 1) {
deque_free(&sidechain->data[ix]);
bfree(sidechain->buf[ix]);
}
pthread_mutex_destroy(&sidechain->mutex);
}
da_clear(cd->sidechains);
static void clear_sidechains(struct compressor_data *cd)
{
size_t sidechain_count = cd->sidechains.num;
for (size_t ix = 0; ix < sidechain_count; ix += 1) {
struct sidechain *sidechain = &cd->sidechains.array[ix];
obs_source_t *old_source =
obs_weak_source_get_source(sidechain->weak_ref);
if (old_source) {
obs_source_remove_audio_capture_callback(
old_source, sidechain_capture, sidechain);
obs_source_release(old_source);
}
if (sidechain->weak_ref) {
obs_weak_source_release(sidechain->weak_ref);
sidechain->weak_ref = NULL;
}
if (sidechain->name) {
bfree(sidechain->name);
sidechain->name = NULL;
}
for (size_t ix = 0; ix < MAX_AUDIO_CHANNELS; ix += 1) {
deque_free(&sidechain->data[ix]);
bfree(sidechain->buf[ix]);
}
pthread_mutex_destroy(&sidechain->mutex);
}
da_clear(cd->sidechains);
}
static void compressor_update(void *data, obs_data_t *s)
@ -242,9 +244,9 @@ static void compressor_update(void *data, obs_data_t *s)
(float)obs_data_get_int(s, S_RELEASE_TIME);
const float output_gain_db =
(float)obs_data_get_double(s, S_OUTPUT_GAIN);
obs_data_array_t *sidechain_names =
obs_data_get_array(s, S_SIDECHAIN_SOURCE);
size_t sidechain_count = obs_data_array_count(sidechain_names);
obs_data_array_t *sidechain_names =
obs_data_get_array(s, S_SIDECHAIN_SOURCE);
size_t sidechain_count = obs_data_array_count(sidechain_names);
cd->ratio = (float)obs_data_get_double(s, S_RATIO);
cd->threshold = (float)obs_data_get_double(s, S_THRESHOLD);
@ -256,44 +258,44 @@ static void compressor_update(void *data, obs_data_t *s)
cd->num_channels = num_channels;
cd->sample_rate = sample_rate;
cd->slope = 1.0f - (1.0f / cd->ratio);
// reset callbacks and cleanup after ourselves before updating list
pthread_mutex_lock(&cd->sidechain_update_mutex);
clear_sidechains(cd);
pthread_mutex_unlock(&cd->sidechain_update_mutex);
size_t erased_count = 0;
for (size_t ix = 0; ix < sidechain_count; ix += 1) {
obs_data_t *source = obs_data_array_item(sidechain_names, ix);
const char *sidechain_name = obs_data_get_string(source, "value");
bool valid_sidechain = *sidechain_name &&
strcmp(sidechain_name, "none") != 0;
if (!valid_sidechain) {
continue;
}
pthread_mutex_lock(&cd->sidechain_update_mutex);
struct sidechain *sidechain = da_push_back_new(cd->sidechains);
if (pthread_mutex_init(&sidechain->mutex, NULL) != 0) {
blog(LOG_ERROR, "Failed to create mutex for sidechain");
da_erase(cd->sidechains, (ix - erased_count));
erased_count += 1;
}
sidechain->num_channels = num_channels;
sidechain->name = bstrdup(sidechain_name);
sidechain->weak_ref = NULL;
pthread_mutex_unlock(&cd->sidechain_update_mutex);
}
// reset callbacks and cleanup after ourselves before updating list
pthread_mutex_lock(&cd->sidechain_update_mutex);
clear_sidechains(cd);
pthread_mutex_unlock(&cd->sidechain_update_mutex);
size_t erased_count = 0;
for (size_t ix = 0; ix < sidechain_count; ix += 1) {
obs_data_t *source = obs_data_array_item(sidechain_names, ix);
const char *sidechain_name =
obs_data_get_string(source, "value");
bool valid_sidechain = *sidechain_name &&
strcmp(sidechain_name, "none") != 0;
if (!valid_sidechain) {
continue;
}
pthread_mutex_lock(&cd->sidechain_update_mutex);
struct sidechain *sidechain = da_push_back_new(cd->sidechains);
if (pthread_mutex_init(&sidechain->mutex, NULL) != 0) {
blog(LOG_ERROR, "Failed to create mutex for sidechain");
da_erase(cd->sidechains, (ix - erased_count));
erased_count += 1;
}
sidechain->num_channels = num_channels;
sidechain->name = bstrdup(sidechain_name);
sidechain->weak_ref = NULL;
pthread_mutex_unlock(&cd->sidechain_update_mutex);
}
size_t sample_len = sample_rate * DEFAULT_AUDIO_BUF_MS / MS_IN_S;
resize_env_buffer(cd, sample_len);
resize_env_buffer(cd, sample_len);
}
static void *compressor_create(obs_data_t *settings, obs_source_t *filter)
@ -307,8 +309,8 @@ static void *compressor_create(obs_data_t *settings, obs_source_t *filter)
bfree(cd);
return NULL;
}
da_init(cd->sidechains);
da_init(cd->sidechains);
compressor_update(cd, settings);
return cd;
@ -316,16 +318,16 @@ static void *compressor_create(obs_data_t *settings, obs_source_t *filter)
static void compressor_destroy(void *data)
{
struct compressor_data *cd = data;
pthread_mutex_destroy(&cd->sidechain_update_mutex);
clear_sidechains(cd);
da_free(cd->sidechains);
bfree(cd->envelope_buf);
pthread_mutex_destroy(&cd->sidechain_update_mutex);
clear_sidechains(cd);
da_free(cd->sidechains);
bfree(cd->envelope_buf);
bfree(cd);
}
@ -354,7 +356,6 @@ static void analyze_envelope(struct compressor_data *cd, float **samples,
env = env_in + release_gain * (env - env_in);
}
envelope_buf[i] = fmaxf(envelope_buf[i], env);
}
}
cd->envelope = cd->envelope_buf[num_samples - 1];
@ -371,35 +372,43 @@ static void analyze_sidechain(struct compressor_data *cd,
const float attack_gain = cd->attack_gain;
const float release_gain = cd->release_gain;
memset(cd->envelope_buf, 0, num_samples * sizeof(cd->envelope_buf[0]));
for (size_t chan = 0; chan < cd->num_channels; ++chan) {
for (size_t sidechain_ix = 0; sidechain_ix < cd->sidechains.num; sidechain_ix += 1) {
float **sidechain_buf = cd->sidechains.array[sidechain_ix].buf;
if (!sidechain_buf[chan])
continue;
float *envelope_buf = cd->envelope_buf;
float env = cd->envelope;
for (uint32_t i = 0; i < num_samples; ++i) {
const float env_in = fabsf(sidechain_buf[chan][i]);
if (env < env_in) {
env = env_in + attack_gain * (env - env_in);
} else {
env = env_in + release_gain * (env - env_in);
}
envelope_buf[i] = fmaxf(envelope_buf[i], env);
}
// we don't need to check any other sidechains if we have successfully found one.
// TODO(Ben): the goal of this logic is to short-circuit the check, but this logic unfortunately
// is applied to a /muted/ source too...
//goto continue_outer;
}
//continue_outer:;
}
memset(cd->envelope_buf, 0, num_samples * sizeof(cd->envelope_buf[0]));
for (size_t chan = 0; chan < cd->num_channels; ++chan) {
bool compression_applied = false;
for (size_t sidechain_ix = 0; sidechain_ix < cd->sidechains.num;
sidechain_ix += 1) {
float **sidechain_buf =
cd->sidechains.array[sidechain_ix].buf;
if (!sidechain_buf[chan])
continue;
float *envelope_buf = cd->envelope_buf;
float env = cd->envelope;
for (uint32_t i = 0; i < num_samples; ++i) {
const float env_in =
fabsf(sidechain_buf[chan][i]);
if (env < env_in) {
env = env_in +
attack_gain * (env - env_in);
compression_applied = true;
} else {
env = env_in +
release_gain * (env - env_in);
}
envelope_buf[i] = fmaxf(envelope_buf[i], env);
}
// if the threshold was reached on the channel already, there's no need to continue
// with other sidechains.
if (compression_applied) {
goto continue_outer;
}
}
continue_outer:;
}
cd->envelope = cd->envelope_buf[num_samples - 1];
}
@ -421,28 +430,30 @@ static inline void process_compression(const struct compressor_data *cd,
static void compressor_tick(void *data, float seconds)
{
UNUSED_PARAMETER(seconds);
UNUSED_PARAMETER(seconds);
struct compressor_data *cd = data;
size_t sidechain_count = cd->sidechains.num;
for (size_t ix = 0; ix < sidechain_count; ix += 1) {
struct sidechain *sidechain = &cd->sidechains.array[ix];
// is this a sidechain just created by update()?
if (sidechain->name && !sidechain->weak_ref) {
pthread_mutex_lock(&cd->sidechain_update_mutex);
struct compressor_data *cd = data;
size_t sidechain_count = cd->sidechains.num;
for (size_t ix = 0; ix < sidechain_count; ix += 1) {
struct sidechain *sidechain = &cd->sidechains.array[ix];
// is this a sidechain just created by update()?
if (sidechain->name && !sidechain->weak_ref) {
pthread_mutex_lock(&cd->sidechain_update_mutex);
obs_source_t *source = obs_get_source_by_name(sidechain->name);
obs_weak_source_t *weak_sidechain = obs_source_get_weak_source(source);
sidechain->weak_ref = weak_sidechain;
pthread_mutex_unlock(&cd->sidechain_update_mutex);
obs_source_add_audio_capture_callback(source, sidechain_capture, sidechain);
obs_source_release(source);
}
}
obs_source_t *source =
obs_get_source_by_name(sidechain->name);
obs_weak_source_t *weak_sidechain =
obs_source_get_weak_source(source);
sidechain->weak_ref = weak_sidechain;
pthread_mutex_unlock(&cd->sidechain_update_mutex);
obs_source_add_audio_capture_callback(
source, sidechain_capture, sidechain);
obs_source_release(source);
}
}
}
static struct obs_audio_data *
@ -457,15 +468,15 @@ compressor_filter_audio(void *data, struct obs_audio_data *audio)
float **samples = (float **)audio->data;
pthread_mutex_lock(&cd->sidechain_update_mutex);
bool has_sidechain = false;
const size_t sidechain_count = cd->sidechains.num;
for (size_t ix = 0; ix < sidechain_count; ix += 1) {
struct sidechain *sidechain = &cd->sidechains.array[ix];
if (sidechain->weak_ref) {
has_sidechain = true;
break;
}
}
bool has_sidechain = false;
const size_t sidechain_count = cd->sidechains.num;
for (size_t ix = 0; ix < sidechain_count; ix += 1) {
struct sidechain *sidechain = &cd->sidechains.array[ix];
if (sidechain->weak_ref) {
has_sidechain = true;
break;
}
}
pthread_mutex_unlock(&cd->sidechain_update_mutex);
if (has_sidechain)
@ -517,10 +528,10 @@ static obs_properties_t *compressor_properties(void *data)
MAX_OUTPUT_GAIN_DB, 0.1);
obs_property_float_set_suffix(p, " dB");
obs_properties_add_editable_list(props, S_SIDECHAIN_SOURCE,
TEXT_SIDECHAIN_SOURCE,
OBS_EDITABLE_LIST_TYPE_SOURCES, "",
"");
obs_properties_add_editable_list(props, S_SIDECHAIN_SOURCE,
TEXT_SIDECHAIN_SOURCE,
OBS_EDITABLE_LIST_TYPE_SOURCES, "",
"");
return props;
}

View File

@ -23,6 +23,7 @@
#include <QDir>
#include <QGroupBox>
#include <QObject>
#include <QUuid>
#include <QDesktopServices>
#include "QtCore/qstring.h"
#include "QtGui/qaction.h"
@ -2264,45 +2265,45 @@ public:
};
class DropdownDialog : public QDialog {
QComboBox *dropdown;
QComboBox *dropdown;
public:
DropdownDialog(QWidget *parent) : QDialog(parent)
{
QHBoxLayout *topLayout = new QHBoxLayout();
QVBoxLayout *mainLayout = new QVBoxLayout();
DropdownDialog(QWidget *parent) : QDialog(parent)
{
QHBoxLayout *topLayout = new QHBoxLayout();
QVBoxLayout *mainLayout = new QVBoxLayout();
dropdown = new QComboBox();
topLayout->addWidget(dropdown);
topLayout->setAlignment(dropdown, Qt::AlignVCenter);
dropdown = new QComboBox();
topLayout->addWidget(dropdown);
topLayout->setAlignment(dropdown, Qt::AlignVCenter);
QDialogButtonBox::StandardButtons buttons =
QDialogButtonBox::Ok | QDialogButtonBox::Cancel;
QDialogButtonBox::StandardButtons buttons =
QDialogButtonBox::Ok | QDialogButtonBox::Cancel;
QDialogButtonBox *buttonBox = new QDialogButtonBox(buttons);
buttonBox->setCenterButtons(true);
QDialogButtonBox *buttonBox = new QDialogButtonBox(buttons);
buttonBox->setCenterButtons(true);
mainLayout->addLayout(topLayout);
mainLayout->addWidget(buttonBox);
mainLayout->addLayout(topLayout);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
resize(400, 80);
setLayout(mainLayout);
resize(400, 80);
connect(buttonBox, &QDialogButtonBox::accepted, this,
&DropdownDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this,
&DropdownDialog::reject);
}
connect(buttonBox, &QDialogButtonBox::accepted, this,
&DropdownDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this,
&DropdownDialog::reject);
}
int GetSelectedIndex() const { return dropdown->currentIndex(); }
int GetSelectedIndex() const { return dropdown->currentIndex(); }
QString GetSelectedString() { return dropdown->currentText(); }
QString GetSelectedString() { return dropdown->currentText(); }
void AddField(const char *name) { dropdown->addItem(name); }
void AddField(const QString &icon, const char *name)
{
dropdown->addItem(icon, name);
}
void AddField(const char *name) { dropdown->addItem(name); }
void AddField(const QString &icon, const char *name)
{
dropdown->addItem(icon, name);
}
};
void WidgetInfo::EditListAdd()
@ -2314,11 +2315,11 @@ void WidgetInfo::EditListAdd()
EditListAddText();
return;
}
if (type == OBS_EDITABLE_LIST_TYPE_SOURCES) {
EditListAddSource();
return;
}
if (type == OBS_EDITABLE_LIST_TYPE_SOURCES) {
EditListAddSource();
return;
}
/* Files and URLs */
QMenu popup(view->window());
@ -2346,36 +2347,36 @@ void WidgetInfo::EditListAdd()
void WidgetInfo::EditListAddSource()
{
QListWidget *list = reinterpret_cast<QListWidget *>(widget);
const char *desc = obs_property_description(property);
QListWidget *list = reinterpret_cast<QListWidget *>(widget);
const char *desc = obs_property_description(property);
DropdownDialog dialog(widget->window());
/* ListEntry reused because its syntactically correct here. */
auto title = QTStr("Basic.PropertiesWindow.AddEditableListEntry")
.arg(QT_UTF8(desc));
DropdownDialog dialog(widget->window());
/* ListEntry reused because its syntactically correct here. */
auto title = tr("Basic.PropertiesWindow.AddEditableListEntry")
.arg(QT_UTF8(desc));
dialog.setWindowTitle(title);
dialog.setWindowTitle(title);
dialog.AddField(QTStr("None"), "none");
dialog.AddField(tr("None"), "none");
obs_enum_sources(
[](void *info, obs_source_t *source) {
auto dialog = reinterpret_cast<DropdownDialog *>(info);
const char *name = obs_source_get_name(source);
obs_enum_sources(
[](void *info, obs_source_t *source) {
auto dialog = reinterpret_cast<DropdownDialog *>(info);
const char *name = obs_source_get_name(source);
dialog->AddField(name);
return true;
},
&dialog);
dialog->AddField(name);
return true;
},
&dialog);
if (dialog.exec() == QDialog::Rejected) {
return;
}
if (dialog.exec() == QDialog::Rejected) {
return;
}
QString qstr = dialog.GetSelectedString();
QString qstr = dialog.GetSelectedString();
list->addItem(qstr);
EditableListChanged();
list->addItem(qstr);
EditableListChanged();
}
void WidgetInfo::EditListAddText()

View File

@ -74,7 +74,7 @@ public slots:
void EditListAdd();
void EditListAddText();
void EditListAddFiles();
void EditListAddSource();
void EditListAddSource();
void EditListAddDir();
void EditListRemove();
void EditListEdit();