pax_global_header00006660000000000000000000000064140122320510014500gustar00rootroot0000000000000052 comment=67b33abe1558160ed76ada1322329b0e9e058b02 libebur128-1.2.6/000077500000000000000000000000001401223205100133655ustar00rootroot00000000000000libebur128-1.2.6/.clang-format000066400000000000000000000002711401223205100157400ustar00rootroot00000000000000BasedOnStyle: LLVM AllowShortCaseLabelsOnASingleLine: true BinPackParameters: false Cpp11BracedListStyle: false IndentCaseLabels: true PointerAlignment: Left SpaceAfterCStyleCast: true libebur128-1.2.6/.github/000077500000000000000000000000001401223205100147255ustar00rootroot00000000000000libebur128-1.2.6/.github/workflows/000077500000000000000000000000001401223205100167625ustar00rootroot00000000000000libebur128-1.2.6/.github/workflows/build.yml000066400000000000000000000026361401223205100206130ustar00rootroot00000000000000name: build on: push: pull_request: jobs: build: strategy: matrix: include: - name: Ubuntu 20.04 os: ubuntu-20.04 install_dir: ~/libebur128 cmake_extras: -DCMAKE_BUILD_TYPE=RelWithDebInfo - name: macOS 10.15 os: macos-10.15 install_dir: ~/libebur128 cmake_extras: -DCMAKE_BUILD_TYPE=RelWithDebInfo - name: Windows 2019 os: windows-2019 install_dir: C:\libebur128 cmake_extras: -DCMAKE_TOOLCHAIN_FILE=C:\vcpkg\scripts\buildsystems\vcpkg.cmake cmake_config: --config RelWithDebInfo ctest_config: --build-config RelWithDebInfo name: ${{ matrix.name }} runs-on: ${{ matrix.os }} steps: - name: Check out Git repository uses: actions/checkout@v2 - name: Configure run: cmake -DCMAKE_INSTALL_PREFIX=${{ matrix.install_dir }} -DBUILD_SHARED_LIBS=ON ${{ matrix.cmake_extras }} -S . -B build - name: Build run: cmake --build build ${{ matrix.cmake_config }} env: CMAKE_BUILD_PARALLEL_LEVEL: 2 # TODO: build and run tests and fuzzer - name: Install run: cmake --install build ${{ matrix.cmake_config }} - name: Upload Build Artifact uses: actions/upload-artifact@v2 with: name: ${{ matrix.name }} libebur128 build path: ${{ matrix.install_dir }} libebur128-1.2.6/.gitignore000066400000000000000000000000101401223205100153440ustar00rootroot00000000000000build*/ libebur128-1.2.6/CMakeLists.txt000066400000000000000000000023241401223205100161260ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) project(libebur128 C) option(BUILD_SHARED_LIBS "Build shared libraries instead of static ones" ON) if(ENABLE_FUZZER) enable_language(CXX) set(SANITIZER_FLAGS "-fsanitize=address,undefined,unsigned-integer-overflow") set(FUZZER_FLAGS "${SANITIZER_FLAGS},fuzzer") endif() list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) include(utils) include(GNUInstallDirs) add_subdirectory(ebur128) add_subdirectory(test) to_yes_no(SUMMARY_HAS_QUEUE) if(ENABLE_INTERNAL_QUEUE_H) set(USE_QUEUE "using own copy of queue.h") else() set(USE_QUEUE "using system copy of queue.h") endif() ##### Print status message(STATUS "Status found / disabled --") message(STATUS "queue.h: ${SUMMARY_HAS_QUEUE}" " ${USE_QUEUE}") if(BUILD_SHARED_LIBS) message(STATUS "Building shared library (set BUILD_SHARED_LIBS to OFF to build static)") else() message(STATUS "Building static library") endif() if(ENABLE_TESTS) message(STATUS "building tests!") else() message(STATUS "not building tests, set ENABLE_TESTS to ON to enable") endif() libebur128-1.2.6/COPYING000066400000000000000000000020431401223205100144170ustar00rootroot00000000000000Copyright (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. libebur128-1.2.6/README.md000066400000000000000000000054611401223205100146520ustar00rootroot00000000000000libebur128 ========== libebur128 is a library that implements the EBU R 128 standard for loudness normalisation. All source code is licensed under the MIT license. See COPYING file for details. See also [loudness-scanner tool](https://github.com/jiixyj/loudness-scanner). News ---- v1.2.6 released: * Fix dynamic linking on Windows. v1.2.5 released: * Remove `BUILD_STATIC_LIBS` build option. Instead the CMake-supported `BUILD_SHARED_LIBS` option is now honored as expected. * Various code cleanups, warning fixes and documentation improvements * Fix issue related to filter state indexing with high channel enums (#77) * Introduce limits for number of channels and maximum supported samplerate to avoid integer overflows * Fix error return code of `ebur128_set_channel`. The actual behavior is now aligned to the documentation (#90). v1.2.4 released: * Fix broken `ebur128_loudness_global_multiple()` function. Since v1.1.0 it calculated the relative threshold just from the last state given to it, resulting in wrong values. * More tests * Fix some minor build issues * Fix uninitialized memory in `ebur128_init()`, possibly resulting in wrong values v1.2.3 released: * Fix uninitialized memory access during true peak scanning (bug #72) v1.2.2 released (v1.2.1 was mistagged): * Fix a null pointer dereference when doing true peak scanning of 192kHz data v1.2.0 released: * New functions for real time loudness/peak monitoring: * `ebur128_loudness_window()` * `ebur128_set_max_window()` * `ebur128_set_max_history()` * `ebur128_prev_sample_peak()` * `ebur128_prev_true_peak()` * New FIR resampler for true peak calculation, removing Speex dependency * Add true peak conformance tests * Bug fixes v1.1.0 released: * Add `ebur128_relative_threshold()` * Add channel definitions from ITU R-REC-BS 1770-4 to channel enum * Fix some minor build issues v1.0.3 released: * Fix build with recent speexdsp * Correct license file name * CMake option to disable static library * minimal-example.c: do not hard code program name in usage Features -------- * Portable ANSI C code * Implements M, S and I modes * Implements loudness range measurement (EBU - TECH 3342) * True peak scanning * Supports all samplerates by recalculation of the filter coefficients Installation ------------ In the root folder, type: mkdir build cd build cmake .. make If you want the git version, run simply: git clone git://github.com/jiixyj/libebur128.git Usage ----- Library usage should be pretty straightforward. All exported symbols are documented in the ebur128.h header file. For a usage example, see minimal-example.c in the tests folder. On some operating systems, static libraries should be compiled as position independent code. You can enable that by turning on `WITH_STATIC_PIC`. libebur128-1.2.6/cmake/000077500000000000000000000000001401223205100144455ustar00rootroot00000000000000libebur128-1.2.6/cmake/utils.cmake000066400000000000000000000036111401223205100166100ustar00rootroot00000000000000macro(to_yes_no vars) foreach(var ${ARGV}) if(${var}) set(${var} "yes") else() set(${var} "no ") endif() endforeach() endmacro() macro(if_empty_print_missing vars) foreach(var ${ARGV}) if(NOT ${var}) set(${var} "") endif() endforeach() endmacro() function(to_space_list sc_list) set(ret) foreach(val ${${sc_list}}) set(ret "${ret} ${val}") endforeach() if(ret) string(STRIP ${ret} ret) set(${sc_list} "${ret}" PARENT_SCOPE) endif() endfunction() macro(find_pkg_config prefix pkgname) find_package(PkgConfig ${ARGV2}) if(PKG_CONFIG_FOUND) pkg_check_modules(${prefix}_PKGCONF ${ARGV2} ${pkgname}) if(${${prefix}_PKGCONF_FOUND}) message(STATUS "${pkgname} library dirs: ${${prefix}_PKGCONF_LIBRARY_DIRS}") message(STATUS "${pkgname} cflags: ${${prefix}_PKGCONF_CFLAGS_OTHER}") message(STATUS "${pkgname} include dirs: ${${prefix}_PKGCONF_INCLUDE_DIRS}") message(STATUS "${pkgname} libraries: ${${prefix}_PKGCONF_LIBRARIES}") message(STATUS "${pkgname} ldflags: ${${prefix}_PKGCONF_LDFLAGS_OTHER}") set(${prefix}_FOUND ${${prefix}_PKGCONF_FOUND}) set(${prefix}_CFLAGS ${${prefix}_PKGCONF_CFLAGS_OTHER}) to_space_list(${prefix}_CFLAGS) set(${prefix}_INCLUDE_DIRS ${${prefix}_PKGCONF_INCLUDE_DIRS}) foreach(lib ${${prefix}_PKGCONF_LIBRARIES}) string(TOUPPER ${lib} LIB) find_library(${prefix}_${LIB}_LIBRARY ${lib} HINTS ${${prefix}_PKGCONF_LIBRARY_DIRS}) mark_as_advanced(${prefix}_${LIB}_LIBRARY) list(APPEND ${prefix}_LIBRARIES ${${prefix}_${LIB}_LIBRARY}) endforeach() list(APPEND ${prefix}_LIBRARIES ${${prefix}_PKGCONF_LDFLAGS_OTHER}) endif() endif() endmacro() libebur128-1.2.6/doc/000077500000000000000000000000001401223205100141325ustar00rootroot00000000000000libebur128-1.2.6/doc/license/000077500000000000000000000000001401223205100155545ustar00rootroot00000000000000libebur128-1.2.6/doc/license/R128Scan.txt000066400000000000000000000021511401223205100175550ustar00rootroot00000000000000EBU R128 Gain processor. Copyright (C) 2011 Chris Moeller Portions 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." libebur128-1.2.6/doc/license/queue.txt000066400000000000000000000027541401223205100174510ustar00rootroot00000000000000Copyright (c) 1991, 1993 The Regents of the University of California. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. libebur128-1.2.6/ebur128/000077500000000000000000000000001401223205100145555ustar00rootroot00000000000000libebur128-1.2.6/ebur128/CMakeLists.txt000066400000000000000000000043311401223205100173160ustar00rootroot00000000000000set(BUILD_STATIC_LIBS ON CACHE BOOL "Build static library") set(WITH_STATIC_PIC OFF CACHE BOOL "Compile static library with -fPIC flag") #### queue.h file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/queuetest.c "#include \nLIST_HEAD(listhead, entry) head;\nint main() { return 0; }") try_compile(HAS_QUEUE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/queuetest.c) set(SUMMARY_HAS_QUEUE ${HAS_QUEUE} CACHE INTERNAL "") include(CMakeDependentOption) cmake_dependent_option(ENABLE_INTERNAL_QUEUE_H "Use own queue.h" OFF "HAS_QUEUE" ON) # PARENT_SCOPE set necessary due to status print in parent CMakeLists.txt set(ENABLE_INTERNAL_QUEUE_H ${ENABLE_INTERNAL_QUEUE_H} PARENT_SCOPE) if(ENABLE_INTERNAL_QUEUE_H) include_directories(SYSTEM queue) endif() if(MSVC) add_definitions(-D_USE_MATH_DEFINES) if(CMAKE_SIZEOF_VOID_P LESS 8) add_definitions(/arch:SSE2) endif() endif() set(EBUR128_VERSION_MAJOR 1) set(EBUR128_VERSION 1.2.6) add_library(ebur128 ebur128.c) set_target_properties(ebur128 PROPERTIES SOVERSION ${EBUR128_VERSION_MAJOR} VERSION ${EBUR128_VERSION} ) if(BUILD_SHARED_LIBS) if(MSVC) target_sources(ebur128 PRIVATE ebur128.def) endif() else() if(WITH_STATIC_PIC) set_property(TARGET ebur128 PROPERTY POSITION_INDEPENDENT_CODE ON) endif() endif() # Link with Math library if available find_library(MATH_LIBRARY m) if(MATH_LIBRARY) target_link_libraries(ebur128 ${MATH_LIBRARY}) endif() if(ENABLE_FUZZER) target_compile_options(ebur128 PUBLIC "${FUZZER_FLAGS}") target_compile_definitions(ebur128 PRIVATE malloc=my_malloc calloc=my_calloc) target_link_libraries(ebur128 "${SANITIZER_FLAGS}") endif() set(EBUR128_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "") install(FILES ebur128.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(TARGETS ebur128 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) #### pkg-config configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libebur128.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/libebur128.pc @ONLY) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libebur128.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) libebur128-1.2.6/ebur128/ebur128.c000066400000000000000000001476701401223205100161300ustar00rootroot00000000000000/* 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 #define CHECK_ERROR(condition, errorcode, goto_point) \ if ((condition)) { \ errcode = (errorcode); \ goto goto_point; \ } #define EBUR128_MAX(a, b) (((a) > (b)) ? (a) : (b)) static int safe_size_mul(size_t nmemb, size_t size, size_t* result) { /* Adapted from OpenBSD reallocarray. */ #define MUL_NO_OVERFLOW (((size_t) 1) << (sizeof(size_t) * 4)) if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && /**/ nmemb > 0 && ((size_t) (-1)) / nmemb < size) { return 1; } #undef MUL_NO_OVERFLOW *result = nmemb * size; return 0; } STAILQ_HEAD(ebur128_double_queue, ebur128_dq_entry); struct ebur128_dq_entry { double z; STAILQ_ENTRY(ebur128_dq_entry) entries; }; #define ALMOST_ZERO 0.000001 #define FILTER_STATE_SIZE 5 typedef struct { unsigned int count; /* Number of coefficients in this subfilter */ unsigned int* index; /* Delay index of corresponding filter coeff */ double* coeff; /* List of subfilter coefficients */ } interp_filter; typedef struct { /* Data structure for polyphase FIR interpolator */ unsigned int factor; /* Interpolation factor of the interpolator */ unsigned int taps; /* Taps (prefer odd to increase zero coeffs) */ unsigned int channels; /* Number of channels */ unsigned int delay; /* Size of delay buffer */ interp_filter* filter; /* List of subfilters (one for each factor) */ float** z; /* List of delay buffers (one for each channel) */ unsigned int zi; /* Current delay buffer index */ } interpolator; /** BS.1770 filter state. */ typedef double filter_state[FILTER_STATE_SIZE]; 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]; /** one filter_state per channel. */ filter_state* v; /** Linked list of block energies. */ struct ebur128_double_queue block_list; unsigned long block_list_max; unsigned long block_list_size; /** Linked list of 3s-block energies, used to calculate LRA. */ struct ebur128_double_queue short_term_block_list; unsigned long st_block_list_max; unsigned long st_block_list_size; 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; double* prev_sample_peak; /** Maximum true peak, one per channel */ double* true_peak; double* prev_true_peak; interpolator* interp; float* resampler_buffer_input; size_t resampler_buffer_input_frames; float* resampler_buffer_output; size_t resampler_buffer_output_frames; /** The maximum window duration in ms. */ unsigned long window; unsigned long history; }; 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 interpolator* interp_create(unsigned int taps, unsigned int factor, unsigned int channels) { int errcode; /* unused */ interpolator* interp; unsigned int j; interp = (interpolator*) calloc(1, sizeof(interpolator)); CHECK_ERROR(!interp, 0, exit); interp->taps = taps; interp->factor = factor; interp->channels = channels; interp->delay = (interp->taps + interp->factor - 1) / interp->factor; /* Initialize the filter memory * One subfilter per interpolation factor. */ interp->filter = (interp_filter*) calloc(interp->factor, sizeof(*interp->filter)); CHECK_ERROR(!interp->filter, 0, free_interp); for (j = 0; j < interp->factor; j++) { interp->filter[j].index = (unsigned int*) calloc(interp->delay, sizeof(unsigned int)); interp->filter[j].coeff = (double*) calloc(interp->delay, sizeof(double)); CHECK_ERROR(!interp->filter[j].index || !interp->filter[j].coeff, 0, free_filter_index_coeff); } /* One delay buffer per channel. */ interp->z = (float**) calloc(interp->channels, sizeof(float*)); CHECK_ERROR(!interp->z, 0, free_filter_index_coeff); for (j = 0; j < interp->channels; j++) { interp->z[j] = (float*) calloc(interp->delay, sizeof(float)); CHECK_ERROR(!interp->z[j], 0, free_filter_z); } /* Calculate the filter coefficients */ for (j = 0; j < interp->taps; j++) { /* Calculate sinc */ double m = (double) j - (double) (interp->taps - 1) / 2.0; double c = 1.0; if (fabs(m) > ALMOST_ZERO) { c = sin(m * M_PI / interp->factor) / (m * M_PI / interp->factor); } /* Apply Hanning window */ c *= 0.5 * (1 - cos(2 * M_PI * j / (interp->taps - 1))); if (fabs(c) > ALMOST_ZERO) { /* Ignore any zero coeffs. */ /* Put the coefficient into the correct subfilter */ unsigned int f = j % interp->factor; unsigned int t = interp->filter[f].count++; interp->filter[f].coeff[t] = c; interp->filter[f].index[t] = j / interp->factor; } } return interp; free_filter_z: for (j = 0; j < interp->channels; j++) { free(interp->z[j]); } free(interp->z); free_filter_index_coeff: for (j = 0; j < interp->factor; j++) { free(interp->filter[j].index); free(interp->filter[j].coeff); } free(interp->filter); free_interp: free(interp); exit: return NULL; } static void interp_destroy(interpolator* interp) { unsigned int j = 0; if (!interp) { return; } for (j = 0; j < interp->factor; j++) { free(interp->filter[j].index); free(interp->filter[j].coeff); } free(interp->filter); for (j = 0; j < interp->channels; j++) { free(interp->z[j]); } free(interp->z); free(interp); } static size_t interp_process(interpolator* interp, size_t frames, float* in, float* out) { size_t frame = 0; unsigned int chan = 0; unsigned int f = 0; unsigned int t = 0; unsigned int out_stride = interp->channels * interp->factor; float* outp = 0; double acc = 0; double c = 0; for (frame = 0; frame < frames; frame++) { for (chan = 0; chan < interp->channels; chan++) { /* Add sample to delay buffer */ interp->z[chan][interp->zi] = *in++; /* Apply coefficients */ outp = out + chan; for (f = 0; f < interp->factor; f++) { acc = 0.0; for (t = 0; t < interp->filter[f].count; t++) { int i = (int) interp->zi - (int) interp->filter[f].index[t]; if (i < 0) { i += (int) interp->delay; } c = interp->filter[f].coeff[t]; acc += (double) interp->z[chan][i] * c; } *outp = (float) acc; outp += interp->channels; } } out += out_stride; interp->zi++; if (interp->zi == interp->delay) { interp->zi = 0; } } return frames * interp->factor; } static int ebur128_init_filter(ebur128_state* st) { int errcode = EBUR128_SUCCESS; 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]; st->d->v = (filter_state*) malloc(st->channels * sizeof(filter_state)); CHECK_ERROR(!st->d->v, EBUR128_ERROR_NOMEM, exit); for (i = 0; i < (int) st->channels; ++i) { for (j = 0; j < FILTER_STATE_SIZE; ++j) { st->d->v[i][j] = 0.0; } } exit: return errcode; } 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; } static int ebur128_init_resampler(ebur128_state* st) { int errcode = EBUR128_SUCCESS; if (st->samplerate < 96000) { st->d->interp = interp_create(49, 4, st->channels); CHECK_ERROR(!st->d->interp, EBUR128_ERROR_NOMEM, exit) } else if (st->samplerate < 192000) { st->d->interp = interp_create(49, 2, st->channels); CHECK_ERROR(!st->d->interp, EBUR128_ERROR_NOMEM, exit) } else { st->d->resampler_buffer_input = NULL; st->d->resampler_buffer_output = NULL; st->d->interp = NULL; goto exit; } st->d->resampler_buffer_input_frames = st->d->samples_in_100ms * 4; st->d->resampler_buffer_input = (float*) malloc( st->d->resampler_buffer_input_frames * st->channels * sizeof(float)); CHECK_ERROR(!st->d->resampler_buffer_input, EBUR128_ERROR_NOMEM, free_interp) st->d->resampler_buffer_output_frames = st->d->resampler_buffer_input_frames * st->d->interp->factor; st->d->resampler_buffer_output = (float*) malloc( st->d->resampler_buffer_output_frames * st->channels * sizeof(float)); CHECK_ERROR(!st->d->resampler_buffer_output, EBUR128_ERROR_NOMEM, free_input) return errcode; free_interp: interp_destroy(st->d->interp); st->d->interp = 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; interp_destroy(st->d->interp); st->d->interp = NULL; } void ebur128_get_version(int* major, int* minor, int* patch) { *major = EBUR128_VERSION_MAJOR; *minor = EBUR128_VERSION_MINOR; *patch = EBUR128_VERSION_PATCH; } #define VALIDATE_MAX_CHANNELS (64) #define VALIDATE_MAX_SAMPLERATE (2822400) #define VALIDATE_CHANNELS_AND_SAMPLERATE(err) \ do { \ if (channels == 0 || channels > VALIDATE_MAX_CHANNELS) { \ return (err); \ } \ \ if (samplerate < 16 || samplerate > VALIDATE_MAX_SAMPLERATE) { \ return (err); \ } \ } while (0); ebur128_state* ebur128_init(unsigned int channels, unsigned long samplerate, int mode) { int result; int errcode; ebur128_state* st; unsigned int i; size_t j; VALIDATE_CHANNELS_AND_SAMPLERATE(NULL); 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->prev_sample_peak = (double*) malloc(channels * sizeof(double)); CHECK_ERROR(!st->d->prev_sample_peak, 0, free_sample_peak) st->d->true_peak = (double*) malloc(channels * sizeof(double)); CHECK_ERROR(!st->d->true_peak, 0, free_prev_sample_peak) st->d->prev_true_peak = (double*) malloc(channels * sizeof(double)); CHECK_ERROR(!st->d->prev_true_peak, 0, free_true_peak) for (i = 0; i < channels; ++i) { st->d->sample_peak[i] = 0.0; st->d->prev_sample_peak[i] = 0.0; st->d->true_peak[i] = 0.0; st->d->prev_true_peak[i] = 0.0; } st->d->use_histogram = mode & EBUR128_MODE_HISTOGRAM ? 1 : 0; st->d->history = ULONG_MAX; 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->window = 3000; } else if ((mode & EBUR128_MODE_M) == EBUR128_MODE_M) { st->d->window = 400; } else { goto free_prev_true_peak; } st->d->audio_data_frames = st->samplerate * st->d->window / 1000; if (st->d->audio_data_frames % st->d->samples_in_100ms) { /* round up to multiple of samples_in_100ms */ st->d->audio_data_frames = (st->d->audio_data_frames + st->d->samples_in_100ms) - (st->d->audio_data_frames % st->d->samples_in_100ms); } st->d->audio_data = (double*) malloc(st->d->audio_data_frames * st->channels * sizeof(double)); CHECK_ERROR(!st->d->audio_data, 0, free_prev_true_peak) for (j = 0; j < st->d->audio_data_frames * st->channels; ++j) { st->d->audio_data[j] = 0.0; } errcode = ebur128_init_filter(st); CHECK_ERROR(errcode, 0, free_audio_data) if (st->d->use_histogram) { st->d->block_energy_histogram = (unsigned long*) malloc(1000 * sizeof(unsigned long)); CHECK_ERROR(!st->d->block_energy_histogram, 0, free_filter) 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 = (unsigned long*) 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; } STAILQ_INIT(&st->d->block_list); st->d->block_list_size = 0; st->d->block_list_max = st->d->history / 100; STAILQ_INIT(&st->d->short_term_block_list); st->d->st_block_list_size = 0; st->d->st_block_list_max = st->d->history / 3000; st->d->short_term_frame_counter = 0; result = ebur128_init_resampler(st); CHECK_ERROR(result, 0, free_short_term_block_energy_histogram) /* 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_filter: free(st->d->v); free_audio_data: free(st->d->audio_data); free_prev_true_peak: free(st->d->prev_true_peak); free_true_peak: free(st->d->true_peak); free_prev_sample_peak: free(st->d->prev_sample_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->short_term_block_energy_histogram); free((*st)->d->block_energy_histogram); free((*st)->d->v); free((*st)->d->audio_data); free((*st)->d->channel_map); free((*st)->d->sample_peak); free((*st)->d->prev_sample_peak); free((*st)->d->true_peak); free((*st)->d->prev_true_peak); while (!STAILQ_EMPTY(&(*st)->d->block_list)) { entry = STAILQ_FIRST(&(*st)->d->block_list); STAILQ_REMOVE_HEAD(&(*st)->d->block_list, entries); free(entry); } while (!STAILQ_EMPTY(&(*st)->d->short_term_block_list)) { entry = STAILQ_FIRST(&(*st)->d->short_term_block_list); STAILQ_REMOVE_HEAD(&(*st)->d->short_term_block_list, entries); free(entry); } ebur128_destroy_resampler(*st); free((*st)->d); free(*st); *st = NULL; } static void ebur128_check_true_peak(ebur128_state* st, size_t frames) { size_t c, i, frames_out; frames_out = interp_process(st->d->interp, frames, st->d->resampler_buffer_input, st->d->resampler_buffer_output); for (i = 0; i < frames_out; ++i) { for (c = 0; c < st->channels; ++c) { double val = (double) st->d->resampler_buffer_output[i * st->channels + c]; if (EBUR128_MAX(val, -val) > st->d->prev_true_peak[c]) { st->d->prev_true_peak[c] = EBUR128_MAX(val, -val); } } } } #if defined(__SSE2_MATH__) || defined(_M_X64) || _M_IX86_FP >= 2 #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[c][4] = fabs(st->d->v[c][4]) < DBL_MIN ? 0.0 : st->d->v[c][4]; \ st->d->v[c][3] = fabs(st->d->v[c][3]) < DBL_MIN ? 0.0 : st->d->v[c][3]; \ st->d->v[c][2] = fabs(st->d->v[c][2]) < DBL_MIN ? 0.0 : st->d->v[c][2]; \ st->d->v[c][1] = fabs(st->d->v[c][1]) < DBL_MIN ? 0.0 : st->d->v[c][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 = \ EBUR128_MAX(-((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) { \ double cur = (double) src[i * st->channels + c]; \ if (EBUR128_MAX(cur, -cur) > max) { \ max = EBUR128_MAX(cur, -cur); \ } \ } \ max /= scaling_factor; \ if (max > st->d->prev_sample_peak[c]) { \ st->d->prev_sample_peak[c] = max; \ } \ } \ } \ if ((st->mode & EBUR128_MODE_TRUE_PEAK) == EBUR128_MODE_TRUE_PEAK && \ st->d->interp) { \ for (i = 0; i < frames; ++i) { \ for (c = 0; c < st->channels; ++c) { \ st->d->resampler_buffer_input[i * st->channels + c] = \ (float) ((double) src[i * st->channels + c] / scaling_factor); \ } \ } \ ebur128_check_true_peak(st, frames); \ } \ for (c = 0; c < st->channels; ++c) { \ if (st->d->channel_map[c] == EBUR128_UNUSED) { \ continue; \ } \ for (i = 0; i < frames; ++i) { \ st->d->v[c][0] = \ (double) ((double) src[i * st->channels + c] / scaling_factor) - \ st->d->a[1] * st->d->v[c][1] - /**/ \ st->d->a[2] * st->d->v[c][2] - /**/ \ st->d->a[3] * st->d->v[c][3] - /**/ \ st->d->a[4] * st->d->v[c][4]; \ audio_data[i * st->channels + c] = /**/ \ st->d->b[0] * st->d->v[c][0] + /**/ \ st->d->b[1] * st->d->v[c][1] + /**/ \ st->d->b[2] * st->d->v[c][2] + /**/ \ st->d->b[3] * st->d->v[c][3] + /**/ \ st->d->b[4] * st->d->v[c][4]; \ st->d->v[c][4] = st->d->v[c][3]; \ st->d->v[c][3] = st->d->v[c][2]; \ st->d->v[c][2] = st->d->v[c][1]; \ st->d->v[c][1] = st->d->v[c][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_Mp110 || st->d->channel_map[c] == EBUR128_Mm110 || st->d->channel_map[c] == EBUR128_Mp060 || st->d->channel_map[c] == EBUR128_Mm060 || st->d->channel_map[c] == EBUR128_Mp090 || st->d->channel_map[c] == EBUR128_Mm090) { 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; } 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; if (st->d->block_list_size == st->d->block_list_max) { block = STAILQ_FIRST(&st->d->block_list); STAILQ_REMOVE_HEAD(&st->d->block_list, entries); } else { block = (struct ebur128_dq_entry*) malloc(sizeof(struct ebur128_dq_entry)); if (!block) { return EBUR128_ERROR_NOMEM; } st->d->block_list_size++; } block->z = sum; STAILQ_INSERT_TAIL(&st->d->block_list, block, entries); } } return EBUR128_SUCCESS; } int ebur128_set_channel(ebur128_state* st, unsigned int channel_number, int value) { if (channel_number >= st->channels) { return EBUR128_ERROR_INVALID_CHANNEL_INDEX; } if (value == EBUR128_DUAL_MONO && (st->channels != 1 || channel_number != 0)) { fprintf(stderr, "EBUR128_DUAL_MONO only works with mono files!\n"); return EBUR128_ERROR_INVALID_CHANNEL_INDEX; } st->d->channel_map[channel_number] = value; return EBUR128_SUCCESS; } int ebur128_change_parameters(ebur128_state* st, unsigned int channels, unsigned long samplerate) { int errcode = EBUR128_SUCCESS; size_t j; /* This is needed to suppress a clang-tidy warning. */ #ifndef __has_builtin #define __has_builtin(x) 0 #endif #if __has_builtin(__builtin_unreachable) if (st->channels == 0) { __builtin_unreachable(); } #endif VALIDATE_CHANNELS_AND_SAMPLERATE(EBUR128_ERROR_NOMEM); if (channels == st->channels && samplerate == st->samplerate) { return EBUR128_ERROR_NO_CHANGE; } 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->prev_sample_peak); st->d->prev_sample_peak = NULL; free(st->d->true_peak); st->d->true_peak = NULL; free(st->d->prev_true_peak); st->d->prev_true_peak = NULL; st->channels = channels; 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->prev_sample_peak = (double*) malloc(channels * sizeof(double)); CHECK_ERROR(!st->d->prev_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) st->d->prev_true_peak = (double*) malloc(channels * sizeof(double)); CHECK_ERROR(!st->d->prev_true_peak, EBUR128_ERROR_NOMEM, exit) for (i = 0; i < channels; ++i) { st->d->sample_peak[i] = 0.0; st->d->prev_sample_peak[i] = 0.0; st->d->true_peak[i] = 0.0; st->d->prev_true_peak[i] = 0.0; } } if (samplerate != st->samplerate) { st->samplerate = samplerate; st->d->samples_in_100ms = (st->samplerate + 5) / 10; } /* If we're here, either samplerate or channels * have changed. Re-init filter. */ free(st->d->v); st->d->v = NULL; errcode = ebur128_init_filter(st); CHECK_ERROR(errcode, EBUR128_ERROR_NOMEM, exit) st->d->audio_data_frames = st->samplerate * st->d->window / 1000; if (st->d->audio_data_frames % st->d->samples_in_100ms) { /* round up to multiple of samples_in_100ms */ st->d->audio_data_frames = (st->d->audio_data_frames + st->d->samples_in_100ms) - (st->d->audio_data_frames % st->d->samples_in_100ms); } 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) for (j = 0; j < st->d->audio_data_frames * st->channels; ++j) { st->d->audio_data[j] = 0.0; } ebur128_destroy_resampler(st); errcode = ebur128_init_resampler(st); CHECK_ERROR(errcode, 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; exit: return errcode; } int ebur128_set_max_window(ebur128_state* st, unsigned long window) { int errcode = EBUR128_SUCCESS; size_t j; if ((st->mode & EBUR128_MODE_S) == EBUR128_MODE_S && window < 3000) { window = 3000; } else if ((st->mode & EBUR128_MODE_M) == EBUR128_MODE_M && window < 400) { window = 400; } if (window == st->d->window) { return EBUR128_ERROR_NO_CHANGE; } size_t new_audio_data_frames; if (safe_size_mul(st->samplerate, window, &new_audio_data_frames) != 0 || new_audio_data_frames > ((size_t) -1) - st->d->samples_in_100ms) { return EBUR128_ERROR_NOMEM; } if (new_audio_data_frames % st->d->samples_in_100ms) { /* round up to multiple of samples_in_100ms */ new_audio_data_frames = (new_audio_data_frames + st->d->samples_in_100ms) - (new_audio_data_frames % st->d->samples_in_100ms); } size_t new_audio_data_size; if (safe_size_mul(new_audio_data_frames, st->channels * sizeof(double), &new_audio_data_size) != 0) { return EBUR128_ERROR_NOMEM; } double* new_audio_data = (double*) malloc(new_audio_data_size); CHECK_ERROR(!new_audio_data, EBUR128_ERROR_NOMEM, exit) st->d->window = window; free(st->d->audio_data); st->d->audio_data = new_audio_data; st->d->audio_data_frames = new_audio_data_frames; for (j = 0; j < st->d->audio_data_frames * st->channels; ++j) { st->d->audio_data[j] = 0.0; } /* 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; exit: return errcode; } int ebur128_set_max_history(ebur128_state* st, unsigned long history) { if ((st->mode & EBUR128_MODE_LRA) == EBUR128_MODE_LRA && history < 3000) { history = 3000; } else if ((st->mode & EBUR128_MODE_M) == EBUR128_MODE_M && history < 400) { history = 400; } if (history == st->d->history) { return EBUR128_ERROR_NO_CHANGE; } st->d->history = history; st->d->block_list_max = st->d->history / 100; st->d->st_block_list_max = st->d->history / 3000; while (st->d->block_list_size > st->d->block_list_max) { struct ebur128_dq_entry* block = STAILQ_FIRST(&st->d->block_list); STAILQ_REMOVE_HEAD(&st->d->block_list, entries); free(block); st->d->block_list_size--; } while (st->d->st_block_list_size > st->d->st_block_list_max) { struct ebur128_dq_entry* block = STAILQ_FIRST(&st->d->short_term_block_list); STAILQ_REMOVE_HEAD(&st->d->short_term_block_list, entries); free(block); st->d->st_block_list_size--; } return EBUR128_SUCCESS; } 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; \ unsigned int c = 0; \ for (c = 0; c < st->channels; c++) { \ st->d->prev_sample_peak[c] = 0.0; \ st->d->prev_true_peak[c] = 0.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; \ if (ebur128_energy_shortterm(st, &st_energy) == EBUR128_SUCCESS && \ st_energy >= histogram_energy_boundaries[0]) { \ if (st->d->use_histogram) { \ ++st->d->short_term_block_energy_histogram \ [find_histogram_index(st_energy)]; \ } else { \ if (st->d->st_block_list_size == st->d->st_block_list_max) { \ block = STAILQ_FIRST(&st->d->short_term_block_list); \ STAILQ_REMOVE_HEAD(&st->d->short_term_block_list, entries); \ } else { \ block = (struct ebur128_dq_entry*) malloc( \ sizeof(struct ebur128_dq_entry)); \ if (!block) { \ return EBUR128_ERROR_NOMEM; \ } \ st->d->st_block_list_size++; \ } \ block->z = st_energy; \ STAILQ_INSERT_TAIL(&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 -= (unsigned long) frames; \ frames = 0; \ } \ } \ for (c = 0; c < st->channels; c++) { \ if (st->d->prev_sample_peak[c] > st->d->sample_peak[c]) { \ st->d->sample_peak[c] = st->d->prev_sample_peak[c]; \ } \ if (st->d->prev_true_peak[c] > st->d->true_peak[c]) { \ st->d->true_peak[c] = st->d->prev_true_peak[c]; \ } \ } \ return EBUR128_SUCCESS; \ } EBUR128_ADD_FRAMES(short) EBUR128_ADD_FRAMES(int) EBUR128_ADD_FRAMES(float) EBUR128_ADD_FRAMES(double) static int ebur128_calc_relative_threshold(ebur128_state* st, size_t* above_thresh_counter, double* relative_threshold) { struct ebur128_dq_entry* it; size_t i; if (st->d->use_histogram) { for (i = 0; i < 1000; ++i) { *relative_threshold += st->d->block_energy_histogram[i] * histogram_energies[i]; *above_thresh_counter += st->d->block_energy_histogram[i]; } } else { STAILQ_FOREACH(it, &st->d->block_list, entries) { ++*above_thresh_counter; *relative_threshold += it->z; } } return EBUR128_SUCCESS; } static int ebur128_gated_loudness(ebur128_state** sts, size_t size, double* out) { struct ebur128_dq_entry* it; double gated_loudness = 0.0; double relative_threshold = 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; } ebur128_calc_relative_threshold(sts[i], &above_thresh_counter, &relative_threshold); } 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 { STAILQ_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_relative_threshold(ebur128_state* st, double* out) { double relative_threshold = 0.0; size_t above_thresh_counter = 0; if ((st->mode & EBUR128_MODE_I) != EBUR128_MODE_I) { return EBUR128_ERROR_INVALID_MODE; } ebur128_calc_relative_threshold(st, &above_thresh_counter, &relative_threshold); if (!above_thresh_counter) { *out = -70.0; return EBUR128_SUCCESS; } relative_threshold /= (double) above_thresh_counter; relative_threshold *= relative_gate_factor; *out = ebur128_energy_to_loudness(relative_threshold); 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; error = ebur128_energy_in_interval(st, st->d->samples_in_100ms * 4, &energy); if (error) { return error; } 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; error = ebur128_energy_shortterm(st, &energy); if (error) { return error; } if (energy <= 0.0) { *out = -HUGE_VAL; return EBUR128_SUCCESS; } *out = ebur128_energy_to_loudness(energy); return EBUR128_SUCCESS; } int ebur128_loudness_window(ebur128_state* st, unsigned long window, double* out) { double energy; size_t interval_frames; int error; if (window > st->d->window) { return EBUR128_ERROR_INVALID_MODE; } interval_frames = st->samplerate * window / 1000; error = ebur128_energy_in_interval(st, interval_frames, &energy); if (error) { return error; } 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; } stl_size = 0; for (i = 0; i < size; ++i) { if (!sts[i]) { continue; } STAILQ_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; } j = 0; for (i = 0; i < size; ++i) { if (!sts[i]) { continue; } STAILQ_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); } 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; } if (channel_number >= st->channels) { return EBUR128_ERROR_INVALID_CHANNEL_INDEX; } *out = st->d->sample_peak[channel_number]; return EBUR128_SUCCESS; } int ebur128_prev_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; } if (channel_number >= st->channels) { return EBUR128_ERROR_INVALID_CHANNEL_INDEX; } *out = st->d->prev_sample_peak[channel_number]; return EBUR128_SUCCESS; } 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; } if (channel_number >= st->channels) { return EBUR128_ERROR_INVALID_CHANNEL_INDEX; } *out = EBUR128_MAX(st->d->true_peak[channel_number], st->d->sample_peak[channel_number]); return EBUR128_SUCCESS; } int ebur128_prev_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; } if (channel_number >= st->channels) { return EBUR128_ERROR_INVALID_CHANNEL_INDEX; } *out = EBUR128_MAX(st->d->prev_true_peak[channel_number], st->d->prev_sample_peak[channel_number]); return EBUR128_SUCCESS; } libebur128-1.2.6/ebur128/ebur128.def000066400000000000000000000011201401223205100164170ustar00rootroot00000000000000LIBRARY EBUR128 EXPORTS ebur128_get_version ebur128_init ebur128_destroy ebur128_set_channel ebur128_change_parameters ebur128_set_max_window ebur128_set_max_history ebur128_add_frames_short ebur128_add_frames_int ebur128_add_frames_float ebur128_add_frames_double ebur128_loudness_global ebur128_loudness_global_multiple ebur128_loudness_momentary ebur128_loudness_shortterm ebur128_loudness_window ebur128_loudness_range ebur128_loudness_range_multiple ebur128_sample_peak ebur128_prev_sample_peak ebur128_true_peak ebur128_prev_true_peak ebur128_relative_threshold libebur128-1.2.6/ebur128/ebur128.h000066400000000000000000000362731401223205100161310ustar00rootroot00000000000000/* 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 2 #define EBUR128_VERSION_PATCH 6 #include /* for size_t */ /** \enum channel * Use these values when setting the channel map with ebur128_set_channel(). * See definitions in ITU R-REC-BS 1770-4 */ enum channel { EBUR128_UNUSED = 0, /**< unused channel (for example LFE channel) */ EBUR128_LEFT = 1, /**< */ EBUR128_Mp030 = 1, /**< itu M+030 */ EBUR128_RIGHT = 2, /**< */ EBUR128_Mm030 = 2, /**< itu M-030 */ EBUR128_CENTER = 3, /**< */ EBUR128_Mp000 = 3, /**< itu M+000 */ EBUR128_LEFT_SURROUND = 4, /**< */ EBUR128_Mp110 = 4, /**< itu M+110 */ EBUR128_RIGHT_SURROUND = 5, /**< */ EBUR128_Mm110 = 5, /**< itu M-110 */ EBUR128_DUAL_MONO, /**< a channel that is counted twice */ EBUR128_MpSC, /**< itu M+SC */ EBUR128_MmSC, /**< itu M-SC */ EBUR128_Mp060, /**< itu M+060 */ EBUR128_Mm060, /**< itu M-060 */ EBUR128_Mp090, /**< itu M+090 */ EBUR128_Mm090, /**< itu M-090 */ EBUR128_Mp135, /**< itu M+135 */ EBUR128_Mm135, /**< itu M-135 */ EBUR128_Mp180, /**< itu M+180 */ EBUR128_Up000, /**< itu U+000 */ EBUR128_Up030, /**< itu U+030 */ EBUR128_Um030, /**< itu U-030 */ EBUR128_Up045, /**< itu U+045 */ EBUR128_Um045, /**< itu U-030 */ EBUR128_Up090, /**< itu U+090 */ EBUR128_Um090, /**< itu U-090 */ EBUR128_Up110, /**< itu U+110 */ EBUR128_Um110, /**< itu U-110 */ EBUR128_Up135, /**< itu U+135 */ EBUR128_Um135, /**< itu U-135 */ EBUR128_Up180, /**< itu U+180 */ EBUR128_Tp000, /**< itu T+000 */ EBUR128_Bp000, /**< itu B+000 */ EBUR128_Bp045, /**< itu B+045 */ EBUR128_Bm045 /**< itu B-045 */ }; /** \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_loudness_global_* and ebur128_relative_threshold */ 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, or NULL on error. */ 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 Set the maximum window duration. * * Set the maximum duration that will be used for ebur128_loudness_window(). * Note that this destroys the current content of the audio buffer. * * @param st library state. * @param window duration of the window in ms. * @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 window duration not changed. */ int ebur128_set_max_window(ebur128_state* st, unsigned long window); /** \brief Set the maximum history. * * Set the maximum history that will be stored for loudness integration. * More history provides more accurate results, but requires more resources. * * Applies to ebur128_loudness_range() and ebur128_loudness_global() when * EBUR128_MODE_HISTOGRAM is not set. * * Default is ULONG_MAX (at least ~50 days). * Minimum is 3000ms for EBUR128_MODE_LRA and 400ms for EBUR128_MODE_M. * * @param st library state. * @param history duration of history in ms. * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_NO_CHANGE if history not changed. */ int ebur128_set_max_history(ebur128_state* st, unsigned long history); /** \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 of the specified window in LUFS. * * window must not be larger than the current window set in st. * The current window can be changed by calling ebur128_set_max_window(). * * @param st library state. * @param window window in ms to calculate loudness. * @param out loudness in LUFS. -HUGE_VAL if result is negative infinity. * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_INVALID_MODE if window larger than current window in st. */ int ebur128_loudness_window(ebur128_state* st, unsigned long window, 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 from all frames that have been processed. * * The equation to convert to dBFS is: 20 * log10(out) * * @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 sample peak from the last call to add_frames(). * * The equation to convert to dBFS is: 20 * log10(out) * * @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_prev_sample_peak(ebur128_state* st, unsigned int channel_number, double* out); /** \brief Get maximum true peak from all frames that have been processed. * * 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 a custom polyphase FIR interpolator 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. * * The equation to convert to dBTP is: 20 * log10(out) * * @param st library state * @param channel_number channel to analyse * @param out maximum true peak in float format (1.0 is 0 dBTP) * @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); /** \brief Get maximum true peak from the last call to add_frames(). * * 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 a custom polyphase FIR interpolator 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. * * The equation to convert to dBTP is: 20 * log10(out) * * @param st library state * @param channel_number channel to analyse * @param out maximum true peak in float format (1.0 is 0 dBTP) * @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_prev_true_peak(ebur128_state* st, unsigned int channel_number, double* out); /** \brief Get relative threshold in LUFS. * * @param st library state * @param out relative threshold in LUFS. * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_I" has not * been set. */ int ebur128_relative_threshold(ebur128_state* st, double* out); #ifdef __cplusplus } #endif #endif /* EBUR128_H_ */ libebur128-1.2.6/ebur128/libebur128.pc.cmake000066400000000000000000000004731401223205100200430ustar00rootroot00000000000000prefix=@CMAKE_INSTALL_PREFIX@ includedir=${prefix}/include libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ Name: libebur128 Description: EBU R 128 standard for loudness normalisation Version: @EBUR128_VERSION@ URL: https://github.com/jiixyj/libebur128 Libs: -L${libdir} -lebur128 Libs.private: -lm Cflags: -I${includedir} libebur128-1.2.6/ebur128/queue/000077500000000000000000000000001401223205100157015ustar00rootroot00000000000000libebur128-1.2.6/ebur128/queue/sys/000077500000000000000000000000001401223205100165175ustar00rootroot00000000000000libebur128-1.2.6/ebur128/queue/sys/queue.h000066400000000000000000000461231401223205100200220ustar00rootroot00000000000000/* * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)queue.h 8.5 (Berkeley) 8/20/94 */ #ifndef _SYS_QUEUE_H_ #define _SYS_QUEUE_H_ /* * This file defines five types of data structures: singly-linked lists, * lists, simple queues, tail queues, and circular queues. * * A singly-linked list is headed by a single forward pointer. The * elements are singly linked for minimum space and pointer manipulation * overhead at the expense of O(n) removal for arbitrary elements. New * elements can be added to the list after an existing element or at the * head of the list. Elements being removed from the head of the list * should use the explicit macro for this purpose for optimum * efficiency. A singly-linked list may only be traversed in the forward * direction. Singly-linked lists are ideal for applications with large * datasets and few or no removals or for implementing a LIFO queue. * * A list is headed by a single forward pointer (or an array of forward * pointers for a hash table header). The elements are doubly linked * so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before * or after an existing element or at the head of the list. A list * may only be traversed in the forward direction. * * A simple queue is headed by a pair of pointers, one the head of the * list and the other to the tail of the list. The elements are singly * linked to save space, so elements can only be removed from the * head of the list. New elements can be added to the list after * an existing element, at the head of the list, or at the end of the * list. A simple queue may only be traversed in the forward direction. * * A tail queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or * after an existing element, at the head of the list, or at the end of * the list. A tail queue may be traversed in either direction. * * A circle queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or after * an existing element, at the head of the list, or at the end of the list. * A circle queue may be traversed in either direction, but has a more * complex end of list detection. * * For details on the use of these macros, see the queue(3) manual page. */ /* * List definitions. */ #define LIST_HEAD(name, type) \ struct name { \ struct type *lh_first; /* first element */ \ } #define LIST_HEAD_INITIALIZER(head) \ { NULL } #define LIST_ENTRY(type) \ struct { \ struct type *le_next; /* next element */ \ struct type **le_prev; /* address of previous next element */ \ } /* * List functions. */ #define LIST_INIT(head) do { \ (head)->lh_first = NULL; \ } while (/*CONSTCOND*/0) #define LIST_INSERT_AFTER(listelm, elm, field) do { \ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ (listelm)->field.le_next->field.le_prev = \ &(elm)->field.le_next; \ (listelm)->field.le_next = (elm); \ (elm)->field.le_prev = &(listelm)->field.le_next; \ } while (/*CONSTCOND*/0) #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ (elm)->field.le_prev = (listelm)->field.le_prev; \ (elm)->field.le_next = (listelm); \ *(listelm)->field.le_prev = (elm); \ (listelm)->field.le_prev = &(elm)->field.le_next; \ } while (/*CONSTCOND*/0) #define LIST_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.le_next = (head)->lh_first) != NULL) \ (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ (head)->lh_first = (elm); \ (elm)->field.le_prev = &(head)->lh_first; \ } while (/*CONSTCOND*/0) #define LIST_REMOVE(elm, field) do { \ if ((elm)->field.le_next != NULL) \ (elm)->field.le_next->field.le_prev = \ (elm)->field.le_prev; \ *(elm)->field.le_prev = (elm)->field.le_next; \ } while (/*CONSTCOND*/0) #define LIST_FOREACH(var, head, field) \ for ((var) = ((head)->lh_first); \ (var); \ (var) = ((var)->field.le_next)) /* * List access methods. */ #define LIST_EMPTY(head) ((head)->lh_first == NULL) #define LIST_FIRST(head) ((head)->lh_first) #define LIST_NEXT(elm, field) ((elm)->field.le_next) /* * Singly-linked List definitions. */ #define SLIST_HEAD(name, type) \ struct name { \ struct type *slh_first; /* first element */ \ } #define SLIST_HEAD_INITIALIZER(head) \ { NULL } #define SLIST_ENTRY(type) \ struct { \ struct type *sle_next; /* next element */ \ } /* * Singly-linked List functions. */ #define SLIST_INIT(head) do { \ (head)->slh_first = NULL; \ } while (/*CONSTCOND*/0) #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ (elm)->field.sle_next = (slistelm)->field.sle_next; \ (slistelm)->field.sle_next = (elm); \ } while (/*CONSTCOND*/0) #define SLIST_INSERT_HEAD(head, elm, field) do { \ (elm)->field.sle_next = (head)->slh_first; \ (head)->slh_first = (elm); \ } while (/*CONSTCOND*/0) #define SLIST_REMOVE_HEAD(head, field) do { \ (head)->slh_first = (head)->slh_first->field.sle_next; \ } while (/*CONSTCOND*/0) #define SLIST_REMOVE(head, elm, type, field) do { \ if ((head)->slh_first == (elm)) { \ SLIST_REMOVE_HEAD((head), field); \ } \ else { \ struct type *curelm = (head)->slh_first; \ while(curelm->field.sle_next != (elm)) \ curelm = curelm->field.sle_next; \ curelm->field.sle_next = \ curelm->field.sle_next->field.sle_next; \ } \ } while (/*CONSTCOND*/0) #define SLIST_FOREACH(var, head, field) \ for((var) = (head)->slh_first; (var); (var) = (var)->field.sle_next) /* * Singly-linked List access methods. */ #define SLIST_EMPTY(head) ((head)->slh_first == NULL) #define SLIST_FIRST(head) ((head)->slh_first) #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) /* * Singly-linked Tail queue declarations. */ #define STAILQ_HEAD(name, type) \ struct name { \ struct type *stqh_first; /* first element */ \ struct type **stqh_last; /* addr of last next element */ \ } #define STAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).stqh_first } #define STAILQ_ENTRY(type) \ struct { \ struct type *stqe_next; /* next element */ \ } /* * Singly-linked Tail queue functions. */ #define STAILQ_INIT(head) do { \ (head)->stqh_first = NULL; \ (head)->stqh_last = &(head)->stqh_first; \ } while (/*CONSTCOND*/0) #define STAILQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.stqe_next = (head)->stqh_first) == NULL) \ (head)->stqh_last = &(elm)->field.stqe_next; \ (head)->stqh_first = (elm); \ } while (/*CONSTCOND*/0) #define STAILQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.stqe_next = NULL; \ *(head)->stqh_last = (elm); \ (head)->stqh_last = &(elm)->field.stqe_next; \ } while (/*CONSTCOND*/0) #define STAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ if (((elm)->field.stqe_next = (listelm)->field.stqe_next) == NULL)\ (head)->stqh_last = &(elm)->field.stqe_next; \ (listelm)->field.stqe_next = (elm); \ } while (/*CONSTCOND*/0) #define STAILQ_REMOVE_HEAD(head, field) do { \ if (((head)->stqh_first = (head)->stqh_first->field.stqe_next) == NULL) \ (head)->stqh_last = &(head)->stqh_first; \ } while (/*CONSTCOND*/0) #define STAILQ_REMOVE(head, elm, type, field) do { \ if ((head)->stqh_first == (elm)) { \ STAILQ_REMOVE_HEAD((head), field); \ } else { \ struct type *curelm = (head)->stqh_first; \ while (curelm->field.stqe_next != (elm)) \ curelm = curelm->field.stqe_next; \ if ((curelm->field.stqe_next = \ curelm->field.stqe_next->field.stqe_next) == NULL) \ (head)->stqh_last = &(curelm)->field.stqe_next; \ } \ } while (/*CONSTCOND*/0) #define STAILQ_FOREACH(var, head, field) \ for ((var) = ((head)->stqh_first); \ (var); \ (var) = ((var)->field.stqe_next)) #define STAILQ_CONCAT(head1, head2) do { \ if (!STAILQ_EMPTY((head2))) { \ *(head1)->stqh_last = (head2)->stqh_first; \ (head1)->stqh_last = (head2)->stqh_last; \ STAILQ_INIT((head2)); \ } \ } while (/*CONSTCOND*/0) /* * Singly-linked Tail queue access methods. */ #define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) #define STAILQ_FIRST(head) ((head)->stqh_first) #define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) /* * Simple queue definitions. */ #define SIMPLEQ_HEAD(name, type) \ struct name { \ struct type *sqh_first; /* first element */ \ struct type **sqh_last; /* addr of last next element */ \ } #define SIMPLEQ_HEAD_INITIALIZER(head) \ { NULL, &(head).sqh_first } #define SIMPLEQ_ENTRY(type) \ struct { \ struct type *sqe_next; /* next element */ \ } /* * Simple queue functions. */ #define SIMPLEQ_INIT(head) do { \ (head)->sqh_first = NULL; \ (head)->sqh_last = &(head)->sqh_first; \ } while (/*CONSTCOND*/0) #define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ (head)->sqh_last = &(elm)->field.sqe_next; \ (head)->sqh_first = (elm); \ } while (/*CONSTCOND*/0) #define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.sqe_next = NULL; \ *(head)->sqh_last = (elm); \ (head)->sqh_last = &(elm)->field.sqe_next; \ } while (/*CONSTCOND*/0) #define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ (head)->sqh_last = &(elm)->field.sqe_next; \ (listelm)->field.sqe_next = (elm); \ } while (/*CONSTCOND*/0) #define SIMPLEQ_REMOVE_HEAD(head, field) do { \ if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ (head)->sqh_last = &(head)->sqh_first; \ } while (/*CONSTCOND*/0) #define SIMPLEQ_REMOVE(head, elm, type, field) do { \ if ((head)->sqh_first == (elm)) { \ SIMPLEQ_REMOVE_HEAD((head), field); \ } else { \ struct type *curelm = (head)->sqh_first; \ while (curelm->field.sqe_next != (elm)) \ curelm = curelm->field.sqe_next; \ if ((curelm->field.sqe_next = \ curelm->field.sqe_next->field.sqe_next) == NULL) \ (head)->sqh_last = &(curelm)->field.sqe_next; \ } \ } while (/*CONSTCOND*/0) #define SIMPLEQ_FOREACH(var, head, field) \ for ((var) = ((head)->sqh_first); \ (var); \ (var) = ((var)->field.sqe_next)) /* * Simple queue access methods. */ #define SIMPLEQ_EMPTY(head) ((head)->sqh_first == NULL) #define SIMPLEQ_FIRST(head) ((head)->sqh_first) #define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) /* * Tail queue definitions. */ #define _TAILQ_HEAD(name, type, qual) \ struct name { \ qual type *tqh_first; /* first element */ \ qual type *qual *tqh_last; /* addr of last next element */ \ } #define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,) #define TAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).tqh_first } #define _TAILQ_ENTRY(type, qual) \ struct { \ qual type *tqe_next; /* next element */ \ qual type *qual *tqe_prev; /* address of previous next element */\ } #define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,) /* * Tail queue functions. */ #define TAILQ_INIT(head) do { \ (head)->tqh_first = NULL; \ (head)->tqh_last = &(head)->tqh_first; \ } while (/*CONSTCOND*/0) #define TAILQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ (head)->tqh_first->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (head)->tqh_first = (elm); \ (elm)->field.tqe_prev = &(head)->tqh_first; \ } while (/*CONSTCOND*/0) #define TAILQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.tqe_next = NULL; \ (elm)->field.tqe_prev = (head)->tqh_last; \ *(head)->tqh_last = (elm); \ (head)->tqh_last = &(elm)->field.tqe_next; \ } while (/*CONSTCOND*/0) #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ (elm)->field.tqe_next->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (listelm)->field.tqe_next = (elm); \ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ } while (/*CONSTCOND*/0) #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ (elm)->field.tqe_next = (listelm); \ *(listelm)->field.tqe_prev = (elm); \ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ } while (/*CONSTCOND*/0) #define TAILQ_REMOVE(head, elm, field) do { \ if (((elm)->field.tqe_next) != NULL) \ (elm)->field.tqe_next->field.tqe_prev = \ (elm)->field.tqe_prev; \ else \ (head)->tqh_last = (elm)->field.tqe_prev; \ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ } while (/*CONSTCOND*/0) #define TAILQ_FOREACH(var, head, field) \ for ((var) = ((head)->tqh_first); \ (var); \ (var) = ((var)->field.tqe_next)) #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last)); \ (var); \ (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last))) #define TAILQ_CONCAT(head1, head2, field) do { \ if (!TAILQ_EMPTY(head2)) { \ *(head1)->tqh_last = (head2)->tqh_first; \ (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ (head1)->tqh_last = (head2)->tqh_last; \ TAILQ_INIT((head2)); \ } \ } while (/*CONSTCOND*/0) /* * Tail queue access methods. */ #define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) #define TAILQ_FIRST(head) ((head)->tqh_first) #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) #define TAILQ_LAST(head, headname) \ (*(((struct headname *)((head)->tqh_last))->tqh_last)) #define TAILQ_PREV(elm, headname, field) \ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) /* * Circular queue definitions. */ #define CIRCLEQ_HEAD(name, type) \ struct name { \ struct type *cqh_first; /* first element */ \ struct type *cqh_last; /* last element */ \ } #define CIRCLEQ_HEAD_INITIALIZER(head) \ { (void *)&head, (void *)&head } #define CIRCLEQ_ENTRY(type) \ struct { \ struct type *cqe_next; /* next element */ \ struct type *cqe_prev; /* previous element */ \ } /* * Circular queue functions. */ #define CIRCLEQ_INIT(head) do { \ (head)->cqh_first = (void *)(head); \ (head)->cqh_last = (void *)(head); \ } while (/*CONSTCOND*/0) #define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ (elm)->field.cqe_next = (listelm)->field.cqe_next; \ (elm)->field.cqe_prev = (listelm); \ if ((listelm)->field.cqe_next == (void *)(head)) \ (head)->cqh_last = (elm); \ else \ (listelm)->field.cqe_next->field.cqe_prev = (elm); \ (listelm)->field.cqe_next = (elm); \ } while (/*CONSTCOND*/0) #define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ (elm)->field.cqe_next = (listelm); \ (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ if ((listelm)->field.cqe_prev == (void *)(head)) \ (head)->cqh_first = (elm); \ else \ (listelm)->field.cqe_prev->field.cqe_next = (elm); \ (listelm)->field.cqe_prev = (elm); \ } while (/*CONSTCOND*/0) #define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ (elm)->field.cqe_next = (head)->cqh_first; \ (elm)->field.cqe_prev = (void *)(head); \ if ((head)->cqh_last == (void *)(head)) \ (head)->cqh_last = (elm); \ else \ (head)->cqh_first->field.cqe_prev = (elm); \ (head)->cqh_first = (elm); \ } while (/*CONSTCOND*/0) #define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.cqe_next = (void *)(head); \ (elm)->field.cqe_prev = (head)->cqh_last; \ if ((head)->cqh_first == (void *)(head)) \ (head)->cqh_first = (elm); \ else \ (head)->cqh_last->field.cqe_next = (elm); \ (head)->cqh_last = (elm); \ } while (/*CONSTCOND*/0) #define CIRCLEQ_REMOVE(head, elm, field) do { \ if ((elm)->field.cqe_next == (void *)(head)) \ (head)->cqh_last = (elm)->field.cqe_prev; \ else \ (elm)->field.cqe_next->field.cqe_prev = \ (elm)->field.cqe_prev; \ if ((elm)->field.cqe_prev == (void *)(head)) \ (head)->cqh_first = (elm)->field.cqe_next; \ else \ (elm)->field.cqe_prev->field.cqe_next = \ (elm)->field.cqe_next; \ } while (/*CONSTCOND*/0) #define CIRCLEQ_FOREACH(var, head, field) \ for ((var) = ((head)->cqh_first); \ (var) != (const void *)(head); \ (var) = ((var)->field.cqe_next)) #define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ for ((var) = ((head)->cqh_last); \ (var) != (const void *)(head); \ (var) = ((var)->field.cqe_prev)) /* * Circular queue access methods. */ #define CIRCLEQ_EMPTY(head) ((head)->cqh_first == (void *)(head)) #define CIRCLEQ_FIRST(head) ((head)->cqh_first) #define CIRCLEQ_LAST(head) ((head)->cqh_last) #define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) #define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) #define CIRCLEQ_LOOP_NEXT(head, elm, field) \ (((elm)->field.cqe_next == (void *)(head)) \ ? ((head)->cqh_first) \ : (elm->field.cqe_next)) #define CIRCLEQ_LOOP_PREV(head, elm, field) \ (((elm)->field.cqe_prev == (void *)(head)) \ ? ((head)->cqh_last) \ : (elm->field.cqe_prev)) #endif /* sys/queue.h */ libebur128-1.2.6/test/000077500000000000000000000000001401223205100143445ustar00rootroot00000000000000libebur128-1.2.6/test/CMakeLists.txt000066400000000000000000000020601401223205100171020ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.12) set(ENABLE_TESTS OFF CACHE BOOL "Build test binaries, needs libsndfile") set(ENABLE_FUZZER OFF CACHE BOOL "Build fuzzer binary") if(ENABLE_TESTS) find_package(PkgConfig REQUIRED) find_pkg_config(SNDFILE sndfile REQUIRED) include_directories(${EBUR128_INCLUDE_DIR}) include_directories(SYSTEM ${SNDFILE_INCLUDE_DIRS}) add_executable(r128-test-library tests) add_executable(minimal-example minimal-example) set_property(TARGET r128-test-library APPEND_STRING PROPERTY COMPILE_FLAGS " ${SNDFILE_CFLAGS}") set_property(TARGET minimal-example APPEND_STRING PROPERTY COMPILE_FLAGS " ${SNDFILE_CFLAGS}") target_link_libraries(r128-test-library ebur128 ${SNDFILE_LIBRARIES}) target_link_libraries(minimal-example ebur128 ${SNDFILE_LIBRARIES}) endif() if(ENABLE_FUZZER) include_directories(${EBUR128_INCLUDE_DIR}) add_executable(fuzzer fuzzer) target_link_libraries(fuzzer ebur128) target_compile_options(fuzzer PUBLIC "${FUZZER_FLAGS}") target_link_libraries(fuzzer "${FUZZER_FLAGS}") endif() libebur128-1.2.6/test/fuzzer.cpp000066400000000000000000000054371401223205100164060ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "ebur128.h" static double malloc_fail_rate = 100.0; static double malloc_fail_percentage = 0.995; extern "C" void* my_malloc(size_t size) { if (size > 64 * 1024 * 1024) { return NULL; } if (!(rand() % ((int) malloc_fail_rate))) { return NULL; } malloc_fail_rate *= malloc_fail_percentage; if (malloc_fail_rate < 2) { malloc_fail_rate = 2; } return malloc(size); } extern "C" void* my_calloc(size_t nmemb, size_t size) { if (size * nmemb > 64 * 1024 * 1024) { return NULL; } if (!(rand() % ((int) malloc_fail_rate))) { return NULL; } malloc_fail_rate *= malloc_fail_percentage; if (malloc_fail_rate < 2) { malloc_fail_rate = 2; } return calloc(nmemb, size); } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { unsigned int channels; unsigned long samplerate; int mode; unsigned int new_channels; unsigned long new_samplerate; unsigned long window; unsigned int random_seed; size_t data_offset = sizeof(channels) + sizeof(samplerate) + sizeof(mode) + sizeof(new_channels) + sizeof(new_samplerate) + sizeof(window) + sizeof(random_seed); if (size < data_offset) { return 0; } memcpy(&channels, data, sizeof(channels)); data += sizeof(channels); memcpy(&samplerate, data, sizeof(samplerate)); data += sizeof(samplerate); memcpy(&mode, data, sizeof(mode)); data += sizeof(mode); memcpy(&new_channels, data, sizeof(new_channels)); data += sizeof(new_channels); memcpy(&new_samplerate, data, sizeof(new_samplerate)); data += sizeof(new_samplerate); memcpy(&window, data, sizeof(window)); data += sizeof(window); memcpy(&random_seed, data, sizeof(random_seed)); data += sizeof(random_seed); srand(random_seed); size -= data_offset; if (rand() % 5) { mode &= (1 << 7) - 1; mode |= EBUR128_MODE_TRUE_PEAK | EBUR128_MODE_HISTOGRAM; samplerate %= 96000; new_samplerate %= 96000; channels = 20; new_channels = 30; } if (new_channels < channels) { return 0; } ebur128_state* state = ebur128_init(channels, samplerate, mode); if (state) { if (ebur128_add_frames_int(state, (int const*) data, size / channels / sizeof(int)) || ebur128_set_max_window(state, window) || ebur128_change_parameters(state, new_channels, new_samplerate) || ebur128_add_frames_int(state, (int const*) data, size / new_channels / sizeof(int))) { ebur128_destroy(&state); return 0; } ebur128_destroy(&state); } return 0; } libebur128-1.2.6/test/minimal-example.c000066400000000000000000000043661401223205100176000ustar00rootroot00000000000000/* See COPYING file for copyright and license details. */ #include #include #include #include "ebur128.h" int main(int ac, const char* av[]) { SF_INFO file_info; SNDFILE* file; sf_count_t nr_frames_read; ebur128_state** sts = NULL; double* buffer; double loudness; int i; if (ac < 2) { fprintf(stderr, "usage: %s FILENAME...\n", av[0]); exit(1); } sts = malloc((size_t) (ac - 1) * sizeof(ebur128_state*)); if (!sts) { fprintf(stderr, "malloc failed\n"); return 1; } for (i = 0; i < ac - 1; ++i) { memset(&file_info, '\0', sizeof(file_info)); file = sf_open(av[i + 1], SFM_READ, &file_info); if (!file) { fprintf(stderr, "Could not open file with sf_open!\n"); return 1; } sts[i] = ebur128_init((unsigned) file_info.channels, (unsigned) file_info.samplerate, EBUR128_MODE_I); if (!sts[i]) { fprintf(stderr, "Could not create ebur128_state!\n"); return 1; } /* example: set channel map (note: see ebur128.h for the default map) */ if (file_info.channels == 5) { ebur128_set_channel(sts[i], 0, EBUR128_LEFT); ebur128_set_channel(sts[i], 1, EBUR128_RIGHT); ebur128_set_channel(sts[i], 2, EBUR128_CENTER); ebur128_set_channel(sts[i], 3, EBUR128_LEFT_SURROUND); ebur128_set_channel(sts[i], 4, EBUR128_RIGHT_SURROUND); } buffer = (double*) malloc(sts[i]->samplerate * sts[i]->channels * sizeof(double)); if (!buffer) { fprintf(stderr, "malloc failed\n"); return 1; } while ((nr_frames_read = sf_readf_double( file, buffer, (sf_count_t) sts[i]->samplerate))) { ebur128_add_frames_double(sts[i], buffer, (size_t) nr_frames_read); } ebur128_loudness_global(sts[i], &loudness); fprintf(stderr, "%.2f LUFS, %s\n", loudness, av[i + 1]); free(buffer); buffer = NULL; if (sf_close(file)) { fprintf(stderr, "Could not close input file!\n"); } } ebur128_loudness_global_multiple(sts, (size_t) ac - 1, &loudness); fprintf(stderr, "-----------\n%.2f LUFS\n", loudness); /* clean up */ for (i = 0; i < ac - 1; ++i) { ebur128_destroy(&sts[i]); } free(sts); return 0; } libebur128-1.2.6/test/tests.c000066400000000000000000000372271401223205100156650ustar00rootroot00000000000000/* See COPYING file for copyright and license details. */ #include #include #include #include #include "ebur128.h" double test_global_loudness(const char* filename, ebur128_state** out_state) { SF_INFO file_info; SNDFILE* file; sf_count_t nr_frames_read; ebur128_state* st = NULL; double gated_loudness; double* buffer; memset(&file_info, '\0', sizeof(file_info)); file = sf_open(filename, SFM_READ, &file_info); if (!file) { fprintf(stderr, "Could not open file %s!\n", filename); return 0.0; } st = ebur128_init((unsigned) file_info.channels, (unsigned) file_info.samplerate, EBUR128_MODE_I); if (file_info.channels == 5) { ebur128_set_channel(st, 0, EBUR128_LEFT); ebur128_set_channel(st, 1, EBUR128_RIGHT); ebur128_set_channel(st, 2, EBUR128_CENTER); ebur128_set_channel(st, 3, EBUR128_LEFT_SURROUND); ebur128_set_channel(st, 4, EBUR128_RIGHT_SURROUND); } buffer = (double*) malloc(st->samplerate * st->channels * sizeof(double)); while ((nr_frames_read = sf_readf_double(file, buffer, (sf_count_t) st->samplerate))) { ebur128_add_frames_double(st, buffer, (size_t) nr_frames_read); } ebur128_loudness_global(st, &gated_loudness); *out_state = st; free(buffer); buffer = NULL; if (sf_close(file)) { fprintf(stderr, "Could not close input file!\n"); } return gated_loudness; } double test_loudness_range(const char* filename) { SF_INFO file_info; SNDFILE* file; sf_count_t nr_frames_read; ebur128_state* st = NULL; double loudness_range; double* buffer; memset(&file_info, '\0', sizeof(file_info)); file = sf_open(filename, SFM_READ, &file_info); if (!file) { fprintf(stderr, "Could not open file %s!\n", filename); return 0.0; } st = ebur128_init((unsigned) file_info.channels, (unsigned) file_info.samplerate, EBUR128_MODE_LRA); if (file_info.channels == 5) { ebur128_set_channel(st, 0, EBUR128_LEFT); ebur128_set_channel(st, 1, EBUR128_RIGHT); ebur128_set_channel(st, 2, EBUR128_CENTER); ebur128_set_channel(st, 3, EBUR128_LEFT_SURROUND); ebur128_set_channel(st, 4, EBUR128_RIGHT_SURROUND); } buffer = (double*) malloc(st->samplerate * st->channels * sizeof(double)); while ((nr_frames_read = sf_readf_double(file, buffer, (sf_count_t) st->samplerate))) { ebur128_add_frames_double(st, buffer, (size_t) nr_frames_read); } ebur128_loudness_range(st, &loudness_range); /* clean up */ ebur128_destroy(&st); free(buffer); buffer = NULL; if (sf_close(file)) { fprintf(stderr, "Could not close input file!\n"); } return loudness_range; } double test_true_peak(const char* filename) { SF_INFO file_info; SNDFILE* file; sf_count_t nr_frames_read; int i; ebur128_state* st = NULL; double true_peak; double max_true_peak = -HUGE_VAL; double* buffer; memset(&file_info, '\0', sizeof(file_info)); file = sf_open(filename, SFM_READ, &file_info); if (!file) { fprintf(stderr, "Could not open file %s!\n", filename); return 0.0; } st = ebur128_init((unsigned) file_info.channels, (unsigned) file_info.samplerate, EBUR128_MODE_TRUE_PEAK); if (file_info.channels == 5) { ebur128_set_channel(st, 0, EBUR128_LEFT); ebur128_set_channel(st, 1, EBUR128_RIGHT); ebur128_set_channel(st, 2, EBUR128_CENTER); ebur128_set_channel(st, 3, EBUR128_LEFT_SURROUND); ebur128_set_channel(st, 4, EBUR128_RIGHT_SURROUND); } buffer = (double*) malloc(st->samplerate * st->channels * sizeof(double)); while ((nr_frames_read = sf_readf_double(file, buffer, (sf_count_t) st->samplerate))) { ebur128_add_frames_double(st, buffer, (size_t) nr_frames_read); } for (i = 0; i < file_info.channels; i++) { ebur128_true_peak(st, (unsigned) i, &true_peak); if (true_peak > max_true_peak) max_true_peak = true_peak; } /* clean up */ ebur128_destroy(&st); free(buffer); buffer = NULL; if (sf_close(file)) { fprintf(stderr, "Could not close input file!\n"); } return 20 * log10(max_true_peak); } double test_max_momentary(const char* filename) { SF_INFO file_info; SNDFILE* file; sf_count_t nr_frames_read; sf_count_t total_frames_read = 0; ebur128_state* st = NULL; double momentary; double max_momentary = -HUGE_VAL; double* buffer; memset(&file_info, '\0', sizeof(file_info)); file = sf_open(filename, SFM_READ, &file_info); if (!file) { fprintf(stderr, "Could not open file %s!\n", filename); return 0.0; } st = ebur128_init((unsigned) file_info.channels, (unsigned) file_info.samplerate, EBUR128_MODE_M); if (file_info.channels == 5) { ebur128_set_channel(st, 0, EBUR128_LEFT); ebur128_set_channel(st, 1, EBUR128_RIGHT); ebur128_set_channel(st, 2, EBUR128_CENTER); ebur128_set_channel(st, 3, EBUR128_LEFT_SURROUND); ebur128_set_channel(st, 4, EBUR128_RIGHT_SURROUND); } /* 10 ms buffer/ 100 Hz refresh rate as 10 Hz refresh rate fails on several * tests */ buffer = (double*) malloc(st->samplerate / 100 * st->channels * sizeof(double)); while ((nr_frames_read = sf_readf_double( file, buffer, (sf_count_t) st->samplerate / 100))) { ebur128_add_frames_double(st, buffer, (size_t) nr_frames_read); total_frames_read += nr_frames_read; /* invalid results before the first 400 ms */ if (total_frames_read >= 4 * st->samplerate / 10) { ebur128_loudness_momentary(st, &momentary); if (momentary > max_momentary) max_momentary = momentary; } } /* clean up */ ebur128_destroy(&st); free(buffer); buffer = NULL; if (sf_close(file)) { fprintf(stderr, "Could not close input file!\n"); } return max_momentary; } double test_max_shortterm(const char* filename) { SF_INFO file_info; SNDFILE* file; sf_count_t nr_frames_read; sf_count_t total_frames_read = 0; ebur128_state* st = NULL; double shortterm; double max_shortterm = -HUGE_VAL; double* buffer; memset(&file_info, '\0', sizeof(file_info)); file = sf_open(filename, SFM_READ, &file_info); if (!file) { fprintf(stderr, "Could not open file %s!\n", filename); return 0.0; } st = ebur128_init((unsigned) file_info.channels, (unsigned) file_info.samplerate, EBUR128_MODE_S); if (file_info.channels == 5) { ebur128_set_channel(st, 0, EBUR128_LEFT); ebur128_set_channel(st, 1, EBUR128_RIGHT); ebur128_set_channel(st, 2, EBUR128_CENTER); ebur128_set_channel(st, 3, EBUR128_LEFT_SURROUND); ebur128_set_channel(st, 4, EBUR128_RIGHT_SURROUND); } /* 100 ms buffer / 10 Hz refresh rate */ buffer = (double*) malloc(st->samplerate / 10 * st->channels * sizeof(double)); while ((nr_frames_read = sf_readf_double(file, buffer, (sf_count_t) st->samplerate / 10))) { ebur128_add_frames_double(st, buffer, (size_t) nr_frames_read); total_frames_read += nr_frames_read; /* invalid results before the first 3 s */ if (total_frames_read >= 3 * st->samplerate) { ebur128_loudness_shortterm(st, &shortterm); if (shortterm > max_shortterm) max_shortterm = shortterm; } } /* clean up */ ebur128_destroy(&st); free(buffer); buffer = NULL; if (sf_close(file)) { fprintf(stderr, "Could not close input file!\n"); } return max_shortterm; } double gr[] = { -23.0, -33.0, -23.0, -23.0, -23.0, -23.0, -23.0, -23.0, -23.0 }; double gre[] = { -2.2953556442089987e+01, -3.2959860397340044e+01, -2.2995899818255047e+01, -2.3035918615414182e+01, -2.2949997446096436e+01, -2.3017157781104373e+01, -2.3017157781104373e+01, -2.2980242495081757e+01, -2.3009077718930545e+01 }; double lra[] = { 10.0, 5.0, 20.0, 15.0, 5.0, 15.0 }; double lrae[] = { 1.0001105488329134e+01, 4.9993734051522178e+00, 1.9995064067783115e+01, 1.4999273937723455e+01, 4.9747585878473721e+00, 1.4993650849123316e+01 }; int main() { double result; ebur128_state* states[9] = { 0 }; int i; fprintf(stderr, "Note: the tests do not have to pass with EXACT_PASSED.\n" "Passing these tests does not mean that the library is " "100%% EBU R 128 compliant!\n\n"); #define TEST_GLOBAL_LOUDNESS(filename, i, state_array) \ result = test_global_loudness(filename, &state_array[i]); \ if (result == result) { \ printf("%s, %s - %s: %1.16e\n", \ (result <= gr[i] + 0.1 && result >= gr[i] - 0.1) ? "PASSED" \ : "FAILED", \ (result == gre[i]) ? "EXACT_PASSED" : "EXACT_FAILED", filename, \ result); \ } TEST_GLOBAL_LOUDNESS("seq-3341-1-16bit.wav", 0, states) TEST_GLOBAL_LOUDNESS("seq-3341-2-16bit.wav", 1, states) TEST_GLOBAL_LOUDNESS("seq-3341-3-16bit-v02.wav", 2, states) TEST_GLOBAL_LOUDNESS("seq-3341-4-16bit-v02.wav", 3, states) TEST_GLOBAL_LOUDNESS("seq-3341-5-16bit-v02.wav", 4, states) TEST_GLOBAL_LOUDNESS("seq-3341-6-5channels-16bit.wav", 5, states) TEST_GLOBAL_LOUDNESS("seq-3341-6-6channels-WAVEEX-16bit.wav", 6, states) TEST_GLOBAL_LOUDNESS("seq-3341-7_seq-3342-5-24bit.wav", 7, states) TEST_GLOBAL_LOUDNESS("seq-3341-2011-8_seq-3342-6-24bit-v02.wav", 8, states) /* Move some states around, to make the bug where * ebur128_loudness_global_multiple() calculated the relative threshold just * from the last state easier to reproduce. Don't care for leaks, etc. */ states[5] = states[0]; states[0] = states[8]; for (i = 0; i < 6; ++i) { if (states[i] == NULL) { printf("FAILED, ebur128_loudness_global_multiple needs all states of " "seq-3341-1 until seq-3341-5 (inclusive) and " "seq-3341-2011-8_seq-3342-6-24bit\n"); goto after_multiple_test; } } result = 0; ebur128_loudness_global_multiple(states, 6, &result); fprintf(stderr, "multiple output: %f\n", result); if (result >= -23.18758 - 0.05 && result <= -23.18758 + 0.05) { printf("PASSED, ebur128_loudness_global_multiple\n"); } else { printf("FAILED, ebur128_loudness_global_multiple\n"); } after_multiple_test:; #define TEST_LRA(filename, i) \ result = test_loudness_range(filename); \ if (result == result) { \ printf("%s, %s - %s: %1.16e\n", \ (result <= lra[i] + 1 && result >= lra[i] - 1) ? "PASSED" \ : "FAILED", \ (result == lrae[i]) ? "EXACT_PASSED" : "EXACT_FAILED", filename, \ result); \ } TEST_LRA("seq-3342-1-16bit.wav", 0) TEST_LRA("seq-3342-2-16bit.wav", 1) TEST_LRA("seq-3342-3-16bit.wav", 2) TEST_LRA("seq-3342-4-16bit.wav", 3) TEST_LRA("seq-3341-7_seq-3342-5-24bit.wav", 4) TEST_LRA("seq-3341-2011-8_seq-3342-6-24bit-v02.wav", 5) #define TEST_MAX_TRUE_PEAK(filename, expected) \ result = test_true_peak(filename); \ if (result == result) { \ printf("%s - %s: %1.16e\n", \ (result <= expected + 0.2 && result >= expected - 0.4) ? "PASSED" \ : "FAILED", \ filename, result); \ } TEST_MAX_TRUE_PEAK("seq-3341-15-24bit.wav.wav", -6.0) TEST_MAX_TRUE_PEAK("seq-3341-16-24bit.wav.wav", -6.0) TEST_MAX_TRUE_PEAK("seq-3341-17-24bit.wav.wav", -6.0) TEST_MAX_TRUE_PEAK("seq-3341-18-24bit.wav.wav", -6.0) TEST_MAX_TRUE_PEAK("seq-3341-19-24bit.wav.wav", 3.0) TEST_MAX_TRUE_PEAK("seq-3341-20-24bit.wav.wav", 0.0) TEST_MAX_TRUE_PEAK("seq-3341-21-24bit.wav.wav", 0.0) TEST_MAX_TRUE_PEAK("seq-3341-22-24bit.wav.wav", 0.0) TEST_MAX_TRUE_PEAK("seq-3341-23-24bit.wav.wav", 0.0) #define TEST_MAX_MOMENTARY(filename, expected) \ result = test_max_momentary(filename); \ if (result == result) { \ printf("%s - %s: %1.16e\n", \ (result <= expected + 0.1 && result >= expected - 0.1) ? "PASSED" \ : "FAILED", \ filename, result); \ } TEST_MAX_MOMENTARY("seq-3341-13-1-24bit.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-2-24bit.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-3-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-4-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-5-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-6-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-7-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-8-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-9-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-10-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-11-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-12-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-13-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-14-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-15-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-16-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-17-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-18-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-19-24bit.wav.wav", -23.0) TEST_MAX_MOMENTARY("seq-3341-13-20-24bit.wav.wav", -23.0) #define TEST_MAX_SHORTTERM(filename, expected) \ result = test_max_shortterm(filename); \ if (result == result) { \ printf("%s - %s: %1.16e\n", \ (result <= expected + 0.1 && result >= expected - 0.1) ? "PASSED" \ : "FAILED", \ filename, result); \ } TEST_MAX_SHORTTERM("seq-3341-10-1-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-2-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-3-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-4-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-5-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-6-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-7-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-8-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-9-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-10-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-11-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-12-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-13-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-14-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-15-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-16-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-17-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-18-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-19-24bit.wav", -23.0) TEST_MAX_SHORTTERM("seq-3341-10-20-24bit.wav", -23.0) return 0; }