From: Brian Matherly Date: Wed, 5 Feb 2014 04:38:07 +0000 (-0600) Subject: Add new audio loudness filter based on EBU R128 X-Git-Url: https://git.sesse.net/?p=mlt;a=commitdiff_plain;h=a4dc0cafe87d33184507f8369e408b9e7ebea4f4 Add new audio loudness filter based on EBU R128 --- diff --git a/src/modules/plus/Makefile b/src/modules/plus/Makefile index 44646655..8102a1b7 100644 --- a/src/modules/plus/Makefile +++ b/src/modules/plus/Makefile @@ -12,10 +12,12 @@ OBJS = consumer_blipflash.o \ filter_charcoal.o \ filter_dynamictext.o \ filter_invert.o \ + filter_loudness.o \ filter_sepia.o \ producer_blipflash.o \ producer_count.o \ - transition_affine.o + transition_affine.o \ + ebur128/ebur128.o SRCS := $(OBJS:.o=.c) diff --git a/src/modules/plus/ebur128/COPYING b/src/modules/plus/ebur128/COPYING new file mode 100644 index 00000000..11a48212 --- /dev/null +++ b/src/modules/plus/ebur128/COPYING @@ -0,0 +1,739 @@ + + + + + + + + + libebur128/COPYING at master · jiixyj/libebur128 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+
+ + + + + + + + + + +
+
+ + + + + +
+ + This repository + + +
+
+ +
+ + +
This repository
+
+ +
+ + +
All repositories
+
+ +
+
+
+ + + + + + + + +
+ +
+ + + + + + + + + + +
+
+ + + + + + + + +
+ +
+
+ + +
    + +
  • +
    + +
    + + + + + Watch + + + +
    +
    +
    + Notification status + +
    + + + +
    +
    +
    + +
    +
  • + +
  • + + + + +
  • + + +
  • + + Fork + + +
  • + + +
+ +

+ public + + + + + / + libebur128 + + + Octocat-spinner-32 + + +

+
+
+ +
+ + +
+ +
+ + + + +
+ + + + +
+

HTTPS clone URL

+
+ + + +
+
+ + + +
+

SSH clone URL

+
+ + + +
+
+ + + +
+

Subversion checkout URL

+
+ + + +
+
+ + +

You can clone with + HTTPS, + SSH, + or Subversion. + + + + + +

+ + + + + + Download ZIP + +
+
+ +
+ + + + + + + + + +
+ + +
+ + + branch: + master + + +
+ +
+
+ Switch branches/tags + +
+ +
+
+ +
+
+ +
+
+ +
+ +
+ + +
+ + master +
+
+ +
Nothing to show
+
+ +
+
+ + +
+ + v1.0.1 +
+
+ + v1.0.0 +
+
+ +
Nothing to show
+
+ +
+
+
+ + +
+ + + +
+ + + + + + + +
+ +
+
+
+
+ + file + 20 lines (16 sloc) + 1.059 kb +
+ + +
+
+ + + + + +
+ 1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 + + +
Copyright (c) 2011 Jan Kokemüller

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+
+
+ +
+
+ + + + +
+ +
+ +
+
+ + +
+ +
+ +
+ + +
+
+
+ +
+
+
+
+
+
+ +
+ + + +
+ + + Something went wrong with that request. Please try again. +
+ + + + diff --git a/src/modules/plus/ebur128/ebur128.c b/src/modules/plus/ebur128/ebur128.c new file mode 100644 index 00000000..d59f8e90 --- /dev/null +++ b/src/modules/plus/ebur128/ebur128.c @@ -0,0 +1,997 @@ +/* See COPYING file for copyright and license details. */ + +#include "ebur128.h" + +#include +#include +#include /* You may have to define _USE_MATH_DEFINES if you use MSVC */ +#include +#include + +/* This can be replaced by any BSD-like queue implementation. */ +#include + +#ifdef USE_SPEEX_RESAMPLER + #include +#endif + +#define CHECK_ERROR(condition, errorcode, goto_point) \ + if ((condition)) { \ + errcode = (errorcode); \ + goto goto_point; \ + } + +SLIST_HEAD(ebur128_double_queue, ebur128_dq_entry); +struct ebur128_dq_entry { + double z; + SLIST_ENTRY(ebur128_dq_entry) entries; +}; + +struct ebur128_state_internal { + /** Filtered audio data (used as ring buffer). */ + double* audio_data; + /** Size of audio_data array. */ + size_t audio_data_frames; + /** Current index for audio_data. */ + size_t audio_data_index; + /** How many frames are needed for a gating block. Will correspond to 400ms + * of audio at initialization, and 100ms after the first block (75% overlap + * as specified in the 2011 revision of BS1770). */ + unsigned long needed_frames; + /** The channel map. Has as many elements as there are channels. */ + int* channel_map; + /** How many samples fit in 100ms (rounded). */ + unsigned long samples_in_100ms; + /** BS.1770 filter coefficients (nominator). */ + double b[5]; + /** BS.1770 filter coefficients (denominator). */ + double a[5]; + /** BS.1770 filter state. */ + double v[5][5]; + /** Linked list of block energies. */ + struct ebur128_double_queue block_list; + /** Linked list of 3s-block energies, used to calculate LRA. */ + struct ebur128_double_queue short_term_block_list; + int use_histogram; + unsigned long *block_energy_histogram; + unsigned long *short_term_block_energy_histogram; + /** Keeps track of when a new short term block is needed. */ + size_t short_term_frame_counter; + /** Maximum sample peak, one per channel */ + double* sample_peak; + /** Maximum true peak, one per channel */ + double* true_peak; +#ifdef USE_SPEEX_RESAMPLER + SpeexResamplerState* resampler; +#endif + size_t oversample_factor; + float* resampler_buffer_input; + size_t resampler_buffer_input_frames; + float* resampler_buffer_output; + size_t resampler_buffer_output_frames; +}; + +static double relative_gate = -10.0; + +/* Those will be calculated when initializing the library */ +static double relative_gate_factor; +static double minus_twenty_decibels; +static double histogram_energies[1000]; +static double histogram_energy_boundaries[1001]; + +static void ebur128_init_filter(ebur128_state* st) { + int i, j; + + double f0 = 1681.974450955533; + double G = 3.999843853973347; + double Q = 0.7071752369554196; + + double K = tan(M_PI * f0 / (double) st->samplerate); + double Vh = pow(10.0, G / 20.0); + double Vb = pow(Vh, 0.4996667741545416); + + double pb[3] = {0.0, 0.0, 0.0}; + double pa[3] = {1.0, 0.0, 0.0}; + double rb[3] = {1.0, -2.0, 1.0}; + double ra[3] = {1.0, 0.0, 0.0}; + + double a0 = 1.0 + K / Q + K * K ; + pb[0] = (Vh + Vb * K / Q + K * K) / a0; + pb[1] = 2.0 * (K * K - Vh) / a0; + pb[2] = (Vh - Vb * K / Q + K * K) / a0; + pa[1] = 2.0 * (K * K - 1.0) / a0; + pa[2] = (1.0 - K / Q + K * K) / a0; + + /* fprintf(stderr, "%.14f %.14f %.14f %.14f %.14f\n", + b1[0], b1[1], b1[2], a1[1], a1[2]); */ + + f0 = 38.13547087602444; + Q = 0.5003270373238773; + K = tan(M_PI * f0 / (double) st->samplerate); + + ra[1] = 2.0 * (K * K - 1.0) / (1.0 + K / Q + K * K); + ra[2] = (1.0 - K / Q + K * K) / (1.0 + K / Q + K * K); + + /* fprintf(stderr, "%.14f %.14f\n", a2[1], a2[2]); */ + + st->d->b[0] = pb[0] * rb[0]; + st->d->b[1] = pb[0] * rb[1] + pb[1] * rb[0]; + st->d->b[2] = pb[0] * rb[2] + pb[1] * rb[1] + pb[2] * rb[0]; + st->d->b[3] = pb[1] * rb[2] + pb[2] * rb[1]; + st->d->b[4] = pb[2] * rb[2]; + + st->d->a[0] = pa[0] * ra[0]; + st->d->a[1] = pa[0] * ra[1] + pa[1] * ra[0]; + st->d->a[2] = pa[0] * ra[2] + pa[1] * ra[1] + pa[2] * ra[0]; + st->d->a[3] = pa[1] * ra[2] + pa[2] * ra[1]; + st->d->a[4] = pa[2] * ra[2]; + + for (i = 0; i < 5; ++i) { + for (j = 0; j < 5; ++j) { + st->d->v[i][j] = 0.0; + } + } +} + +static int ebur128_init_channel_map(ebur128_state* st) { + size_t i; + st->d->channel_map = (int*) malloc(st->channels * sizeof(int)); + if (!st->d->channel_map) return EBUR128_ERROR_NOMEM; + if (st->channels == 4) { + st->d->channel_map[0] = EBUR128_LEFT; + st->d->channel_map[1] = EBUR128_RIGHT; + st->d->channel_map[2] = EBUR128_LEFT_SURROUND; + st->d->channel_map[3] = EBUR128_RIGHT_SURROUND; + } else if (st->channels == 5) { + st->d->channel_map[0] = EBUR128_LEFT; + st->d->channel_map[1] = EBUR128_RIGHT; + st->d->channel_map[2] = EBUR128_CENTER; + st->d->channel_map[3] = EBUR128_LEFT_SURROUND; + st->d->channel_map[4] = EBUR128_RIGHT_SURROUND; + } else { + for (i = 0; i < st->channels; ++i) { + switch (i) { + case 0: st->d->channel_map[i] = EBUR128_LEFT; break; + case 1: st->d->channel_map[i] = EBUR128_RIGHT; break; + case 2: st->d->channel_map[i] = EBUR128_CENTER; break; + case 3: st->d->channel_map[i] = EBUR128_UNUSED; break; + case 4: st->d->channel_map[i] = EBUR128_LEFT_SURROUND; break; + case 5: st->d->channel_map[i] = EBUR128_RIGHT_SURROUND; break; + default: st->d->channel_map[i] = EBUR128_UNUSED; break; + } + } + } + return EBUR128_SUCCESS; +} + +#ifdef USE_SPEEX_RESAMPLER +static int ebur128_init_resampler(ebur128_state* st) { + int errcode = EBUR128_SUCCESS; + + if (st->samplerate < 96000) { + st->d->oversample_factor = 4; + } else if (st->samplerate < 192000) { + st->d->oversample_factor = 2; + } else { + st->d->oversample_factor = 1; + st->d->resampler_buffer_input = NULL; + st->d->resampler_buffer_output = NULL; + st->d->resampler = NULL; + } + + st->d->resampler_buffer_input_frames = st->d->samples_in_100ms * 4; + st->d->resampler_buffer_input = malloc(st->d->resampler_buffer_input_frames * + st->channels * + sizeof(float)); + CHECK_ERROR(!st->d->resampler_buffer_input, EBUR128_ERROR_NOMEM, exit) + + st->d->resampler_buffer_output_frames = + st->d->resampler_buffer_input_frames * + st->d->oversample_factor; + st->d->resampler_buffer_output = malloc + (st->d->resampler_buffer_output_frames * + st->channels * + sizeof(float)); + CHECK_ERROR(!st->d->resampler_buffer_output, EBUR128_ERROR_NOMEM, free_input) + + st->d->resampler = speex_resampler_init + ((spx_uint32_t) st->channels, + (spx_uint32_t) st->samplerate, + (spx_uint32_t) (st->samplerate * st->d->oversample_factor), + 8, NULL); + CHECK_ERROR(!st->d->resampler, EBUR128_ERROR_NOMEM, free_output) + + return errcode; + +free_output: + free(st->d->resampler_buffer_output); + st->d->resampler_buffer_output = NULL; +free_input: + free(st->d->resampler_buffer_input); + st->d->resampler_buffer_input = NULL; +exit: + return errcode; +} + +static void ebur128_destroy_resampler(ebur128_state* st) { + free(st->d->resampler_buffer_input); + st->d->resampler_buffer_input = NULL; + free(st->d->resampler_buffer_output); + st->d->resampler_buffer_output = NULL; + speex_resampler_destroy(st->d->resampler); + st->d->resampler = NULL; +} +#endif + +void ebur128_get_version(int* major, int* minor, int* patch) { + *major = EBUR128_VERSION_MAJOR; + *minor = EBUR128_VERSION_MINOR; + *patch = EBUR128_VERSION_PATCH; +} + +ebur128_state* ebur128_init(unsigned int channels, + unsigned long samplerate, + int mode) { + int errcode, result; + ebur128_state* st; + unsigned int i; + + st = (ebur128_state*) malloc(sizeof(ebur128_state)); + CHECK_ERROR(!st, 0, exit) + st->d = (struct ebur128_state_internal*) + malloc(sizeof(struct ebur128_state_internal)); + CHECK_ERROR(!st->d, 0, free_state) + st->channels = channels; + errcode = ebur128_init_channel_map(st); + CHECK_ERROR(errcode, 0, free_internal) + + st->d->sample_peak = (double*) malloc(channels * sizeof(double)); + CHECK_ERROR(!st->d->sample_peak, 0, free_channel_map) + st->d->true_peak = (double*) malloc(channels * sizeof(double)); + CHECK_ERROR(!st->d->true_peak, 0, free_sample_peak) + for (i = 0; i < channels; ++i) { + st->d->sample_peak[i] = 0.0; + st->d->true_peak[i] = 0.0; + } + + st->d->use_histogram = mode & EBUR128_MODE_HISTOGRAM ? 1 : 0; + + st->samplerate = samplerate; + st->d->samples_in_100ms = (st->samplerate + 5) / 10; + st->mode = mode; + if ((mode & EBUR128_MODE_S) == EBUR128_MODE_S) { + st->d->audio_data_frames = st->d->samples_in_100ms * 30; + } else if ((mode & EBUR128_MODE_M) == EBUR128_MODE_M) { + st->d->audio_data_frames = st->d->samples_in_100ms * 4; + } else { + return NULL; + } + st->d->audio_data = (double*) malloc(st->d->audio_data_frames * + st->channels * + sizeof(double)); + CHECK_ERROR(!st->d->audio_data, 0, free_true_peak) + ebur128_init_filter(st); + + if (st->d->use_histogram) { + st->d->block_energy_histogram = malloc(1000 * sizeof(unsigned long)); + CHECK_ERROR(!st->d->block_energy_histogram, 0, free_audio_data) + for (i = 0; i < 1000; ++i) { + st->d->block_energy_histogram[i] = 0; + } + } else { + st->d->block_energy_histogram = NULL; + } + if (st->d->use_histogram) { + st->d->short_term_block_energy_histogram = malloc(1000 * sizeof(unsigned long)); + CHECK_ERROR(!st->d->short_term_block_energy_histogram, 0, free_block_energy_histogram) + for (i = 0; i < 1000; ++i) { + st->d->short_term_block_energy_histogram[i] = 0; + } + } else { + st->d->short_term_block_energy_histogram = NULL; + } + SLIST_INIT(&st->d->block_list); + SLIST_INIT(&st->d->short_term_block_list); + st->d->short_term_frame_counter = 0; + +#ifdef USE_SPEEX_RESAMPLER + result = ebur128_init_resampler(st); + CHECK_ERROR(result, 0, free_short_term_block_energy_histogram) +#endif + + /* the first block needs 400ms of audio data */ + st->d->needed_frames = st->d->samples_in_100ms * 4; + /* start at the beginning of the buffer */ + st->d->audio_data_index = 0; + + /* initialize static constants */ + relative_gate_factor = pow(10.0, relative_gate / 10.0); + minus_twenty_decibels = pow(10.0, -20.0 / 10.0); + histogram_energy_boundaries[0] = pow(10.0, (-70.0 + 0.691) / 10.0); + if (st->d->use_histogram) { + for (i = 0; i < 1000; ++i) { + histogram_energies[i] = pow(10.0, ((double) i / 10.0 - 69.95 + 0.691) / 10.0); + } + for (i = 1; i < 1001; ++i) { + histogram_energy_boundaries[i] = pow(10.0, ((double) i / 10.0 - 70.0 + 0.691) / 10.0); + } + } + + return st; + +free_short_term_block_energy_histogram: + free(st->d->short_term_block_energy_histogram); +free_block_energy_histogram: + free(st->d->block_energy_histogram); +free_audio_data: + free(st->d->audio_data); +free_true_peak: + free(st->d->true_peak); +free_sample_peak: + free(st->d->sample_peak); +free_channel_map: + free(st->d->channel_map); +free_internal: + free(st->d); +free_state: + free(st); +exit: + return NULL; +} + +void ebur128_destroy(ebur128_state** st) { + struct ebur128_dq_entry* entry; + free((*st)->d->block_energy_histogram); + free((*st)->d->short_term_block_energy_histogram); + free((*st)->d->audio_data); + free((*st)->d->channel_map); + free((*st)->d->sample_peak); + free((*st)->d->true_peak); + while (!SLIST_EMPTY(&(*st)->d->block_list)) { + entry = SLIST_FIRST(&(*st)->d->block_list); + SLIST_REMOVE_HEAD(&(*st)->d->block_list, entries); + free(entry); + } + while (!SLIST_EMPTY(&(*st)->d->short_term_block_list)) { + entry = SLIST_FIRST(&(*st)->d->short_term_block_list); + SLIST_REMOVE_HEAD(&(*st)->d->short_term_block_list, entries); + free(entry); + } +#ifdef USE_SPEEX_RESAMPLER + ebur128_destroy_resampler(*st); +#endif + + free((*st)->d); + free(*st); + *st = NULL; +} + +static int ebur128_use_speex_resampler(ebur128_state* st) { +#ifdef USE_SPEEX_RESAMPLER + return ((st->mode & EBUR128_MODE_TRUE_PEAK) == EBUR128_MODE_TRUE_PEAK); +#else + (void) st; + return 0; +#endif +} + +static void ebur128_check_true_peak(ebur128_state* st, size_t frames) { +#ifdef USE_SPEEX_RESAMPLER + size_t c, i; + spx_uint32_t in_len = (spx_uint32_t) frames; + spx_uint32_t out_len = (spx_uint32_t) st->d->resampler_buffer_output_frames; + speex_resampler_process_interleaved_float( + st->d->resampler, + st->d->resampler_buffer_input, &in_len, + st->d->resampler_buffer_output, &out_len); + for (c = 0; c < st->channels; ++c) { + for (i = 0; i < out_len; ++i) { + if (st->d->resampler_buffer_output[i * st->channels + c] > + st->d->true_peak[c]) { + st->d->true_peak[c] = + st->d->resampler_buffer_output[i * st->channels + c]; + } else if (-st->d->resampler_buffer_output[i * st->channels + c] > + st->d->true_peak[c]) { + st->d->true_peak[c] = + -st->d->resampler_buffer_output[i * st->channels + c]; + } + } + } +#else + (void) st; (void) frames; +#endif +} + +#ifdef __SSE2_MATH__ +#include +#define TURN_ON_FTZ \ + unsigned int mxcsr = _mm_getcsr(); \ + _mm_setcsr(mxcsr | _MM_FLUSH_ZERO_ON); +#define TURN_OFF_FTZ _mm_setcsr(mxcsr); +#define FLUSH_MANUALLY +#else +#warning "manual FTZ is being used, please enable SSE2 (-msse2 -mfpmath=sse)" +#define TURN_ON_FTZ +#define TURN_OFF_FTZ +#define FLUSH_MANUALLY \ + st->d->v[ci][4] = fabs(st->d->v[ci][4]) < DBL_MIN ? 0.0 : st->d->v[ci][4]; \ + st->d->v[ci][3] = fabs(st->d->v[ci][3]) < DBL_MIN ? 0.0 : st->d->v[ci][3]; \ + st->d->v[ci][2] = fabs(st->d->v[ci][2]) < DBL_MIN ? 0.0 : st->d->v[ci][2]; \ + st->d->v[ci][1] = fabs(st->d->v[ci][1]) < DBL_MIN ? 0.0 : st->d->v[ci][1]; +#endif + +#define EBUR128_FILTER(type, min_scale, max_scale) \ +static void ebur128_filter_##type(ebur128_state* st, const type* src, \ + size_t frames) { \ + static double scaling_factor = -((double) min_scale) > (double) max_scale ? \ + -((double) min_scale) : (double) max_scale; \ + double* audio_data = st->d->audio_data + st->d->audio_data_index; \ + size_t i, c; \ + \ + TURN_ON_FTZ \ + \ + if ((st->mode & EBUR128_MODE_SAMPLE_PEAK) == EBUR128_MODE_SAMPLE_PEAK) { \ + for (c = 0; c < st->channels; ++c) { \ + double max = 0.0; \ + for (i = 0; i < frames; ++i) { \ + if (src[i * st->channels + c] > max) { \ + max = src[i * st->channels + c]; \ + } else if (-src[i * st->channels + c] > max) { \ + max = -1.0 * src[i * st->channels + c]; \ + } \ + } \ + max /= scaling_factor; \ + if (max > st->d->sample_peak[c]) st->d->sample_peak[c] = max; \ + } \ + } \ + if (ebur128_use_speex_resampler(st)) { \ + for (c = 0; c < st->channels; ++c) { \ + for (i = 0; i < frames; ++i) { \ + st->d->resampler_buffer_input[i * st->channels + c] = \ + (float) (src[i * st->channels + c] / scaling_factor); \ + } \ + } \ + ebur128_check_true_peak(st, frames); \ + } \ + for (c = 0; c < st->channels; ++c) { \ + int ci = st->d->channel_map[c] - 1; \ + if (ci < 0) continue; \ + else if (ci > 4) ci = 0; /* dual mono */ \ + for (i = 0; i < frames; ++i) { \ + st->d->v[ci][0] = (double) (src[i * st->channels + c] / scaling_factor) \ + - st->d->a[1] * st->d->v[ci][1] \ + - st->d->a[2] * st->d->v[ci][2] \ + - st->d->a[3] * st->d->v[ci][3] \ + - st->d->a[4] * st->d->v[ci][4]; \ + audio_data[i * st->channels + c] = \ + st->d->b[0] * st->d->v[ci][0] \ + + st->d->b[1] * st->d->v[ci][1] \ + + st->d->b[2] * st->d->v[ci][2] \ + + st->d->b[3] * st->d->v[ci][3] \ + + st->d->b[4] * st->d->v[ci][4]; \ + st->d->v[ci][4] = st->d->v[ci][3]; \ + st->d->v[ci][3] = st->d->v[ci][2]; \ + st->d->v[ci][2] = st->d->v[ci][1]; \ + st->d->v[ci][1] = st->d->v[ci][0]; \ + } \ + FLUSH_MANUALLY \ + } \ + TURN_OFF_FTZ \ +} +EBUR128_FILTER(short, SHRT_MIN, SHRT_MAX) +EBUR128_FILTER(int, INT_MIN, INT_MAX) +EBUR128_FILTER(float, -1.0f, 1.0f) +EBUR128_FILTER(double, -1.0, 1.0) + +static double ebur128_energy_to_loudness(double energy) { + return 10 * (log(energy) / log(10.0)) - 0.691; +} + +static size_t find_histogram_index(double energy) { + size_t index_min = 0; + size_t index_max = 1000; + size_t index_mid; + + do { + index_mid = (index_min + index_max) / 2; + if (energy >= histogram_energy_boundaries[index_mid]) { + index_min = index_mid; + } else { + index_max = index_mid; + } + } while (index_max - index_min != 1); + + return index_min; +} + +static int ebur128_calc_gating_block(ebur128_state* st, size_t frames_per_block, + double* optional_output) { + size_t i, c; + double sum = 0.0; + double channel_sum; + for (c = 0; c < st->channels; ++c) { + if (st->d->channel_map[c] == EBUR128_UNUSED) continue; + channel_sum = 0.0; + if (st->d->audio_data_index < frames_per_block * st->channels) { + for (i = 0; i < st->d->audio_data_index / st->channels; ++i) { + channel_sum += st->d->audio_data[i * st->channels + c] * + st->d->audio_data[i * st->channels + c]; + } + for (i = st->d->audio_data_frames - + (frames_per_block - + st->d->audio_data_index / st->channels); + i < st->d->audio_data_frames; ++i) { + channel_sum += st->d->audio_data[i * st->channels + c] * + st->d->audio_data[i * st->channels + c]; + } + } else { + for (i = st->d->audio_data_index / st->channels - frames_per_block; + i < st->d->audio_data_index / st->channels; + ++i) { + channel_sum += st->d->audio_data[i * st->channels + c] * + st->d->audio_data[i * st->channels + c]; + } + } + if (st->d->channel_map[c] == EBUR128_LEFT_SURROUND || + st->d->channel_map[c] == EBUR128_RIGHT_SURROUND) { + channel_sum *= 1.41; + } else if (st->d->channel_map[c] == EBUR128_DUAL_MONO) { + channel_sum *= 2.0; + } + sum += channel_sum; + } + sum /= (double) frames_per_block; + if (optional_output) { + *optional_output = sum; + return EBUR128_SUCCESS; + } else if (sum >= histogram_energy_boundaries[0]) { + if (st->d->use_histogram) { + ++st->d->block_energy_histogram[find_histogram_index(sum)]; + } else { + struct ebur128_dq_entry* block; + block = (struct ebur128_dq_entry*) malloc(sizeof(struct ebur128_dq_entry)); + if (!block) return EBUR128_ERROR_NOMEM; + block->z = sum; + SLIST_INSERT_HEAD(&st->d->block_list, block, entries); + } + return EBUR128_SUCCESS; + } else { + return EBUR128_SUCCESS; + } +} + +int ebur128_set_channel(ebur128_state* st, + unsigned int channel_number, + int value) { + if (channel_number >= st->channels) { + return 1; + } + if (value == EBUR128_DUAL_MONO && + (st->channels != 1 || channel_number != 0)) { + fprintf(stderr, "EBUR128_DUAL_MONO only works with mono files!\n"); + return 1; + } + st->d->channel_map[channel_number] = value; + return 0; +} + +int ebur128_change_parameters(ebur128_state* st, + unsigned int channels, + unsigned long samplerate) { + int errcode; + if (channels == st->channels && + samplerate == st->samplerate) { + return 2; + } + free(st->d->audio_data); + st->d->audio_data = NULL; + + if (channels != st->channels) { + unsigned int i; + + free(st->d->channel_map); st->d->channel_map = NULL; + free(st->d->sample_peak); st->d->sample_peak = NULL; + free(st->d->true_peak); st->d->true_peak = NULL; + st->channels = channels; + +#ifdef USE_SPEEX_RESAMPLER + ebur128_destroy_resampler(st); + ebur128_init_resampler(st); +#endif + + errcode = ebur128_init_channel_map(st); + CHECK_ERROR(errcode, EBUR128_ERROR_NOMEM, exit) + + st->d->sample_peak = (double*) malloc(channels * sizeof(double)); + CHECK_ERROR(!st->d->sample_peak, EBUR128_ERROR_NOMEM, exit) + st->d->true_peak = (double*) malloc(channels * sizeof(double)); + CHECK_ERROR(!st->d->true_peak, EBUR128_ERROR_NOMEM, exit) + for (i = 0; i < channels; ++i) { + st->d->sample_peak[i] = 0.0; + st->d->true_peak[i] = 0.0; + } + } + if (samplerate != st->samplerate) { + st->samplerate = samplerate; + ebur128_init_filter(st); + } + if ((st->mode & EBUR128_MODE_S) == EBUR128_MODE_S) { + st->d->audio_data_frames = st->d->samples_in_100ms * 30; + } else if ((st->mode & EBUR128_MODE_M) == EBUR128_MODE_M) { + st->d->audio_data_frames = st->d->samples_in_100ms * 4; + } else { + return 1; + } + st->d->audio_data = (double*) malloc(st->d->audio_data_frames * + st->channels * + sizeof(double)); + CHECK_ERROR(!st->d->audio_data, EBUR128_ERROR_NOMEM, exit) + + /* the first block needs 400ms of audio data */ + st->d->needed_frames = st->d->samples_in_100ms * 4; + /* start at the beginning of the buffer */ + st->d->audio_data_index = 0; + /* reset short term frame counter */ + st->d->short_term_frame_counter = 0; + + return 0; + +exit: + return 1; +} + + +static int ebur128_energy_shortterm(ebur128_state* st, double* out); +#define EBUR128_ADD_FRAMES(type) \ +int ebur128_add_frames_##type(ebur128_state* st, \ + const type* src, size_t frames) { \ + size_t src_index = 0; \ + while (frames > 0) { \ + if (frames >= st->d->needed_frames) { \ + ebur128_filter_##type(st, src + src_index, st->d->needed_frames); \ + src_index += st->d->needed_frames * st->channels; \ + frames -= st->d->needed_frames; \ + st->d->audio_data_index += st->d->needed_frames * st->channels; \ + /* calculate the new gating block */ \ + if ((st->mode & EBUR128_MODE_I) == EBUR128_MODE_I) { \ + if (ebur128_calc_gating_block(st, st->d->samples_in_100ms * 4, NULL)) {\ + return EBUR128_ERROR_NOMEM; \ + } \ + } \ + if ((st->mode & EBUR128_MODE_LRA) == EBUR128_MODE_LRA) { \ + st->d->short_term_frame_counter += st->d->needed_frames; \ + if (st->d->short_term_frame_counter == st->d->samples_in_100ms * 30) { \ + struct ebur128_dq_entry* block; \ + double st_energy; \ + ebur128_energy_shortterm(st, &st_energy); \ + if (st_energy >= histogram_energy_boundaries[0]) { \ + if (st->d->use_histogram) { \ + ++st->d->short_term_block_energy_histogram[ \ + find_histogram_index(st_energy)];\ + } else { \ + block = (struct ebur128_dq_entry*) \ + malloc(sizeof(struct ebur128_dq_entry)); \ + if (!block) return EBUR128_ERROR_NOMEM; \ + block->z = st_energy; \ + SLIST_INSERT_HEAD(&st->d->short_term_block_list, block, entries);\ + } \ + } \ + st->d->short_term_frame_counter = st->d->samples_in_100ms * 20; \ + } \ + } \ + /* 100ms are needed for all blocks besides the first one */ \ + st->d->needed_frames = st->d->samples_in_100ms; \ + /* reset audio_data_index when buffer full */ \ + if (st->d->audio_data_index == st->d->audio_data_frames * st->channels) {\ + st->d->audio_data_index = 0; \ + } \ + } else { \ + ebur128_filter_##type(st, src + src_index, frames); \ + st->d->audio_data_index += frames * st->channels; \ + if ((st->mode & EBUR128_MODE_LRA) == EBUR128_MODE_LRA) { \ + st->d->short_term_frame_counter += frames; \ + } \ + st->d->needed_frames -= frames; \ + frames = 0; \ + } \ + } \ + return EBUR128_SUCCESS; \ +} +EBUR128_ADD_FRAMES(short) +EBUR128_ADD_FRAMES(int) +EBUR128_ADD_FRAMES(float) +EBUR128_ADD_FRAMES(double) + +static int ebur128_gated_loudness(ebur128_state** sts, size_t size, + double* out) { + struct ebur128_dq_entry* it; + double relative_threshold = 0.0; + double gated_loudness = 0.0; + size_t above_thresh_counter = 0; + size_t i, j, start_index; + + for (i = 0; i < size; i++) { + if (sts[i] && (sts[i]->mode & EBUR128_MODE_I) != EBUR128_MODE_I) { + return EBUR128_ERROR_INVALID_MODE; + } + } + + for (i = 0; i < size; i++) { + if (!sts[i]) continue; + if (sts[i]->d->use_histogram) { + for (j = 0; j < 1000; ++j) { + relative_threshold += sts[i]->d->block_energy_histogram[j] * + histogram_energies[j]; + above_thresh_counter += sts[i]->d->block_energy_histogram[j]; + } + } else { + SLIST_FOREACH(it, &sts[i]->d->block_list, entries) { + ++above_thresh_counter; + relative_threshold += it->z; + } + } + } + if (!above_thresh_counter) { + *out = -HUGE_VAL; + return EBUR128_SUCCESS; + } + relative_threshold /= (double) above_thresh_counter; + relative_threshold *= relative_gate_factor; + above_thresh_counter = 0; + if (relative_threshold < histogram_energy_boundaries[0]) { + start_index = 0; + } else { + start_index = find_histogram_index(relative_threshold); + if (relative_threshold > histogram_energies[start_index]) { + ++start_index; + } + } + for (i = 0; i < size; i++) { + if (!sts[i]) continue; + if (sts[i]->d->use_histogram) { + for (j = start_index; j < 1000; ++j) { + gated_loudness += sts[i]->d->block_energy_histogram[j] * + histogram_energies[j]; + above_thresh_counter += sts[i]->d->block_energy_histogram[j]; + } + } else { + SLIST_FOREACH(it, &sts[i]->d->block_list, entries) { + if (it->z >= relative_threshold) { + ++above_thresh_counter; + gated_loudness += it->z; + } + } + } + } + if (!above_thresh_counter) { + *out = -HUGE_VAL; + return EBUR128_SUCCESS; + } + gated_loudness /= (double) above_thresh_counter; + *out = ebur128_energy_to_loudness(gated_loudness); + return EBUR128_SUCCESS; +} + +int ebur128_loudness_global(ebur128_state* st, double* out) { + return ebur128_gated_loudness(&st, 1, out); +} + +int ebur128_loudness_global_multiple(ebur128_state** sts, size_t size, + double* out) { + return ebur128_gated_loudness(sts, size, out); +} + +static int ebur128_energy_in_interval(ebur128_state* st, + size_t interval_frames, + double* out) { + if (interval_frames > st->d->audio_data_frames) { + return EBUR128_ERROR_INVALID_MODE; + } + ebur128_calc_gating_block(st, interval_frames, out); + return EBUR128_SUCCESS; +} + +static int ebur128_energy_shortterm(ebur128_state* st, double* out) { + return ebur128_energy_in_interval(st, st->d->samples_in_100ms * 30, out); +} + +int ebur128_loudness_momentary(ebur128_state* st, double* out) { + double energy; + int error = ebur128_energy_in_interval(st, st->d->samples_in_100ms * 4, + &energy); + if (error) { + return error; + } else if (energy <= 0.0) { + *out = -HUGE_VAL; + return EBUR128_SUCCESS; + } + *out = ebur128_energy_to_loudness(energy); + return EBUR128_SUCCESS; +} + +int ebur128_loudness_shortterm(ebur128_state* st, double* out) { + double energy; + int error = ebur128_energy_shortterm(st, &energy); + if (error) { + return error; + } else if (energy <= 0.0) { + *out = -HUGE_VAL; + return EBUR128_SUCCESS; + } + *out = ebur128_energy_to_loudness(energy); + return EBUR128_SUCCESS; +} + +static int ebur128_double_cmp(const void *p1, const void *p2) { + const double* d1 = (const double*) p1; + const double* d2 = (const double*) p2; + return (*d1 > *d2) - (*d1 < *d2); +} + +/* EBU - TECH 3342 */ +int ebur128_loudness_range_multiple(ebur128_state** sts, size_t size, + double* out) { + size_t i, j; + struct ebur128_dq_entry* it; + double* stl_vector; + size_t stl_size; + double* stl_relgated; + size_t stl_relgated_size; + double stl_power, stl_integrated; + /* High and low percentile energy */ + double h_en, l_en; + int use_histogram = 0; + + for (i = 0; i < size; ++i) { + if (sts[i]) { + if ((sts[i]->mode & EBUR128_MODE_LRA) != EBUR128_MODE_LRA) { + return EBUR128_ERROR_INVALID_MODE; + } + if (i == 0 && sts[i]->mode & EBUR128_MODE_HISTOGRAM) { + use_histogram = 1; + } else if (use_histogram != !!(sts[i]->mode & EBUR128_MODE_HISTOGRAM)) { + return EBUR128_ERROR_INVALID_MODE; + } + } + } + + if (use_histogram) { + unsigned long hist[1000] = { 0 }; + size_t percentile_low, percentile_high; + size_t index; + + stl_size = 0; + stl_power = 0.0; + for (i = 0; i < size; ++i) { + if (!sts[i]) continue; + for (j = 0; j < 1000; ++j) { + hist[j] += sts[i]->d->short_term_block_energy_histogram[j]; + stl_size += sts[i]->d->short_term_block_energy_histogram[j]; + stl_power += sts[i]->d->short_term_block_energy_histogram[j] + * histogram_energies[j]; + } + } + if (!stl_size) { + *out = 0.0; + return EBUR128_SUCCESS; + } + + stl_power /= stl_size; + stl_integrated = minus_twenty_decibels * stl_power; + + if (stl_integrated < histogram_energy_boundaries[0]) { + index = 0; + } else { + index = find_histogram_index(stl_integrated); + if (stl_integrated > histogram_energies[index]) { + ++index; + } + } + stl_size = 0; + for (j = index; j < 1000; ++j) { + stl_size += hist[j]; + } + if (!stl_size) { + *out = 0.0; + return EBUR128_SUCCESS; + } + + percentile_low = (size_t) ((stl_size - 1) * 0.1 + 0.5); + percentile_high = (size_t) ((stl_size - 1) * 0.95 + 0.5); + + stl_size = 0; + j = index; + while (stl_size <= percentile_low) { + stl_size += hist[j++]; + } + l_en = histogram_energies[j - 1]; + while (stl_size <= percentile_high) { + stl_size += hist[j++]; + } + h_en = histogram_energies[j - 1]; + *out = ebur128_energy_to_loudness(h_en) - ebur128_energy_to_loudness(l_en); + return EBUR128_SUCCESS; + + } else { + stl_size = 0; + for (i = 0; i < size; ++i) { + if (!sts[i]) continue; + SLIST_FOREACH(it, &sts[i]->d->short_term_block_list, entries) { + ++stl_size; + } + } + if (!stl_size) { + *out = 0.0; + return EBUR128_SUCCESS; + } + stl_vector = (double*) malloc(stl_size * sizeof(double)); + if (!stl_vector) + return EBUR128_ERROR_NOMEM; + + for (j = 0, i = 0; i < size; ++i) { + if (!sts[i]) continue; + SLIST_FOREACH(it, &sts[i]->d->short_term_block_list, entries) { + stl_vector[j] = it->z; + ++j; + } + } + qsort(stl_vector, stl_size, sizeof(double), ebur128_double_cmp); + stl_power = 0.0; + for (i = 0; i < stl_size; ++i) { + stl_power += stl_vector[i]; + } + stl_power /= (double) stl_size; + stl_integrated = minus_twenty_decibels * stl_power; + + stl_relgated = stl_vector; + stl_relgated_size = stl_size; + while (stl_relgated_size > 0 && *stl_relgated < stl_integrated) { + ++stl_relgated; + --stl_relgated_size; + } + + if (stl_relgated_size) { + h_en = stl_relgated[(size_t) ((stl_relgated_size - 1) * 0.95 + 0.5)]; + l_en = stl_relgated[(size_t) ((stl_relgated_size - 1) * 0.1 + 0.5)]; + free(stl_vector); + *out = ebur128_energy_to_loudness(h_en) - ebur128_energy_to_loudness(l_en); + return EBUR128_SUCCESS; + } else { + free(stl_vector); + *out = 0.0; + return EBUR128_SUCCESS; + } + } +} + +int ebur128_loudness_range(ebur128_state* st, double* out) { + return ebur128_loudness_range_multiple(&st, 1, out); +} + +int ebur128_sample_peak(ebur128_state* st, + unsigned int channel_number, + double* out) { + if ((st->mode & EBUR128_MODE_SAMPLE_PEAK) != EBUR128_MODE_SAMPLE_PEAK) { + return EBUR128_ERROR_INVALID_MODE; + } else if (channel_number >= st->channels) { + return EBUR128_ERROR_INVALID_CHANNEL_INDEX; + } + *out = st->d->sample_peak[channel_number]; + return EBUR128_SUCCESS; +} + +#ifdef USE_SPEEX_RESAMPLER +int ebur128_true_peak(ebur128_state* st, + unsigned int channel_number, + double* out) { + if ((st->mode & EBUR128_MODE_TRUE_PEAK) != EBUR128_MODE_TRUE_PEAK) { + return EBUR128_ERROR_INVALID_MODE; + } else if (channel_number >= st->channels) { + return EBUR128_ERROR_INVALID_CHANNEL_INDEX; + } + *out = st->d->true_peak[channel_number] > st->d->sample_peak[channel_number] + ? st->d->true_peak[channel_number] + : st->d->sample_peak[channel_number]; + return EBUR128_SUCCESS; +} +#endif diff --git a/src/modules/plus/ebur128/ebur128.h b/src/modules/plus/ebur128/ebur128.h new file mode 100644 index 00000000..02be47fe --- /dev/null +++ b/src/modules/plus/ebur128/ebur128.h @@ -0,0 +1,288 @@ +/* See COPYING file for copyright and license details. */ + +#ifndef EBUR128_H_ +#define EBUR128_H_ + +/** \file ebur128.h + * \brief libebur128 - a library for loudness measurement according to + * the EBU R128 standard. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#define EBUR128_VERSION_MAJOR 1 +#define EBUR128_VERSION_MINOR 0 +#define EBUR128_VERSION_PATCH 1 + +#include /* for size_t */ + +/** \enum channel + * Use these values when setting the channel map with ebur128_set_channel(). + */ +enum channel { + EBUR128_UNUSED = 0, /**< unused channel (for example LFE channel) */ + EBUR128_LEFT, /**< left channel */ + EBUR128_RIGHT, /**< right channel */ + EBUR128_CENTER, /**< center channel */ + EBUR128_LEFT_SURROUND, /**< left surround channel */ + EBUR128_RIGHT_SURROUND, /**< right surround channel */ + EBUR128_DUAL_MONO /**< a channel that is counted twice */ +}; + +/** \enum error + * Error return values. + */ +enum error { + EBUR128_SUCCESS = 0, + EBUR128_ERROR_NOMEM, + EBUR128_ERROR_INVALID_MODE, + EBUR128_ERROR_INVALID_CHANNEL_INDEX, + EBUR128_ERROR_NO_CHANGE +}; + +/** \enum mode + * Use these values in ebur128_init (or'ed). Try to use the lowest possible + * modes that suit your needs, as performance will be better. + */ +enum mode { + /** can call ebur128_loudness_momentary */ + EBUR128_MODE_M = (1 << 0), + /** can call ebur128_loudness_shortterm */ + EBUR128_MODE_S = (1 << 1) | EBUR128_MODE_M, + /** can call ebur128_gated_loudness_* */ + EBUR128_MODE_I = (1 << 2) | EBUR128_MODE_M, + /** can call ebur128_loudness_range */ + EBUR128_MODE_LRA = (1 << 3) | EBUR128_MODE_S, + /** can call ebur128_sample_peak */ + EBUR128_MODE_SAMPLE_PEAK = (1 << 4) | EBUR128_MODE_M, + /** can call ebur128_true_peak */ + EBUR128_MODE_TRUE_PEAK = (1 << 5) | EBUR128_MODE_M + | EBUR128_MODE_SAMPLE_PEAK, + /** uses histogram algorithm to calculate loudness */ + EBUR128_MODE_HISTOGRAM = (1 << 6) +}; + +/** forward declaration of ebur128_state_internal */ +struct ebur128_state_internal; + +/** \brief Contains information about the state of a loudness measurement. + * + * You should not need to modify this struct directly. + */ +typedef struct { + int mode; /**< The current mode. */ + unsigned int channels; /**< The number of channels. */ + unsigned long samplerate; /**< The sample rate. */ + struct ebur128_state_internal* d; /**< Internal state. */ +} ebur128_state; + +/** \brief Get library version number. Do not pass null pointers here. + * + * @param major major version number of library + * @param minor minor version number of library + * @param patch patch version number of library + */ +void ebur128_get_version(int* major, int* minor, int* patch); + +/** \brief Initialize library state. + * + * @param channels the number of channels. + * @param samplerate the sample rate. + * @param mode see the mode enum for possible values. + * @return an initialized library state. + */ +ebur128_state* ebur128_init(unsigned int channels, + unsigned long samplerate, + int mode); + +/** \brief Destroy library state. + * + * @param st pointer to a library state. + */ +void ebur128_destroy(ebur128_state** st); + +/** \brief Set channel type. + * + * The default is: + * - 0 -> EBUR128_LEFT + * - 1 -> EBUR128_RIGHT + * - 2 -> EBUR128_CENTER + * - 3 -> EBUR128_UNUSED + * - 4 -> EBUR128_LEFT_SURROUND + * - 5 -> EBUR128_RIGHT_SURROUND + * + * @param st library state. + * @param channel_number zero based channel index. + * @param value channel type from the "channel" enum. + * @return + * - EBUR128_SUCCESS on success. + * - EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index. + */ +int ebur128_set_channel(ebur128_state* st, + unsigned int channel_number, + int value); + +/** \brief Change library parameters. + * + * Note that the channel map will be reset when setting a different number of + * channels. The current unfinished block will be lost. + * + * @param st library state. + * @param channels new number of channels. + * @param samplerate new sample rate. + * @return + * - EBUR128_SUCCESS on success. + * - EBUR128_ERROR_NOMEM on memory allocation error. The state will be + * invalid and must be destroyed. + * - EBUR128_ERROR_NO_CHANGE if channels and sample rate were not changed. + */ +int ebur128_change_parameters(ebur128_state* st, + unsigned int channels, + unsigned long samplerate); + +/** \brief Add frames to be processed. + * + * @param st library state. + * @param src array of source frames. Channels must be interleaved. + * @param frames number of frames. Not number of samples! + * @return + * - EBUR128_SUCCESS on success. + * - EBUR128_ERROR_NOMEM on memory allocation error. + */ +int ebur128_add_frames_short(ebur128_state* st, + const short* src, + size_t frames); +/** \brief See \ref ebur128_add_frames_short */ +int ebur128_add_frames_int(ebur128_state* st, + const int* src, + size_t frames); +/** \brief See \ref ebur128_add_frames_short */ +int ebur128_add_frames_float(ebur128_state* st, + const float* src, + size_t frames); +/** \brief See \ref ebur128_add_frames_short */ +int ebur128_add_frames_double(ebur128_state* st, + const double* src, + size_t frames); + +/** \brief Get global integrated loudness in LUFS. + * + * @param st library state. + * @param out integrated loudness in LUFS. -HUGE_VAL if result is negative + * infinity. + * @return + * - EBUR128_SUCCESS on success. + * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_I" has not been set. + */ +int ebur128_loudness_global(ebur128_state* st, double* out); +/** \brief Get global integrated loudness in LUFS across multiple instances. + * + * @param sts array of library states. + * @param size length of sts + * @param out integrated loudness in LUFS. -HUGE_VAL if result is negative + * infinity. + * @return + * - EBUR128_SUCCESS on success. + * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_I" has not been set. + */ +int ebur128_loudness_global_multiple(ebur128_state** sts, + size_t size, + double* out); + +/** \brief Get momentary loudness (last 400ms) in LUFS. + * + * @param st library state. + * @param out momentary loudness in LUFS. -HUGE_VAL if result is negative + * infinity. + * @return + * - EBUR128_SUCCESS on success. + */ +int ebur128_loudness_momentary(ebur128_state* st, double* out); +/** \brief Get short-term loudness (last 3s) in LUFS. + * + * @param st library state. + * @param out short-term loudness in LUFS. -HUGE_VAL if result is negative + * infinity. + * @return + * - EBUR128_SUCCESS on success. + * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_S" has not been set. + */ +int ebur128_loudness_shortterm(ebur128_state* st, double* out); + +/** \brief Get loudness range (LRA) of programme in LU. + * + * Calculates loudness range according to EBU 3342. + * + * @param st library state. + * @param out loudness range (LRA) in LU. Will not be changed in case of + * error. EBUR128_ERROR_NOMEM or EBUR128_ERROR_INVALID_MODE will be + * returned in this case. + * @return + * - EBUR128_SUCCESS on success. + * - EBUR128_ERROR_NOMEM in case of memory allocation error. + * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_LRA" has not been set. + */ +int ebur128_loudness_range(ebur128_state* st, double* out); +/** \brief Get loudness range (LRA) in LU across multiple instances. + * + * Calculates loudness range according to EBU 3342. + * + * @param sts array of library states. + * @param size length of sts + * @param out loudness range (LRA) in LU. Will not be changed in case of + * error. EBUR128_ERROR_NOMEM or EBUR128_ERROR_INVALID_MODE will be + * returned in this case. + * @return + * - EBUR128_SUCCESS on success. + * - EBUR128_ERROR_NOMEM in case of memory allocation error. + * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_LRA" has not been set. + */ +int ebur128_loudness_range_multiple(ebur128_state** sts, + size_t size, + double* out); + +/** \brief Get maximum sample peak of selected channel in float format. + * + * @param st library state + * @param channel_number channel to analyse + * @param out maximum sample peak in float format (1.0 is 0 dBFS) + * @return + * - EBUR128_SUCCESS on success. + * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_SAMPLE_PEAK" has not + * been set. + * - EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index. + */ +int ebur128_sample_peak(ebur128_state* st, + unsigned int channel_number, + double* out); + +/** \brief Get maximum true peak of selected channel in float format. + * + * Uses an implementation defined algorithm to calculate the true peak. Do not + * try to compare resulting values across different versions of the library, + * as the algorithm may change. + * + * The current implementation uses the Speex resampler with quality level 8 to + * calculate true peak. Will oversample 4x for sample rates < 96000 Hz, 2x for + * sample rates < 192000 Hz and leave the signal unchanged for 192000 Hz. + * + * @param st library state + * @param channel_number channel to analyse + * @param out maximum true peak in float format (1.0 is 0 dBFS) + * @return + * - EBUR128_SUCCESS on success. + * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_TRUE_PEAK" has not + * been set. + * - EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index. + */ +int ebur128_true_peak(ebur128_state* st, + unsigned int channel_number, + double* out); + +#ifdef __cplusplus +} +#endif + +#endif /* EBUR128_H_ */ diff --git a/src/modules/plus/factory.c b/src/modules/plus/factory.c index 949b4269..a7399571 100644 --- a/src/modules/plus/factory.c +++ b/src/modules/plus/factory.c @@ -26,6 +26,7 @@ extern mlt_consumer consumer_blipflash_init( mlt_profile profile, mlt_service_ty extern mlt_filter filter_affine_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ); extern mlt_filter filter_charcoal_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ); extern mlt_filter filter_dynamictext_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ); +extern mlt_filter filter_loudness_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ); extern mlt_filter filter_invert_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ); extern mlt_filter filter_sepia_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ); extern mlt_producer producer_blipflash_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ); @@ -46,6 +47,7 @@ MLT_REPOSITORY MLT_REGISTER( filter_type, "charcoal", filter_charcoal_init ); MLT_REGISTER( filter_type, "dynamictext", filter_dynamictext_init ); MLT_REGISTER( filter_type, "invert", filter_invert_init ); + MLT_REGISTER( filter_type, "loudness", filter_loudness_init ); MLT_REGISTER( filter_type, "sepia", filter_sepia_init ); MLT_REGISTER( producer_type, "blipflash", producer_blipflash_init ); MLT_REGISTER( producer_type, "count", producer_count_init ); @@ -56,6 +58,7 @@ MLT_REPOSITORY MLT_REGISTER_METADATA( filter_type, "charcoal", metadata, "filter_charcoal.yml" ); MLT_REGISTER_METADATA( filter_type, "dynamictext", metadata, "filter_dynamictext.yml" ); MLT_REGISTER_METADATA( filter_type, "invert", metadata, "filter_invert.yml" ); + MLT_REGISTER_METADATA( filter_type, "loudness", metadata, "filter_loudness.yml" ); MLT_REGISTER_METADATA( filter_type, "sepia", metadata, "filter_sepia.yml" ); MLT_REGISTER_METADATA( producer_type, "blipflash", metadata, "producer_blipflash.yml" ); MLT_REGISTER_METADATA( producer_type, "count", metadata, "producer_count.yml" ); diff --git a/src/modules/plus/filter_loudness.c b/src/modules/plus/filter_loudness.c new file mode 100644 index 00000000..fb8683f4 --- /dev/null +++ b/src/modules/plus/filter_loudness.c @@ -0,0 +1,268 @@ +/* + * filter_ebur128.c -- normalize audio according to EBU R128 + * Copyright (C) 2014 Brian Matherly + * Author: Brian Matherly + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include "ebur128/ebur128.h" + +#define MAX_RESULT_SIZE 512 + +typedef struct +{ + ebur128_state* state; +} analyze_data; + +typedef struct +{ + double in_loudness; + double in_range; + double in_peak; + double coeff; +} apply_data; + +typedef struct +{ + analyze_data* analyze; + apply_data* apply; + mlt_position last_position; +} private_data; + +static void destroy_analyze_data( mlt_filter filter ) +{ + private_data* private = (private_data*)filter->child; + ebur128_destroy( &private->analyze->state ); + free( private->analyze ); + private->analyze = NULL; +} + +static void init_analyze_data( mlt_filter filter, int channels, int samplerate ) +{ + private_data* private = (private_data*)filter->child; + private->analyze = (analyze_data*)calloc( 1, sizeof(analyze_data) ); + private->analyze->state = ebur128_init( (unsigned int)channels, (unsigned long)samplerate, EBUR128_MODE_I | EBUR128_MODE_LRA | EBUR128_MODE_SAMPLE_PEAK ); + private->last_position = 0; +} + +static void destroy_apply_data( mlt_filter filter ) +{ + private_data* private = (private_data*)filter->child; + free( private->apply ); + private->apply = NULL; +} + +static void init_apply_data( mlt_filter filter ) +{ + private_data* private = (private_data*)filter->child; + mlt_properties properties = MLT_FILTER_PROPERTIES( filter ); + char* results = mlt_properties_get( properties, "results" ); + int scan_return = 0; + + private->apply = (apply_data*)calloc( 1, sizeof(apply_data) ); + + scan_return = sscanf( results,"L: %lf\tR: %lf\tP %lf\n", &private->apply->in_loudness, &private->apply->in_range, &private->apply->in_peak ); + if( scan_return != 3 ) + { + mlt_log_error( MLT_FILTER_SERVICE( filter ), "Unable to load results: %s\n", results ); + destroy_apply_data( filter ); + return; + } + else + { + double target_db = mlt_properties_get_double( properties, "program" ); + double delta_db = target_db - private->apply->in_loudness; + private->apply->coeff = delta_db > -90.0f ? powf(10.0f, delta_db * 0.05f) : 0.0f; + mlt_log_info( MLT_FILTER_SERVICE( filter ), "Loaded Results: L: %lf\tR: %lf\tP %lf\n", private->apply->in_loudness, private->apply->in_range, private->apply->in_peak ); + mlt_log_info( MLT_FILTER_SERVICE( filter ), "Coefficient: %lf\n", private->apply->coeff ); + } +} + +static void analyze( mlt_filter filter, mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + private_data* private = (private_data*)filter->child; + mlt_position pos = mlt_filter_get_position( filter, frame ); + + // If any frames are skipped, analysis data will be incomplete. + if( private->analyze && pos != private->last_position + 1 ) + { + mlt_log_error( MLT_FILTER_SERVICE(filter), "Analysis Failed: Bad frame sequence\n" ); + destroy_analyze_data( filter ); + } + + // Analyze Audio + if( !private->analyze && pos == 0 ) + { + init_analyze_data( filter, *channels, *frequency ); + } + + if( private->analyze ) + { + ebur128_add_frames_float( private->analyze->state, *buffer, *samples ); + + if ( pos + 1 == mlt_filter_get_length2( filter, frame ) ) + { + mlt_properties properties = MLT_FILTER_PROPERTIES( filter ); + double loudness = 0.0; + double range = 0.0; + double tmpPeak = 0.0; + double peak = 0.0; + int i = 0; + char result[MAX_RESULT_SIZE]; + ebur128_loudness_global( private->analyze->state, &loudness ); + ebur128_loudness_range( private->analyze->state, &range ); + + for ( i = 0; i < *channels; i++ ) + { + ebur128_sample_peak( private->analyze->state, i, &tmpPeak ); + if( tmpPeak > peak ) + { + peak = tmpPeak; + } + } + + snprintf( result, MAX_RESULT_SIZE, "L: %lf\tR: %lf\tP %lf\n", loudness, range, peak ); + result[ MAX_RESULT_SIZE - 1 ] = '\0'; + mlt_log_info( MLT_FILTER_SERVICE( filter ), "Stored results: %s", result ); + mlt_properties_set( properties, "results", result ); + destroy_analyze_data( filter ); + } + + private->last_position = pos; + } +} + +static void apply( mlt_filter filter, mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + private_data* private = (private_data*)filter->child; + + // Analyze Audio + if( !private->apply ) + { + init_apply_data( filter ); + } + + if( private->apply ) + { + float* p = *buffer; + int count = *samples * *channels; + while( count-- ) + { + *p = *p * private->apply->coeff; + p++; + } + } +} + +/** Get the audio. +*/ + +static int filter_get_audio( mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + mlt_filter filter = mlt_frame_pop_audio( frame ); + mlt_properties properties = MLT_FILTER_PROPERTIES( filter ); + + mlt_service_lock( MLT_FILTER_SERVICE( filter ) ); + + // Get the producer's audio + *format = mlt_audio_f32le; + mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples ); + + char* results = mlt_properties_get( properties, "results" ); + if( results && strcmp( results, "" ) ) + { + apply( filter, frame, buffer, format, frequency, channels, samples ); + } + else + { + analyze( filter, frame, buffer, format, frequency, channels, samples ); + } + + mlt_service_unlock( MLT_FILTER_SERVICE( filter ) ); + + return 0; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter filter, mlt_frame frame ) +{ + mlt_frame_push_audio( frame, filter ); + mlt_frame_push_audio( frame, filter_get_audio ); + return frame; +} + +/** Destructor for the filter. +*/ + +static void filter_close( mlt_filter filter ) +{ + private_data* private = (private_data*)filter->child; + + if ( private ) + { + if ( private->analyze ) + { + destroy_analyze_data( filter ); + } + free( private ); + } + filter->child = NULL; + filter->close = NULL; + filter->parent.close = NULL; + mlt_service_close( &filter->parent ); +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_loudness_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ) +{ + mlt_filter filter = mlt_filter_new( ); + private_data* data = (private_data*)calloc( 1, sizeof(private_data) ); + + if ( filter && data ) + { + mlt_properties properties = MLT_FILTER_PROPERTIES( filter ); + mlt_properties_set( properties, "program", "-23.0" ); + + data->analyze = NULL; + + filter->close = filter_close; + filter->process = filter_process; + filter->child = data; + } + else + { + if( filter ) + { + mlt_filter_close( filter ); + filter = NULL; + } + + if( data ) + { + free( data ); + } + } + + return filter; +} diff --git a/src/modules/plus/filter_loudness.yml b/src/modules/plus/filter_loudness.yml new file mode 100644 index 00000000..c5398e86 --- /dev/null +++ b/src/modules/plus/filter_loudness.yml @@ -0,0 +1,41 @@ +schema_version: 0.1 +type: filter +identifier: loudness +title: Loudness +version: 1 +copyright: Brian Matherly +creator: Brian Matherly +license: LGPLv2.1 +language: en +tags: + - Audio +description: Correct audio loudness as recommended by EBU R128. +notes: > + This filter requires two passes. The first pass performs analysis and stores + the result in the "results" property. The second pass applies the results to + the audio in order to achieve the desired loudness over the range of the + filter. + +parameters: + - identifier: results + title: Analysis Results + type: string + description: > + Set after analysis. Used during application. + Loudness information about the original audio. + When results are not supplied, the filter computes the results and stores + them in this property when the last frame has been processed. + mutable: no + + - identifier: program + title: Target Program Loudness + type: float + description: > + Used during application. + The target program loudness in LUFS (Loudness Units Full Scale). + readonly: no + mutable: no + default: -23.0 + minimum: -50.0 + maximum: -10.0 + unit: LUFS