0
0
mirror of https://github.com/obsproject/obs-studio.git synced 2024-09-20 04:42:18 +02:00

Merge pull request #90 from antihax/master

Added simple volume meter for reference of input levels.
This commit is contained in:
Jim 2014-06-03 05:15:18 -07:00
commit 4eb6267372
8 changed files with 199 additions and 197 deletions

View File

@ -173,11 +173,14 @@ void signal_handler_connect(signal_handler_t handler, const char *signal,
pthread_mutex_lock(&handler->mutex);
sig = getsignal(handler, signal, &last);
if (!sig)
return;
pthread_mutex_unlock(&handler->mutex);
if (!sig) {
blog(LOG_WARNING, "signal_handler_connect: "
"signal '%s' not found", signal);
return;
}
/* -------------- */
pthread_mutex_lock(&sig->mutex);

View File

@ -181,78 +181,9 @@ static inline size_t min_size(size_t a, size_t b)
((val > maxval) ? maxval : ((val < minval) ? minval : val))
#endif
#define MIN_S8 -128
#define MAX_S8 127
#define MIN_S16 -32767
#define MAX_S16 32767
#define MIN_S32 -2147483647
#define MAX_S32 2147483647
#define MIX_BUFFER_SIZE 256
/* TODO: optimize mixing */
static void mix_u8(uint8_t *mix, struct circlebuf *buf, size_t size)
{
uint8_t vals[MIX_BUFFER_SIZE];
register int16_t mix_val;
while (size) {
size_t pop_count = min_size(size, sizeof(vals));
size -= pop_count;
circlebuf_pop_front(buf, vals, pop_count);
for (size_t i = 0; i < pop_count; i++) {
mix_val = (int16_t)*mix - 128;
mix_val += (int16_t)vals[i] - 128;
mix_val = CLAMP(mix_val, MIN_S8, MAX_S8) + 128;
*(mix++) = (uint8_t)mix_val;
}
}
}
static void mix_s16(uint8_t *mix_in, struct circlebuf *buf, size_t size)
{
int16_t *mix = (int16_t*)mix_in;
int16_t vals[MIX_BUFFER_SIZE];
register int32_t mix_val;
while (size) {
size_t pop_count = min_size(size, sizeof(vals));
size -= pop_count;
circlebuf_pop_front(buf, vals, pop_count);
pop_count /= sizeof(int16_t);
for (size_t i = 0; i < pop_count; i++) {
mix_val = (int32_t)*mix;
mix_val += (int32_t)vals[i];
*(mix++) = (int16_t)CLAMP(mix_val, MIN_S16, MAX_S16);
}
}
}
static void mix_s32(uint8_t *mix_in, struct circlebuf *buf, size_t size)
{
int32_t *mix = (int32_t*)mix_in;
int32_t vals[MIX_BUFFER_SIZE];
register int64_t mix_val;
while (size) {
size_t pop_count = min_size(size, sizeof(vals));
size -= pop_count;
circlebuf_pop_front(buf, vals, pop_count);
pop_count /= sizeof(int32_t);
for (size_t i = 0; i < pop_count; i++) {
mix_val = (int64_t)*mix;
mix_val += (int64_t)vals[i];
*(mix++) = (int32_t)CLAMP(mix_val, MIN_S32, MAX_S32);
}
}
}
static void mix_float(uint8_t *mix_in, struct circlebuf *buf, size_t size)
{
float *mix = (float*)mix_in;
@ -273,31 +204,6 @@ static void mix_float(uint8_t *mix_in, struct circlebuf *buf, size_t size)
}
}
static inline void mix_audio(enum audio_format format,
uint8_t *mix, struct circlebuf *buf, size_t size)
{
switch (format) {
case AUDIO_FORMAT_UNKNOWN:
break;
case AUDIO_FORMAT_U8BIT:
case AUDIO_FORMAT_U8BIT_PLANAR:
mix_u8(mix, buf, size); break;
case AUDIO_FORMAT_16BIT:
case AUDIO_FORMAT_16BIT_PLANAR:
mix_s16(mix, buf, size); break;
case AUDIO_FORMAT_32BIT:
case AUDIO_FORMAT_32BIT_PLANAR:
mix_s32(mix, buf, size); break;
case AUDIO_FORMAT_FLOAT:
case AUDIO_FORMAT_FLOAT_PLANAR:
mix_float(mix, buf, size); break;
}
}
static inline bool mix_audio_line(struct audio_output *audio,
struct audio_line *line, size_t size, uint64_t timestamp)
{
@ -315,8 +221,7 @@ static inline bool mix_audio_line(struct audio_output *audio,
for (size_t i = 0; i < audio->planes; i++) {
size_t pop_size = min_size(size, line->buffers[i].size);
mix_audio(audio->info.format,
audio->mix_buffers[i].array + time_offset,
mix_float(audio->mix_buffers[i].array + time_offset,
&line->buffers[i], pop_size);
}
@ -711,80 +616,11 @@ uint32_t audio_output_samplerate(audio_t audio)
return audio ? audio->info.samples_per_sec : 0;
}
/* TODO: Optimization of volume multiplication functions */
static inline void mul_vol_u8bit(void *array, float volume, size_t total_num)
/* TODO: optimize these two functions */
static inline void mul_vol_float(float *array, float volume, size_t count)
{
uint8_t *vals = array;
int32_t vol = (int32_t)(volume * 127.0f);
for (size_t i = 0; i < total_num; i++) {
int32_t val = (int32_t)vals[i] - 128;
int32_t output = val * vol / 127;
vals[i] = (uint8_t)(CLAMP(output, MIN_S8, MAX_S8) + 128);
}
}
static inline void mul_vol_16bit(void *array, float volume, size_t total_num)
{
uint16_t *vals = array;
int64_t vol = (int64_t)(volume * 32767.0f);
for (size_t i = 0; i < total_num; i++) {
int64_t output = (int64_t)vals[i] * vol / 32767;
vals[i] = (int32_t)CLAMP(output, MIN_S16, MAX_S16);
}
}
static inline float conv_24bit_to_float(uint8_t *vals)
{
int32_t val = ((int32_t)vals[0]) |
((int32_t)vals[1] << 8) |
((int32_t)vals[2] << 16);
if ((val & 0x800000) != 0)
val |= 0xFF000000;
return (float)val / 8388607.0f;
}
static inline void conv_float_to_24bit(float fval, uint8_t *vals)
{
int32_t val = (int32_t)(fval * 8388607.0f);
vals[0] = (val) & 0xFF;
vals[1] = (val >> 8) & 0xFF;
vals[2] = (val >> 16) & 0xFF;
}
static inline void mul_vol_24bit(void *array, float volume, size_t total_num)
{
uint8_t *vals = array;
for (size_t i = 0; i < total_num; i++) {
float val = conv_24bit_to_float(vals) * volume;
conv_float_to_24bit(CLAMP(val, -1.0f, 1.0f), vals);
vals += 3;
}
}
static inline void mul_vol_32bit(void *array, float volume, size_t total_num)
{
int32_t *vals = array;
double dvol = (double)volume;
for (size_t i = 0; i < total_num; i++) {
double val = (double)vals[i] / 2147483647.0;
double output = val * dvol;
vals[i] = (int32_t)(CLAMP(output, -1.0, 1.0) * 2147483647.0);
}
}
static inline void mul_vol_float(void *array, float volume, size_t total_num)
{
float *vals = array;
for (size_t i = 0; i < total_num; i++)
vals[i] *= volume;
for (size_t i = 0; i < count; i++)
array[i] *= volume;
}
static void audio_line_place_data_pos(struct audio_line *line,
@ -801,25 +637,13 @@ static void audio_line_place_data_pos(struct audio_line *line,
uint8_t *array = line->volume_buffers[i].array;
switch (line->audio->info.format) {
case AUDIO_FORMAT_U8BIT:
case AUDIO_FORMAT_U8BIT_PLANAR:
mul_vol_u8bit(array, data->volume, total_num);
break;
case AUDIO_FORMAT_16BIT:
case AUDIO_FORMAT_16BIT_PLANAR:
mul_vol_16bit(array, data->volume, total_num);
break;
case AUDIO_FORMAT_32BIT:
case AUDIO_FORMAT_32BIT_PLANAR:
mul_vol_32bit(array, data->volume, total_num);
break;
case AUDIO_FORMAT_FLOAT:
case AUDIO_FORMAT_FLOAT_PLANAR:
mul_vol_float(array, data->volume, total_num);
mul_vol_float((float*)array, data->volume, total_num);
break;
case AUDIO_FORMAT_UNKNOWN:
blog(LOG_ERROR, "audio_line_place_data_pos: "
"Unknown format");
"Unsupported or unknown format");
break;
}

View File

@ -276,6 +276,12 @@ struct obs_source {
float present_volume;
int64_t sync_offset;
/* audio levels*/
float vol_mag;
float vol_max;
float vol_peak;
size_t vol_update_count;
/* transition volume is meant to store the sum of transitioning volumes
* of a source, i.e. if a source is within both the "to" and "from"
* targets of a transition, it would add both volumes to this variable,

View File

@ -19,6 +19,7 @@
#include "media-io/format-conversion.h"
#include "media-io/video-frame.h"
#include "media-io/audio-io.h"
#include "util/threading.h"
#include "util/platform.h"
#include "callback/calldata.h"
@ -79,6 +80,8 @@ static const char *source_signals[] = {
"void show(ptr source)",
"void hide(ptr source)",
"void volume(ptr source, in out float volume)",
"void volume_level(ptr source, float level, float magnitude, "
"float peak)",
NULL
};
@ -525,10 +528,83 @@ static inline void handle_ts_jump(obs_source_t source, uint64_t expected,
/* if has video, ignore audio data until reset */
if (source->info.output_flags & OBS_SOURCE_ASYNC)
os_atomic_dec_long(&source->av_sync_ref);
else
else
reset_audio_timing(source, ts);
}
#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 sum_val = 0.0f;
float max_val = 0.0f;
float rms_val = 0.0f;
const uint32_t sample_rate = audio_output_samplerate(obs_audio());
const size_t channels = audio_output_channels(obs_audio());
const size_t count = frames * channels;
const size_t vol_peak_delay = sample_rate * 3;
const float alpha = 0.15f;
for (size_t i = 0; i < count; i++) {
float val = array[i];
float val_pow2 = val * val;
sum_val += val_pow2;
max_val = fmaxf(max_val, val_pow2);
}
rms_val = to_db(sqrtf(sum_val / (float)count));
max_val = to_db(sqrtf(max_val));
if (max_val > source->vol_max)
source->vol_max = max_val;
else
source->vol_max = alpha * source->vol_max +
(1.0f - alpha) * max_val;
if (source->vol_max > source->vol_peak ||
source->vol_update_count > vol_peak_delay) {
source->vol_peak = source->vol_max;
source->vol_update_count = 0;
} else {
source->vol_update_count += count;
}
source->vol_mag = alpha * rms_val + source->vol_mag * (1.0f - alpha);
}
/* TODO update peak/etc later */
static void obs_source_update_volume_level(obs_source_t source,
struct audio_data *in)
{
if (source && in) {
struct calldata data = {0};
calc_volume_levels(source, (float*)in->data[0], in->frames);
calldata_setptr (&data, "source", source);
calldata_setfloat(&data, "level", source->vol_max);
calldata_setfloat(&data, "magnitude", source->vol_mag);
calldata_setfloat(&data, "peak", source->vol_peak);
signal_handler_signal(source->context.signals, "volume_level",
&data);
signal_handler_signal(obs->signals, "source_volume_level",
&data);
calldata_free(&data);
}
}
static void source_output_audio_line(obs_source_t source,
const struct audio_data *data)
{
@ -569,6 +645,7 @@ static void source_output_audio_line(obs_source_t source,
obs->audio.user_volume * obs->audio.present_volume;
audio_line_output(source->audio_line, &in);
obs_source_update_volume_level(source, &in);
}
enum convert_type {
@ -894,13 +971,13 @@ static inline void obs_source_draw_texture(struct obs_source *source,
param = effect_getparambyname(effect, "color_range_min");
effect_setval(effect, param, color_range_min, size);
}
if (color_range_max) {
size_t const size = sizeof(float) * 3;
param = effect_getparambyname(effect, "color_range_max");
effect_setval(effect, param, color_range_max, size);
}
if (color_matrix) {
param = effect_getparambyname(effect, "color_matrix");
effect_setval(effect, param, color_matrix, sizeof(float) * 16);

View File

@ -471,6 +471,8 @@ static const char *obs_signals[] = {
"void source_show(ptr source)",
"void source_hide(ptr source)",
"void source_volume(ptr source, in out float volume)",
"void source_volume_level(ptr source, float level, float magnitude, "
"float peak)",
"void channel_change(int channel, in out ptr source, ptr prev_source)",
"void master_volume(in out float volume)",

View File

@ -525,6 +525,9 @@ EXPORT proc_handler_t obs_source_prochandler(obs_source_t source);
/** Sets the user volume for a source that has audio output */
EXPORT void obs_source_setvolume(obs_source_t source, float volume);
/** Updates live volume for a source */
EXPORT void obs_source_updatevolumelevel(obs_source_t source, int volume);
/** Sets the presentation volume for a source */
EXPORT void obs_source_set_present_volume(obs_source_t source, float volume);

View File

@ -1,20 +1,57 @@
#include "volume-control.hpp"
#include "qt-wrappers.hpp"
#include <util/platform.h>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QSlider>
#include <QLabel>
#include <string>
#include <math.h>
using namespace std;
#define VOL_MIN -96.0f
#define VOL_MAX 0.0f
#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)
{
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", Q_ARG(int, vol));
}
void VolControl::OBSVolumeLevel(void *data, calldata_t calldata)
{
VolControl *volControl = static_cast<VolControl*>(data);
float level = calldata_float(calldata, "level");
float mag = calldata_float(calldata, "magnitude");
/*
* TODO: an actual volume control that can process level, mag, peak.
*
* for the time being, just average level and magnitude.
*/
float result = (level + mag) * 0.5f;
QMetaObject::invokeMethod(volControl, "VolumeLevel",
Q_ARG(float, result));
}
void VolControl::VolumeChanged(int vol)
@ -24,6 +61,25 @@ void VolControl::VolumeChanged(int vol)
signalChanged = true;
}
void VolControl::VolumeLevel(float level)
{
uint64_t curMeterTime = os_gettime_ns() / 1000000;
levelTotal += level;
levelCount += 1.0f;
/* only update after a certain amount of time */
if ((curMeterTime - lastMeterTime) > UPDATE_INTERVAL_MS) {
lastMeterTime = curMeterTime;
float finalLevel = levelTotal / levelCount;
volMeter->setValue(int(DBToLinear(finalLevel) * 10000.0f));
levelTotal = 0.0f;
levelCount = 0.0f;
}
}
void VolControl::SliderChanged(int vol)
{
if (signalChanged) {
@ -35,12 +91,16 @@ void VolControl::SliderChanged(int vol)
signal_handler_connect(obs_source_signalhandler(source),
"volume", OBSVolumeChanged, this);
}
volLabel->setText(QString::number(vol));
}
VolControl::VolControl(OBSSource source_)
: source (source_),
signalChanged (true)
signalChanged (true),
lastMeterTime (0),
levelTotal (0.0f),
levelCount (0.0f)
{
QVBoxLayout *mainLayout = new QVBoxLayout();
QHBoxLayout *textLayout = new QHBoxLayout();
@ -48,6 +108,7 @@ VolControl::VolControl(OBSSource source_)
nameLabel = new QLabel();
volLabel = new QLabel();
volMeter = new QProgressBar();
slider = new QSlider(Qt::Horizontal);
QFont font = nameLabel->font();
@ -60,7 +121,19 @@ VolControl::VolControl(OBSSource source_)
slider->setMinimum(0);
slider->setMaximum(100);
slider->setValue(vol);
//slider->setMaximumHeight(16);
// slider->setMaximumHeight(13);
volMeter->setMaximumHeight(1);
volMeter->setMinimum(0);
volMeter->setMaximum(10000);
volMeter->setTextVisible(false);
/* [Danni] Temporary color. */
QString testColor = "QProgressBar "
"{border: 0px} "
"QProgressBar::chunk "
"{width: 1px; background-color: #AA0000;}";
volMeter->setStyleSheet(testColor);
textLayout->setContentsMargins(0, 0, 0, 0);
textLayout->addWidget(nameLabel);
@ -71,6 +144,7 @@ VolControl::VolControl(OBSSource source_)
mainLayout->setContentsMargins(4, 4, 4, 4);
mainLayout->setSpacing(2);
mainLayout->addItem(textLayout);
mainLayout->addWidget(volMeter);
mainLayout->addWidget(slider);
setLayout(mainLayout);
@ -78,6 +152,9 @@ VolControl::VolControl(OBSSource source_)
signal_handler_connect(obs_source_signalhandler(source),
"volume", OBSVolumeChanged, this);
signal_handler_connect(obs_source_signalhandler(source),
"volume_level", OBSVolumeLevel, this);
QWidget::connect(slider, SIGNAL(valueChanged(int)),
this, SLOT(SliderChanged(int)));
}
@ -86,4 +163,7 @@ VolControl::~VolControl()
{
signal_handler_disconnect(obs_source_signalhandler(source),
"volume", OBSVolumeChanged, this);
signal_handler_disconnect(obs_source_signalhandler(source),
"volume_level", OBSVolumeLevel, this);
}

View File

@ -2,6 +2,7 @@
#include <obs.hpp>
#include <QWidget>
#include <QProgressBar>
/* TODO: Make a real volume control that isn't terrible */
@ -13,15 +14,21 @@ class VolControl : public QWidget {
private:
OBSSource source;
QLabel *nameLabel;
QLabel *volLabel;
QSlider *slider;
bool signalChanged;
QLabel *nameLabel;
QLabel *volLabel;
QProgressBar *volMeter;
QSlider *slider;
bool signalChanged;
uint64_t lastMeterTime;
float levelTotal;
float levelCount;
static void OBSVolumeChanged(void *param, calldata_t calldata);
static void OBSVolumeLevel(void *data, calldata_t calldata);
private slots:
void VolumeChanged(int vol);
void VolumeLevel(float level);
void SliderChanged(int vol);
public: