/*
 * SpanDSP - a series of DSP components for telephony
 *
 * gsm0610_tests.c - Test the GSM 06.10 FR codec.
 *
 * Written by Steve Underwood <steveu@coppice.org>
 *
 * Copyright (C) 2006 Steve Underwood
 *
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2, as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: gsm0610_tests.c,v 1.25 2009/05/30 15:23:13 steveu Exp $
 */

/*! \file */

/*! \page gsm0610_tests_page GSM 06.10 full rate codec tests
\section gsm0610_tests_page_sec_1 What does it do?
Two sets of tests are performed:
    - The tests defined in the GSM 06.10 specification, using the test data files supplied with
      the specification.
    - A generally audio quality test, consisting of compressing and decompressing a speeech
      file for audible comparison.

\section gsm0610_tests_page_sec_2 How is it used?
To perform the tests in the GSM 06.10 specification you need to obtain the test data files from the
specification. These are copyright material, and so cannot be distributed with this test software.
They can, however, be freely downloaded from the ETSI web site.

The files, containing test vectors, which are supplied with the GSM 06.10 specification, should be
copied to etsitests/gsm0610/unpacked so the files are arranged in the following directories.

./fr_A:
    Seq01-A.cod Seq01-A.inp Seq01-A.out
    Seq02-A.cod Seq02-A.inp Seq02-A.out
    Seq03-A.cod Seq03-A.inp Seq03-A.out
    Seq04-A.cod Seq04-A.inp Seq04-A.out
    Seq05-A.out

./fr_L:
    Seq01.cod   Seq01.inp   Seq01.out
    Seq02.cod   Seq02.inp   Seq02.out
    Seq03.cod   Seq03.inp   Seq03.out
    Seq04.cod   Seq04.inp   Seq04.out
    Seq05.cod   Seq05.out

./fr_U:
    Seq01-U.cod Seq01-U.inp Seq01-U.out
    Seq02-U.cod Seq02-U.inp Seq02-U.out
    Seq03-U.cod Seq03-U.inp Seq03-U.out
    Seq04-U.cod Seq04-U.inp Seq04-U.out
    Seq05-U.out

./fr_homing_A:
    Homing01_A.out
    Seq01H_A.cod    Seq01H_A.inp    Seq01H_A.out
    Seq02H_A.cod    Seq02H_A.inp    Seq02H_A.out
    Seq03H_A.cod    Seq03H_A.inp    Seq03H_A.out
    Seq04H_A.cod    Seq04H_A.inp    Seq04H_A.out
    Seq05H_A.out
    Seq06H_A.cod    Seq06H_A.inp

./fr_homing_L:
    Homing01.cod    Homing01.out
    Seq01h.cod      Seq01h.inp      Seq01h.out
    Seq02h.cod      Seq02h.inp      Seq02h.out
    Seq03h.cod      Seq03h.inp      Seq03h.out
    Seq04h.cod      Seq04h.inp      Seq04h.out
    Seq05h.cod      Seq05h.out
    Seq06h.cod      Seq06h.inp

./fr_homing_U:
    Homing01_U.out
    Seq01H_U.cod    Seq01H_U.inp    Seq01H_U.out
    Seq02H_U.cod    Seq02H_U.inp    Seq02H_U.out
    Seq03H_U.cod    Seq03H_U.inp    Seq03H_U.out
    Seq04H_U.cod    Seq04H_U.inp    Seq04H_U.out
    Seq05H_U.out
    Seq06H_U.cod    Seq06H_U.inp

./fr_sync_A:
    Seqsync_A.inp
    Sync000_A.cod   --to--          Sync159_A.cod
    
./fr_sync_L:
    Bitsync.inp
    Seqsync.inp
    Sync000.cod     --to--          Sync159.cod

./fr_sync_U:
    Seqsync_U.inp
    Sync000_U.cod   --to--          Sync159_U.cod

This is different from the directory structure in which they are supplied. Also, the files names are a little
different. The supplied names are messy, and inconsistent across the sets. The names required by these tests
just clean up these inconsistencies. Note that you will need a Windows machine to unpack some of the supplied
files.

To perform a general audio quality test, gsm0610_tests should be run. The file ../test-data/local/short_nb_voice.wav
will be compressed to GSM 06.10 data, decompressed, and the resulting audio stored in post_gsm0610.wav.
*/

#if defined(HAVE_CONFIG_H)
#include "config.h"
#endif

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <sndfile.h>

//#if defined(WITH_SPANDSP_INTERNALS)
#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES
//#endif

#include "spandsp.h"
#include "spandsp-sim.h"

#define BLOCK_LEN       160

#define TESTDATA_DIR "../test-data/etsi/gsm0610/unpacked/fr_"

#define IN_FILE_NAME    "../test-data/local/short_nb_voice.wav"
#define OUT_FILE_NAME   "post_gsm0610.wav"

#define HIST_LEN        1000

uint8_t law_in_vector[1000000];
int16_t in_vector[1000000];
uint16_t code_vector_buf[1000000];
uint8_t code_vector[1000000];
uint8_t ref_code_vector[1000000];
uint8_t decoder_code_vector[1000000];
uint8_t law_out_vector[1000000];
int16_t out_vector[1000000];
int16_t ref_out_vector[1000000];
uint8_t ref_law_out_vector[1000000];
int vector_len;

static int get_test_vector(int full, int disk, const char *name)
{
    char buf[500];
    int in;
    int len;
    int i;
    
    if (full)
    {
        sprintf(buf, "%s%c/%s.inp", TESTDATA_DIR, 'L', name);
        if ((in = open(buf, O_RDONLY)) < 0)
        {
            fprintf(stderr, "Cannot open %s\n", buf);
            exit(2);
        }
        len = read(in, in_vector, 1000000);
        close(in);
        len /= sizeof(int16_t);
        vector_len = len;
    }

    sprintf(buf, "%s%c/%s.out", TESTDATA_DIR, 'L', name);
    if ((in = open(buf, O_RDONLY)) < 0)
    {
        fprintf(stderr, "Cannot open %s\n", buf);
        exit(2);
    }
    len = read(in, ref_out_vector, 1000000);
    close(in);
    len /= sizeof(int16_t);
    if (full)
    {
        if (len != vector_len)
        {
            fprintf(stderr, "Input and reference vector lengths do not match - %d %d\n", vector_len, len);
            exit(2);
        }
    }
    else
    {
        vector_len = len;
    }
    
    sprintf(buf, "%s%c/%s.cod", TESTDATA_DIR, 'L', name);
    if ((in = open(buf, O_RDONLY)) < 0)
    {
        fprintf(stderr, "Cannot open %s\n", buf);
        exit(2);
    }
    len = read(in, code_vector_buf, 1000000);
    close(in);
    len /= sizeof(int16_t);
    for (i = 0;  i < len;  i++)
    {
        ref_code_vector[i] = code_vector_buf[i];
        decoder_code_vector[i] = code_vector_buf[i];
    }
    if (len*BLOCK_LEN != vector_len*76)
    {
        fprintf(stderr, "Input and code vector lengths do not match - %d %d\n", vector_len, len);
        exit(2);
    }

    return len;
}
/*- End of function --------------------------------------------------------*/

static int get_law_test_vector(int full, int law, const char *name)
{
    char buf[500];
    int in;
    int len;
    int i;
    int law_uc;
    
    law_uc = toupper(law);
    
    if (full)
    {
        sprintf(buf, "%s%c/%s-%c.inp", TESTDATA_DIR, law_uc, name, law_uc);
        if ((in = open(buf, O_RDONLY)) < 0)
        {
            fprintf(stderr, "Cannot open %s\n", buf);
            exit(2);
        }
        len = read(in, law_in_vector, 1000000);
        close(in);
        vector_len = len;

        sprintf(buf, "%s%c/%s-%c.cod", TESTDATA_DIR, law_uc, name, law_uc);
        if ((in = open(buf, O_RDONLY)) < 0)
        {
            fprintf(stderr, "Cannot open %s\n", buf);
            exit(2);
        }
        len = read(in, code_vector_buf, 1000000);
        close(in);
        len /= sizeof(int16_t);
        for (i = 0;  i < len;  i++)
            ref_code_vector[i] = code_vector_buf[i];
        if (len*BLOCK_LEN != vector_len*76)
        {
            fprintf(stderr, "Input and code vector lengths do not match - %d %d\n", vector_len, len);
            exit(2);
        }
    }

    sprintf(buf, "%s%c/%s-%c.out", TESTDATA_DIR, law_uc, name, law_uc);
    if ((in = open(buf, O_RDONLY)) < 0)
    {
        fprintf(stderr, "Cannot open %s\n", buf);
        exit(2);
    }
    len = read(in, ref_law_out_vector, 1000000);
    close(in);
    if (full)
    {
        if (len != vector_len)
        {
            fprintf(stderr, "Input and reference vector lengths do not match - %d %d\n", vector_len, len);
            exit(2);
        }
    }
    else
    {
        vector_len = len;
    }

    sprintf(buf, "%s%c/%s.cod", TESTDATA_DIR, 'L', name);
    if ((in = open(buf, O_RDONLY)) < 0)
    {
        fprintf(stderr, "Cannot open %s\n", buf);
        exit(2);
    }
    len = read(in, code_vector_buf, 1000000);
    close(in);
    len /= sizeof(int16_t);
    for (i = 0;  i < len;  i++)
        decoder_code_vector[i] = code_vector_buf[i];
    
    return len;
}
/*- End of function --------------------------------------------------------*/

static int perform_linear_test(int full, int disk, const char *name)
{
    gsm0610_state_t *gsm0610_enc_state;
    gsm0610_state_t *gsm0610_dec_state;
    int i;
    int xxx;
    int mismatches;

    printf("Performing linear test '%s' from disk %d\n", name, disk);

    get_test_vector(full, disk, name);

    if (full)
    {
        if ((gsm0610_enc_state = gsm0610_init(NULL, GSM0610_PACKING_NONE)) == NULL)
        {
            fprintf(stderr, "    Cannot create encoder\n");
            exit(2);
        }
        xxx = gsm0610_encode(gsm0610_enc_state, code_vector, in_vector, vector_len);

        printf("Check code vector of length %d\n", xxx);
        for (i = 0, mismatches = 0;  i < xxx;  i++)
        {
            if (code_vector[i] != ref_code_vector[i])
            {
                printf("%8d/%3d: %6d %6d\n", i/76, i%76, code_vector[i], ref_code_vector[i]);
                mismatches++;
            }
        }
        gsm0610_release(gsm0610_enc_state);
        if (mismatches)
        {
            printf("Test failed: %d of %d samples mismatch\n", mismatches, xxx);
            exit(2);
        }
        printf("Test passed\n");
    }

    if ((gsm0610_dec_state = gsm0610_init(NULL, GSM0610_PACKING_NONE)) == NULL)
    {
        fprintf(stderr, "    Cannot create decoder\n");
        exit(2);
    }
    xxx = gsm0610_decode(gsm0610_dec_state, out_vector, decoder_code_vector, vector_len);
    printf("Check output vector of length %d\n", vector_len);
    for (i = 0, mismatches = 0;  i < vector_len;  i++)
    {
        if (out_vector[i] != ref_out_vector[i])
        {
            printf("%8d: %6d %6d\n", i, out_vector[i], ref_out_vector[i]);
            mismatches++;
        }
    }
    if (mismatches)
    {
        printf("Test failed: %d of %d samples mismatch\n", mismatches, vector_len);
        exit(2);
    }
    gsm0610_release(gsm0610_dec_state);
    printf("Test passed\n");
    return 0;
}
/*- End of function --------------------------------------------------------*/

static int perform_law_test(int full, int law, const char *name)
{
    gsm0610_state_t *gsm0610_enc_state;
    gsm0610_state_t *gsm0610_dec_state;
    int i;
    int xxx;
    int mismatches;

    if (law == 'a')
        printf("Performing A-law test '%s'\n", name);
    else
        printf("Performing u-law test '%s'\n", name);
        
    get_law_test_vector(full, law, name);

    if (full)
    {
        if ((gsm0610_enc_state = gsm0610_init(NULL, GSM0610_PACKING_NONE)) == NULL)
        {
            fprintf(stderr, "    Cannot create encoder\n");
            exit(2);
        }
        if (law == 'a')
        {
            for (i = 0;  i < vector_len;  i++)
                in_vector[i] = alaw_to_linear(law_in_vector[i]);
        }
        else
        {
            for (i = 0;  i < vector_len;  i++)
                in_vector[i] = ulaw_to_linear(law_in_vector[i]);
        }
        xxx = gsm0610_encode(gsm0610_enc_state, code_vector, in_vector, vector_len);

        printf("Check code vector of length %d\n", xxx);
        for (i = 0, mismatches = 0;  i < xxx;  i++)
        {
            if (code_vector[i] != ref_code_vector[i])
            {
                printf("%8d/%3d: %6d %6d %6d\n", i/76, i%76, code_vector[i], ref_code_vector[i], decoder_code_vector[i]);
                mismatches++;
            }
        }
        if (mismatches)
        {
            printf("Test failed: %d of %d samples mismatch\n", mismatches, xxx);
            exit(2);
        }
        printf("Test passed\n");
        gsm0610_release(gsm0610_enc_state);
    }

    if ((gsm0610_dec_state = gsm0610_init(NULL, GSM0610_PACKING_NONE)) == NULL)
    {
        fprintf(stderr, "    Cannot create decoder\n");
        exit(2);
    }
    xxx = gsm0610_decode(gsm0610_dec_state, out_vector, decoder_code_vector, vector_len);
    if (law == 'a')
    {
        for (i = 0;  i < vector_len;  i++)
            law_out_vector[i] = linear_to_alaw(out_vector[i]);
    }
    else
    {
        for (i = 0;  i < vector_len;  i++)
            law_out_vector[i] = linear_to_ulaw(out_vector[i]);
    }
    printf("Check output vector of length %d\n", vector_len);
    for (i = 0, mismatches = 0;  i < vector_len;  i++)
    {
        if (law_out_vector[i] != ref_law_out_vector[i])
        {
            printf("%8d: %6d %6d\n", i, law_out_vector[i], ref_law_out_vector[i]);
            mismatches++;
        }
    }
    if (mismatches)
    {
        printf("Test failed: %d of %d samples mismatch\n", mismatches, vector_len);
        exit(2);
    }
    gsm0610_release(gsm0610_dec_state);
    printf("Test passed\n");
    return 0;
}
/*- End of function --------------------------------------------------------*/

static int repack_gsm0610_voip_to_wav49(uint8_t c[], const uint8_t d[])
{
    gsm0610_frame_t frame[2];
    int n;
 
	n = gsm0610_unpack_voip(&frame[0], d);
	gsm0610_unpack_voip(&frame[1], d + n);
    n = gsm0610_pack_wav49(c, frame);
    return n;
}
/*- End of function --------------------------------------------------------*/

static int repack_gsm0610_wav49_to_voip(uint8_t d[], const uint8_t c[])
{
    gsm0610_frame_t frame[2];
    int n[2];

    gsm0610_unpack_wav49(frame, c);
    n[0] = gsm0610_pack_voip(d, &frame[0]);
    n[1] = gsm0610_pack_voip(d + n[0], &frame[1]);
    return n[0] + n[1];
}
/*- End of function --------------------------------------------------------*/

static int perform_pack_unpack_test(void)
{
    uint8_t a[66];
    uint8_t b[66];
    uint8_t c[66];
    int i;
    int j;

    printf("Performing packing/unpacking tests (not part of the ETSI conformance tests).\n");
    /* Try trans-packing a lot of random data looking for before/after mismatch. */
    for (j = 0;  j < 1000;  j++)
    {        
        for (i = 0;  i < 65;  i++)
            a[i] = rand();
        repack_gsm0610_wav49_to_voip(b, a);
        repack_gsm0610_voip_to_wav49(c, b);
        if (memcmp(a, c, 65))
        {
            printf("Test failed: data mismatch\n");
            exit(2);
        }

        for (i = 0;  i < 66;  i++)
            a[i] = rand();
        /* Insert the magic code */
        a[0] = (a[0] & 0xF) | 0xD0;
        a[33] = (a[33] & 0xF) | 0xD0;
        repack_gsm0610_voip_to_wav49(b, a);
        repack_gsm0610_wav49_to_voip(c, b);
        //for (i = 0;  i < 66;  i++)
        //    printf("%2d: 0x%02X 0x%02X\n", i, a[i], c[i]);
        if (memcmp(a, c, 66))
        {
            printf("Test failed: data mismatch\n");
            exit(2);
        }
    }
    printf("Test passed\n");
    return 0;
}
/*- End of function --------------------------------------------------------*/

static void etsi_compliance_tests(void)
{
    perform_linear_test(TRUE, 1, "Seq01");
    perform_linear_test(TRUE, 1, "Seq02");
    perform_linear_test(TRUE, 1, "Seq03");
    perform_linear_test(TRUE, 1, "Seq04");
    perform_linear_test(FALSE, 1, "Seq05");
    perform_law_test(TRUE, 'a', "Seq01");
    perform_law_test(TRUE, 'a', "Seq02");
    perform_law_test(TRUE, 'a', "Seq03");
    perform_law_test(TRUE, 'a', "Seq04");
    perform_law_test(FALSE, 'a', "Seq05");
    perform_law_test(TRUE, 'u', "Seq01");
    perform_law_test(TRUE, 'u', "Seq02");
    perform_law_test(TRUE, 'u', "Seq03");
    perform_law_test(TRUE, 'u', "Seq04");
    perform_law_test(FALSE, 'u', "Seq05");
    /* This is not actually an ETSI test */
    perform_pack_unpack_test();

    printf("Tests passed.\n");
}
/*- End of function --------------------------------------------------------*/

int main(int argc, char *argv[])
{
    SNDFILE *inhandle;
    SNDFILE *outhandle;
    int frames;
    int outframes;
    int bytes;
    int16_t pre_amp[HIST_LEN];
    int16_t post_amp[HIST_LEN];
    uint8_t gsm0610_data[HIST_LEN];
    gsm0610_state_t *gsm0610_enc_state;
    gsm0610_state_t *gsm0610_dec_state;
    int opt;
    int etsitests;
    int packing;

    etsitests = TRUE;
    packing = GSM0610_PACKING_NONE;
    while ((opt = getopt(argc, argv, "lp:")) != -1)
    {
        switch (opt)
        {
        case 'l':
            etsitests = FALSE;
            break;
        case 'p':
            packing = atoi(optarg);
            break;
        default:
            //usage();
            exit(2);
        }
    }

    if (etsitests)
    {
        etsi_compliance_tests();
    }
    else
    {
        if ((inhandle = sf_open_telephony_read(IN_FILE_NAME, 1)) == NULL)
        {
            fprintf(stderr, "    Cannot open audio file '%s'\n", IN_FILE_NAME);
            exit(2);
        }
        if ((outhandle = sf_open_telephony_write(OUT_FILE_NAME, 1)) == NULL)
        {
            fprintf(stderr, "    Cannot create audio file '%s'\n", OUT_FILE_NAME);
            exit(2);
        }
    
        if ((gsm0610_enc_state = gsm0610_init(NULL, packing)) == NULL)
        {
            fprintf(stderr, "    Cannot create encoder\n");
            exit(2);
        }
            
        if ((gsm0610_dec_state = gsm0610_init(NULL, packing)) == NULL)
        {
            fprintf(stderr, "    Cannot create decoder\n");
            exit(2);
        }

        while ((frames = sf_readf_short(inhandle, pre_amp, 2*BLOCK_LEN)))
        {
            bytes = gsm0610_encode(gsm0610_enc_state, gsm0610_data, pre_amp, frames);
            gsm0610_decode(gsm0610_dec_state, post_amp, gsm0610_data, bytes);
            outframes = sf_writef_short(outhandle, post_amp, frames);
        }
    
        if (sf_close(inhandle) != 0)
        {
            fprintf(stderr, "    Cannot close audio file '%s'\n", IN_FILE_NAME);
            exit(2);
        }
        if (sf_close(outhandle) != 0)
        {
            fprintf(stderr, "    Cannot close audio file '%s'\n", OUT_FILE_NAME);
            exit(2);
        }
        gsm0610_release(gsm0610_enc_state);
        gsm0610_release(gsm0610_dec_state);
    }
    return 0;
}
/*- End of function --------------------------------------------------------*/
/*- End of file ------------------------------------------------------------*/
