diff options
author | Andrew Allen <bitllama@google.com> | 2017-11-08 00:26:23 +0300 |
---|---|---|
committer | Jean-Marc Valin <jmvalin@jmvalin.ca> | 2017-11-08 01:44:36 +0300 |
commit | f643c03fba45ad3547b0e54cc6c0f570361002bf (patch) | |
tree | 38e6605bf9b39151299c364731ec7dde3c92aefc /tests | |
parent | e12df85a62598c7acbee84f13602f82eb6d39734 (diff) |
Support for Channel Mapping 253
OpusProjection* classes
MixingMatrix class
Projection tests
Change-Id: I98644466abf4ffd36e48bdecad1204d69e1539b9
Signed-off-by: Jean-Marc Valin <jmvalin@jmvalin.ca>
Diffstat (limited to 'tests')
-rw-r--r-- | tests/test_opus_projection.c | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/tests/test_opus_projection.c b/tests/test_opus_projection.c new file mode 100644 index 00000000..4b11c129 --- /dev/null +++ b/tests/test_opus_projection.c @@ -0,0 +1,431 @@ +/* Copyright (c) 2017 Google Inc. + Written by Andrew Allen */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include "float_cast.h" +#include "opus.h" +#include "test_opus_common.h" +#include "opus_projection.h" +#include "mathops.h" +#include "../src/mapping_matrix.c" +#include "mathops.c" + +#ifdef ENABLE_EXPERIMENTAL_AMBISONICS + +#define BUFFER_SIZE 960 +#define MAX_DATA_BYTES 32768 +#define MAX_FRAME_SAMPLES 5760 + +#define INT16_TO_FLOAT(x) ((1/32768.f)*(float)x) + +void print_matrix_short(const opus_int16 *data, int rows, int cols) +{ + int i, j; + for (i = 0; i < rows; i++) + { + for (j = 0; j < cols; j++) + { + fprintf(stderr, "%8.5f ", (float)INT16_TO_FLOAT(data[j * rows + i])); + } + fprintf(stderr, "\n"); + } + fprintf(stderr, "\n"); +} + +void print_matrix_float(const float *data, int rows, int cols) +{ + int i, j; + for (i = 0; i < rows; i++) + { + for (j = 0; j < cols; j++) + { + fprintf(stderr, "%8.5f ", data[j * rows + i]); + } + fprintf(stderr, "\n"); + } + fprintf(stderr, "\n"); +} + +void print_matrix(MappingMatrix *matrix) +{ + opus_int16 *data; + + fprintf(stderr, "%d x %d, gain: %d\n", matrix->rows, matrix->cols, + matrix->gain); + + data = mapping_matrix_get_data(matrix); + print_matrix_short(data, matrix->rows, matrix->cols); +} + +int assert_transform_short( + const opus_int16 *a, const opus_int16 *b, int size, opus_int16 tolerance) +{ + int i; + for (i = 0; i < size; i++) + { + if (abs(a[i] - b[i]) > tolerance) + { + return 0; + } + } + return 1; +} + +int assert_transform_float( + const float *a, const float *b, int size, float tolerance) +{ + int i; + for (i = 0; i < size; i++) + { + if (fabsf(a[i] - b[i]) > tolerance) + { + return 0; + } + } + return 1; +} + +void test_matrix_transform(void) +{ + /* Create testing mixing matrix (4 x 3), gain 0dB: + * [ 0 1 0 ] + * [ 1 0 0 ] + * [ 0 0 0 ] + * [ 0 0 1 ] + */ + opus_int32 matrix_size; + MappingMatrix *testing_matrix; + const opus_int16 testing_matrix_data[12] = { + 0, 32767, 0, 0, 32767, 0, 0, 0, 0, 0, 0, 32767 }; + + const int frame_size = 10; + const opus_int16 input[30] = { + 32767, 0, -32768, 29491, -3277, -29491, 26214, -6554, -26214, 22938, -9830, + -22938, 19661, -13107, -19661, 16384, -16384, -16384, 13107, -19661, -13107, + 9830, -22938, -9830, 6554, -26214, -6554, 3277, -29491, -3277}; + const opus_int16 expected_output[40] = { + 0, 32767, 0, -32768, -3277, 29491, 0, -29491, -6554, 26214, 0, -26214, + -9830, 22938, 0, -22938, -13107, 19661, 0, -19661, -16384, 16384, 0, -16384, + -19661, 13107, 0, -13107, -22938, 9830, 0, -9830, -26214, 6554, 0, -6554, + -29491, 3277, 0, -3277}; + opus_int16 output[40] = {0}; + +#ifndef DISABLE_FLOAT_API + int i; + /* Sample-accurate to -93.9794 dB */ + float flt_tolerance = 2e-5f; + float input32[30] = {0}; + float output32[40] = {0}; + float expected_output32[40] = {0}; + + /* Convert short to float representations. */ + for (i = 0; i < 30; i++) + { + input32[i] = INT16_TO_FLOAT(input[i]); + } + for (i = 0; i < 40; i++) + { + expected_output32[i] = INT16_TO_FLOAT(expected_output[i]); + } +#endif /* DISABLE_FLOAT_API */ + + /* Create the matrix. */ + matrix_size = mapping_matrix_get_size(4, 3); + testing_matrix = (MappingMatrix *)opus_alloc(matrix_size); + mapping_matrix_init(testing_matrix, 4, 3, 0, testing_matrix_data, + 12 * sizeof(opus_int16)); + + mapping_matrix_multiply_short(testing_matrix, input, testing_matrix->cols, + output, testing_matrix->rows, frame_size); + if (!assert_transform_short(output, expected_output, 40, 1)) + { + fprintf(stderr, "Matrix:\n"); + print_matrix(testing_matrix); + + fprintf(stderr, "Input (short):\n"); + print_matrix_short(input, testing_matrix->cols, frame_size); + + fprintf(stderr, "Expected Output (short):\n"); + print_matrix_short(expected_output, testing_matrix->rows, frame_size); + + fprintf(stderr, "Output (short):\n"); + print_matrix_short(output, testing_matrix->rows, frame_size); + + goto bad_cleanup; + } + +#ifndef DISABLE_FLOAT_API + mapping_matrix_multiply_float(testing_matrix, input32, testing_matrix->cols, + output32, testing_matrix->rows, frame_size); + if (!assert_transform_float(output32, expected_output32, 40, flt_tolerance)) + { + fprintf(stderr, "Matrix:\n"); + print_matrix(testing_matrix); + + fprintf(stderr, "Input (float):\n"); + print_matrix_float(input32, testing_matrix->cols, frame_size); + + fprintf(stderr, "Expected Output (float):\n"); + print_matrix_float(expected_output32, testing_matrix->rows, frame_size); + + fprintf(stderr, "Output (float):\n"); + print_matrix_float(output32, testing_matrix->rows, frame_size); + + goto bad_cleanup; + } +#endif + opus_free(testing_matrix); + return; +bad_cleanup: + opus_free(testing_matrix); + test_failed(); +} + +void test_creation_arguments(const int channels, const int mapping_family) +{ + int streams; + int coupled_streams; + int enc_error; + int dec_error; + int ret; + OpusProjectionEncoder *st_enc = NULL; + OpusProjectionDecoder *st_dec = NULL; + + const opus_int32 Fs = 48000; + const int application = OPUS_APPLICATION_AUDIO; + + int order_plus_one = (int)floor(sqrt((float)channels)); + int nondiegetic_channels = channels - order_plus_one * order_plus_one; + + int is_channels_valid = 0; + int is_projection_valid = 0; + + st_enc = opus_projection_ambisonics_encoder_create(Fs, channels, + mapping_family, &streams, &coupled_streams, application, &enc_error); + if (st_enc != NULL) + { + opus_int32 matrix_size; + unsigned char *matrix; + + ret = opus_projection_encoder_ctl(st_enc, + OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST, &matrix_size); + if (ret != OPUS_OK || !matrix_size) + test_failed(); + + matrix = (unsigned char *)opus_alloc(matrix_size); + ret = opus_projection_encoder_ctl(st_enc, + OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST, matrix, matrix_size); + + opus_projection_encoder_destroy(st_enc); + + st_dec = opus_projection_decoder_create(Fs, channels, streams, + coupled_streams, matrix, matrix_size, &dec_error); + if (st_dec != NULL) + { + opus_projection_decoder_destroy(st_dec); + } + opus_free(matrix); + } + + is_channels_valid = (order_plus_one >= 2 && order_plus_one <= 4) && + (nondiegetic_channels == 0 || nondiegetic_channels == 2); + is_projection_valid = (enc_error == OPUS_OK && dec_error == OPUS_OK); + if (is_channels_valid ^ is_projection_valid) + { + fprintf(stderr, "Channels: %d, Family: %d\n", channels, mapping_family); + fprintf(stderr, "Order+1: %d, Non-diegetic Channels: %d\n", + order_plus_one, nondiegetic_channels); + fprintf(stderr, "Streams: %d, Coupled Streams: %d\n", + streams, coupled_streams); + test_failed(); + } +} + +void generate_music(short *buf, opus_int32 len, opus_int32 channels) +{ + opus_int32 i,j,k; + opus_int32 *a,*b,*c,*d; + a = (opus_int32 *)malloc(sizeof(opus_int32) * channels); + b = (opus_int32 *)malloc(sizeof(opus_int32) * channels); + c = (opus_int32 *)malloc(sizeof(opus_int32) * channels); + d = (opus_int32 *)malloc(sizeof(opus_int32) * channels); + memset(a, 0, sizeof(opus_int32) * channels); + memset(b, 0, sizeof(opus_int32) * channels); + memset(c, 0, sizeof(opus_int32) * channels); + memset(d, 0, sizeof(opus_int32) * channels); + j=0; + + for(i=0;i<len;i++) + { + for(k=0;k<channels;k++) + { + opus_uint32 r; + opus_int32 v; + v=(((j*((j>>12)^((j>>10|j>>12)&26&j>>7)))&128)+128)<<15; + r=fast_rand();v+=r&65535;v-=r>>16; + b[k]=v-a[k]+((b[k]*61+32)>>6);a[k]=v; + c[k]=(30*(c[k]+b[k]+d[k])+32)>>6;d[k]=b[k]; + v=(c[k]+128)>>8; + buf[i*channels+k]=v>32767?32767:(v<-32768?-32768:v); + if(i%6==0)j++; + } + } + + free(a); + free(b); + free(c); + free(d); +} + +void test_encode_decode(opus_int32 bitrate, opus_int32 channels, + const int mapping_family) +{ + const opus_int32 Fs = 48000; + const int application = OPUS_APPLICATION_AUDIO; + + OpusProjectionEncoder *st_enc; + OpusProjectionDecoder *st_dec; + int streams; + int coupled; + int error; + short *buffer_in; + short *buffer_out; + unsigned char data[MAX_DATA_BYTES] = { 0 }; + int len; + int out_samples; + opus_int32 matrix_size = 0; + unsigned char *matrix = NULL; + + buffer_in = (short *)malloc(sizeof(short) * BUFFER_SIZE * channels); + buffer_out = (short *)malloc(sizeof(short) * BUFFER_SIZE * channels); + + st_enc = opus_projection_ambisonics_encoder_create(Fs, channels, + mapping_family, &streams, &coupled, application, &error); + if (error != OPUS_OK) { + fprintf(stderr, + "Couldn\'t create encoder with %d channels and mapping family %d.\n", + channels, mapping_family); + free(buffer_in); + free(buffer_out); + test_failed(); + } + + error = opus_projection_encoder_ctl(st_enc, + OPUS_SET_BITRATE(bitrate * 1000 * (streams + coupled))); + if (error != OPUS_OK) + { + goto bad_cleanup; + } + + error = opus_projection_encoder_ctl(st_enc, + OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST, &matrix_size); + if (error != OPUS_OK || !matrix_size) + { + goto bad_cleanup; + } + + matrix = (unsigned char *)opus_alloc(matrix_size); + error = opus_projection_encoder_ctl(st_enc, + OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST, matrix, matrix_size); + + st_dec = opus_projection_decoder_create(Fs, channels, streams, coupled, + matrix, matrix_size, &error); + opus_free(matrix); + + if (error != OPUS_OK) { + fprintf(stderr, + "Couldn\'t create decoder with %d channels, %d streams " + "and %d coupled streams.\n", channels, streams, coupled); + goto bad_cleanup; + } + + generate_music(buffer_in, BUFFER_SIZE, channels); + + len = opus_projection_encode( + st_enc, buffer_in, BUFFER_SIZE, data, MAX_DATA_BYTES); + if(len<0 || len>MAX_DATA_BYTES) { + fprintf(stderr,"opus_encode() returned %d\n", len); + goto bad_cleanup; + } + + out_samples = opus_projection_decode( + st_dec, data, len, buffer_out, MAX_FRAME_SAMPLES, 0); + if(out_samples!=BUFFER_SIZE) { + fprintf(stderr,"opus_decode() returned %d\n", out_samples); + goto bad_cleanup; + } + + free(buffer_in); + free(buffer_out); + return; +bad_cleanup: + free(buffer_in); + free(buffer_out); + test_failed(); +} + +int main(int _argc, char **_argv) +{ + unsigned int i; + + (void)_argc; + (void)_argv; + + /* Test matrix creation/multiplication. */ + test_matrix_transform(); + + /* Test full range of channels in creation arguments. */ + for (i = 0; i < 255; i++) + test_creation_arguments(i, 253); + + /* Test encode/decode pipeline. */ + test_encode_decode(64 * 16, 16, 253); + + fprintf(stderr, "All projection tests passed.\n"); + return 0; +} + +#else + +int main(int _argc, char **_argv) +{ + (void)_argc; + (void)_argv; + fprintf(stderr, "Projection tests are disabled. " + "Configure with --enable-ambisonics for support.\n"); + return 0; +} + +#endif /* ENABLE_EXPERIMENTAL_AMBISONICS */ |