Merge pull request #311 from fryshorts/audio-analyzer-move
libobs: Move audio level calculationsmaster
commit
9f3ab85d2b
|
@ -54,6 +54,21 @@ struct obs_volmeter {
|
|||
obs_source_t *source;
|
||||
enum obs_fader_type type;
|
||||
float cur_db;
|
||||
|
||||
unsigned int channels;
|
||||
unsigned int update_ms;
|
||||
unsigned int update_frames;
|
||||
unsigned int peakhold_ms;
|
||||
unsigned int peakhold_frames;
|
||||
|
||||
unsigned int peakhold_count;
|
||||
unsigned int ival_frames;
|
||||
float ival_sum;
|
||||
float ival_max;
|
||||
|
||||
float vol_peak;
|
||||
float vol_mag;
|
||||
float vol_max;
|
||||
};
|
||||
|
||||
static const char *fader_signals[] = {
|
||||
|
@ -260,29 +275,6 @@ 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);
|
||||
|
@ -291,6 +283,131 @@ static void volmeter_source_destroyed(void *vptr, calldata_t *calldata)
|
|||
obs_volmeter_detach_source(volmeter);
|
||||
}
|
||||
|
||||
static void volmeter_sum_and_max(float *data, size_t frames,
|
||||
float *sum, float *max)
|
||||
{
|
||||
float s = *sum;
|
||||
float m = *max;
|
||||
|
||||
for (float *c = data; c < data + frames; ++c) {
|
||||
const float pow = *c * *c;
|
||||
s += pow;
|
||||
m = (m > pow) ? m : pow;
|
||||
}
|
||||
|
||||
*sum = s;
|
||||
*max = m;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo The IIR low pass filter has a different behavior depending on the
|
||||
* update interval and sample rate, it should be replaced with something
|
||||
* that is independent from both.
|
||||
*/
|
||||
static void volmeter_calc_ival_levels(obs_volmeter_t *volmeter)
|
||||
{
|
||||
const float alpha = 0.15f;
|
||||
const float frames = (float) volmeter->ival_frames;
|
||||
const float samples = frames * (float) volmeter->channels;
|
||||
const float ival_max = sqrtf(volmeter->ival_max);
|
||||
const float ival_rms = sqrtf(volmeter->ival_sum / samples);
|
||||
|
||||
if (ival_max > volmeter->vol_max) {
|
||||
volmeter->vol_max = ival_max;
|
||||
} else {
|
||||
volmeter->vol_max = alpha * volmeter->vol_max +
|
||||
(1.0f - alpha) * ival_max;
|
||||
}
|
||||
|
||||
if (volmeter->vol_max > volmeter->vol_peak ||
|
||||
volmeter->peakhold_count > volmeter->peakhold_frames) {
|
||||
volmeter->vol_peak = volmeter->vol_max;
|
||||
volmeter->peakhold_count = 0;
|
||||
} else {
|
||||
volmeter->peakhold_count += frames;
|
||||
}
|
||||
|
||||
volmeter->vol_mag = alpha * ival_rms +
|
||||
volmeter->vol_mag * (1.0f - alpha);
|
||||
|
||||
/* reset interval data */
|
||||
volmeter->ival_frames = 0;
|
||||
volmeter->ival_sum = 0.0f;
|
||||
volmeter->ival_max = 0.0f;
|
||||
}
|
||||
|
||||
static bool volmeter_process_audio_data(obs_volmeter_t *volmeter,
|
||||
struct audio_data *data)
|
||||
{
|
||||
bool updated = false;
|
||||
size_t frames = 0;
|
||||
size_t samples = 0;
|
||||
size_t left = data->frames;
|
||||
float *adata = (float *) data->data[0];
|
||||
|
||||
while (left) {
|
||||
frames = (volmeter->ival_frames + left >
|
||||
volmeter->update_frames)
|
||||
? volmeter->update_frames - volmeter->ival_frames
|
||||
: left;
|
||||
samples = frames * volmeter->channels;
|
||||
|
||||
volmeter_sum_and_max(adata, samples, &volmeter->ival_sum,
|
||||
&volmeter->ival_max);
|
||||
|
||||
volmeter->ival_frames += frames;
|
||||
left -= frames;
|
||||
adata += samples;
|
||||
|
||||
/* break if we did not reach the end of the interval */
|
||||
if (volmeter->ival_frames != volmeter->update_frames)
|
||||
break;
|
||||
|
||||
volmeter_calc_ival_levels(volmeter);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
static void volmeter_source_data_received(void *vptr, calldata_t *calldata)
|
||||
{
|
||||
struct obs_volmeter *volmeter = (struct obs_volmeter *) vptr;
|
||||
bool updated = false;
|
||||
float mul, level, mag, peak;
|
||||
signal_handler_t *sh;
|
||||
|
||||
pthread_mutex_lock(&volmeter->mutex);
|
||||
|
||||
struct audio_data *data = calldata_ptr(calldata, "data");
|
||||
updated = volmeter_process_audio_data(volmeter, data);
|
||||
|
||||
if (updated) {
|
||||
mul = db_to_mul(volmeter->cur_db);
|
||||
|
||||
level = volmeter->db_to_pos(mul_to_db(volmeter->vol_max * mul));
|
||||
mag = volmeter->db_to_pos(mul_to_db(volmeter->vol_mag * mul));
|
||||
peak = volmeter->db_to_pos(
|
||||
mul_to_db(volmeter->vol_peak * mul));
|
||||
sh = volmeter->signals;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&volmeter->mutex);
|
||||
|
||||
if (updated)
|
||||
signal_levels_updated(sh, volmeter, level, mag, peak);
|
||||
}
|
||||
|
||||
static void volmeter_update_audio_settings(obs_volmeter_t *volmeter)
|
||||
{
|
||||
audio_t *audio = obs_get_audio();
|
||||
const unsigned int sr = audio_output_get_sample_rate(audio);
|
||||
|
||||
volmeter->channels = audio_output_get_channels(audio);
|
||||
volmeter->update_frames = volmeter->update_ms * sr / 1000;
|
||||
volmeter->peakhold_frames = volmeter->peakhold_ms * sr / 1000;
|
||||
}
|
||||
|
||||
obs_fader_t *obs_fader_create(enum obs_fader_type type)
|
||||
{
|
||||
struct obs_fader *fader = bzalloc(sizeof(struct obs_fader));
|
||||
|
@ -521,6 +638,9 @@ obs_volmeter_t *obs_volmeter_create(enum obs_fader_type type)
|
|||
}
|
||||
volmeter->type = type;
|
||||
|
||||
obs_volmeter_set_update_interval(volmeter, 50);
|
||||
obs_volmeter_set_peak_hold(volmeter, 1500);
|
||||
|
||||
return volmeter;
|
||||
fail:
|
||||
obs_volmeter_destroy(volmeter);
|
||||
|
@ -553,8 +673,8 @@ bool obs_volmeter_attach_source(obs_volmeter_t *volmeter, obs_source_t *source)
|
|||
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, "audio_data",
|
||||
volmeter_source_data_received, volmeter);
|
||||
signal_handler_connect(sh, "destroy",
|
||||
volmeter_source_destroyed, volmeter);
|
||||
|
||||
|
@ -581,8 +701,8 @@ void obs_volmeter_detach_source(obs_volmeter_t *volmeter)
|
|||
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, "audio_data",
|
||||
volmeter_source_data_received, volmeter);
|
||||
signal_handler_disconnect(sh, "destroy",
|
||||
volmeter_source_destroyed, volmeter);
|
||||
|
||||
|
@ -597,3 +717,49 @@ signal_handler_t *obs_volmeter_get_signal_handler(obs_volmeter_t *volmeter)
|
|||
return (volmeter) ? volmeter->signals : NULL;
|
||||
}
|
||||
|
||||
void obs_volmeter_set_update_interval(obs_volmeter_t *volmeter,
|
||||
const unsigned int ms)
|
||||
{
|
||||
if (!volmeter || !ms)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&volmeter->mutex);
|
||||
volmeter->update_ms = ms;
|
||||
volmeter_update_audio_settings(volmeter);
|
||||
pthread_mutex_unlock(&volmeter->mutex);
|
||||
}
|
||||
|
||||
unsigned int obs_volmeter_get_update_interval(obs_volmeter_t *volmeter)
|
||||
{
|
||||
if (!volmeter)
|
||||
return 0;
|
||||
|
||||
pthread_mutex_lock(&volmeter->mutex);
|
||||
const unsigned int interval = volmeter->update_ms;
|
||||
pthread_mutex_unlock(&volmeter->mutex);
|
||||
|
||||
return interval;
|
||||
}
|
||||
|
||||
void obs_volmeter_set_peak_hold(obs_volmeter_t *volmeter, const unsigned int ms)
|
||||
{
|
||||
if (!volmeter)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&volmeter->mutex);
|
||||
volmeter->peakhold_ms = ms;
|
||||
volmeter_update_audio_settings(volmeter);
|
||||
pthread_mutex_unlock(&volmeter->mutex);
|
||||
}
|
||||
|
||||
unsigned int obs_volmeter_get_peak_hold(obs_volmeter_t *volmeter)
|
||||
{
|
||||
if (!volmeter)
|
||||
return 0;
|
||||
|
||||
pthread_mutex_lock(&volmeter->mutex);
|
||||
const unsigned int peakhold = volmeter->peakhold_ms;
|
||||
pthread_mutex_unlock(&volmeter->mutex);
|
||||
|
||||
return peakhold;
|
||||
}
|
||||
|
|
|
@ -208,6 +208,49 @@ EXPORT void obs_volmeter_detach_source(obs_volmeter_t *volmeter);
|
|||
EXPORT signal_handler_t *obs_volmeter_get_signal_handler(
|
||||
obs_volmeter_t *volmeter);
|
||||
|
||||
/**
|
||||
* @brief Set the update interval for the volume meter
|
||||
* @param volmeter pointer to the volume meter object
|
||||
* @param ms update interval in ms
|
||||
*
|
||||
* This sets the update interval in milliseconds that should be processed before
|
||||
* the resulting values are emitted by the levels_updated signal. The resulting
|
||||
* number of audio samples is rounded to an integer.
|
||||
*
|
||||
* Please note that due to way obs does receive audio data from the sources
|
||||
* this is no hard guarantee for the timing of the signal itself. When the
|
||||
* volume meter receives a chunk of data that is multiple the size of the sample
|
||||
* interval, all data will be sampled and the values updated accordingly, but
|
||||
* only the signal for the last segment is actually emitted.
|
||||
* On the other hand data might be received in a way that will cause the signal
|
||||
* to be emitted in shorter intervals than specified here under some
|
||||
* circumstances.
|
||||
*/
|
||||
EXPORT void obs_volmeter_set_update_interval(obs_volmeter_t *volmeter,
|
||||
const unsigned int ms);
|
||||
|
||||
/**
|
||||
* @brief Get the update interval currently used for the volume meter
|
||||
* @param volmeter pointer to the volume meter object
|
||||
* @return update interval in ms
|
||||
*/
|
||||
EXPORT unsigned int obs_volmeter_get_update_interval(obs_volmeter_t *volmeter);
|
||||
|
||||
/**
|
||||
* @brief Set the peak hold time for the volume meter
|
||||
* @param volmeter pointer to the volume meter object
|
||||
* @param ms peak hold time in ms
|
||||
*/
|
||||
EXPORT void obs_volmeter_set_peak_hold(obs_volmeter_t *volmeter,
|
||||
const unsigned int ms);
|
||||
|
||||
/**
|
||||
* @brief Get the peak hold time for the volume meter
|
||||
* @param volmeter pointer to the volume meter object
|
||||
* @return the peak hold time in ms
|
||||
*/
|
||||
EXPORT unsigned int obs_volmeter_get_peak_hold(obs_volmeter_t *volmeter);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -80,10 +80,9 @@ static const char *source_signals[] = {
|
|||
"void hide(ptr source)",
|
||||
"void rename(ptr source, string new_name, string prev_name)",
|
||||
"void volume(ptr source, in out float volume)",
|
||||
"void volume_level(ptr source, float level, float magnitude, "
|
||||
"float peak)",
|
||||
"void update_properties(ptr source)",
|
||||
"void update_flags(ptr source, int flags)",
|
||||
"void audio_data(ptr source, ptr data)",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -261,7 +260,7 @@ void obs_source_destroy(struct obs_source *source)
|
|||
pthread_mutex_destroy(&source->audio_mutex);
|
||||
pthread_mutex_destroy(&source->video_mutex);
|
||||
obs_context_data_free(&source->context);
|
||||
|
||||
|
||||
if (source->owns_info_id)
|
||||
bfree((void*)source->info.id);
|
||||
|
||||
|
@ -627,77 +626,19 @@ static inline void handle_ts_jump(obs_source_t *source, uint64_t expected,
|
|||
reset_audio_timing(source, ts, os_time);
|
||||
}
|
||||
|
||||
static void calc_volume_levels(struct obs_source *source, float *array,
|
||||
size_t frames, float volume)
|
||||
{
|
||||
float sum_val = 0.0f;
|
||||
float max_val = 0.0f;
|
||||
float rms_val = 0.0f;
|
||||
|
||||
audio_t *audio = obs_get_audio();
|
||||
const uint32_t sample_rate = audio_output_get_sample_rate(audio);
|
||||
const size_t channels = audio_output_get_channels(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 = (max_val > val_pow2) ? max_val : val_pow2;
|
||||
}
|
||||
|
||||
/*
|
||||
We want the volume meters scale linearly in respect to current
|
||||
volume, so, no need to apply volume here.
|
||||
*/
|
||||
|
||||
UNUSED_PARAMETER(volume);
|
||||
|
||||
rms_val = sqrtf(sum_val / (float)count);
|
||||
max_val = 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,
|
||||
static void source_signal_audio_data(obs_source_t *source,
|
||||
struct audio_data *in)
|
||||
{
|
||||
if (source && in) {
|
||||
struct calldata data = {0};
|
||||
struct calldata data;
|
||||
|
||||
calc_volume_levels(source, (float*)in->data[0], in->frames,
|
||||
in->volume);
|
||||
calldata_init(&data);
|
||||
|
||||
calldata_set_ptr (&data, "source", source);
|
||||
calldata_set_float(&data, "level", source->vol_max);
|
||||
calldata_set_float(&data, "magnitude", source->vol_mag);
|
||||
calldata_set_float(&data, "peak", source->vol_peak);
|
||||
calldata_set_ptr(&data, "source", source);
|
||||
calldata_set_ptr(&data, "data", in);
|
||||
|
||||
signal_handler_signal(source->context.signals, "volume_level",
|
||||
&data);
|
||||
signal_handler_signal(obs->signals, "source_volume_level",
|
||||
&data);
|
||||
signal_handler_signal(source->context.signals, "audio_data", &data);
|
||||
|
||||
calldata_free(&data);
|
||||
}
|
||||
calldata_free(&data);
|
||||
}
|
||||
|
||||
static inline uint64_t uint64_diff(uint64_t ts1, uint64_t ts2)
|
||||
|
@ -740,7 +681,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);
|
||||
source_signal_audio_data(source, &in);
|
||||
}
|
||||
|
||||
enum convert_type {
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
#define UPDATE_INTERVAL_MS 50
|
||||
|
||||
void VolControl::OBSVolumeChanged(void *data, calldata_t *calldata)
|
||||
{
|
||||
Q_UNUSED(calldata);
|
||||
|
@ -42,17 +40,7 @@ void VolControl::VolumeChanged()
|
|||
|
||||
void VolControl::VolumeLevel(float mag, float peak, float peakHold)
|
||||
{
|
||||
uint64_t curMeterTime = os_gettime_ns() / 1000000;
|
||||
|
||||
/*
|
||||
Add again peak averaging?
|
||||
*/
|
||||
|
||||
/* only update after a certain amount of time */
|
||||
if ((curMeterTime - lastMeterTime) > UPDATE_INTERVAL_MS) {
|
||||
lastMeterTime = curMeterTime;
|
||||
volMeter->setLevels(mag, peak, peakHold);
|
||||
}
|
||||
volMeter->setLevels(mag, peak, peakHold);
|
||||
}
|
||||
|
||||
void VolControl::SliderChanged(int vol)
|
||||
|
@ -74,7 +62,6 @@ void VolControl::SetName(const QString &newName)
|
|||
|
||||
VolControl::VolControl(OBSSource source_)
|
||||
: source (source_),
|
||||
lastMeterTime (0),
|
||||
levelTotal (0.0f),
|
||||
levelCount (0.0f),
|
||||
obs_fader (obs_fader_create(OBS_FADER_CUBIC)),
|
||||
|
|
|
@ -32,7 +32,6 @@ private:
|
|||
QLabel *volLabel;
|
||||
VolumeMeter *volMeter;
|
||||
QSlider *slider;
|
||||
uint64_t lastMeterTime;
|
||||
float levelTotal;
|
||||
float levelCount;
|
||||
obs_fader_t *obs_fader;
|
||||
|
|
Loading…
Reference in New Issue