/*                                                           21.Mar.2000 V1.3
  ============================================================================

  VBR-G726.C 
  ~~~~~~~~~~

  Description: 
  ~~~~~~~~~~~~
  
  Demonstration program for UGST/ITU-T G.726 module using the variable
  bit rate feature. This version accepts either linear or G.711 A/u-law
  input. Since this implementation of the G.726 requires G.711 compressed
  samples, linear samples are converted to G.711 format before being
  processed. Therefore, the same ammount of quantization distortion should
  be expect either way.
  
  The modules called have been originally written in Fortran, and were
  translated into C by the converter f2c, version of October 15, 1990
  at 19:58:17.
  
  Input data is supposed to be aligned at word boundaries, i.e.,
  organized in 16-bit words, following the operating system normal
  organization (low-byte first for VMS and DOS; high byte first for most
  Unix systems). Linear samples are supposed to be 16-bit right-adjusted. 
  G711 compressed data is supposed to be in the 8 LEAST
  significant bits of the word and the ADPCM data is in the LEAST 5
  bits. Both are without sign extension.
  
  Output data will be generated in the same format as decribed above for
  the input data.
  
  Usage:
  ~~~~~~
  $ VBR-G726 [-options] InpFile OutFile 
             [FrameSize [1stBlock [NoOfBlocks [Reset]]]]
  where:
  InpFile     is the name of the file to be processed;
  OutFile     is the name with the processed data;
  FrameSize   is the frame size, in number of samples; the bitrate 
              will only change in the boundaries of a frame 
              [default: 16 samples]
  1stBlock    is the number of the first block of the input file
              to be processed [default: 1st block]
  NoOfBlocks  is the number of blocks to be processed, starting on
    	      block "1stBlock" [default: all blocks]

  Options:
  -law #      the letters A or a for G.711 A-law, letter u for 
              G.711 u-law, or letter l for linear. If linear is
	      chosen, A-law is used to compress/expand samples to/from
	      the G.726 routines. Default is A-law.
  -rate #     is the bit-rate (in kbit/s): 40, 32, 24 or 16 (in kbit/s); 
              or a combination of them using dashes (e.g. 32-24 or
	      16-24-32). Default is 32 kbit/s.
  -frame #    Number of samples per frame for switching bit rates.
              Default is 16 samples (or 2ms) 
  -enc        run only the G.726 encoder on the samples 
              [default: run encoder and decoder]
  -dec        run only the G.726 decoder on the samples 
              [default: run encoder and decoder]
  -noreset    don't apply reset to the encoder/decoder
  -?/-help    print help message

  Example:
  $ vbr-G726 -law u -enc -rate 32 voice.ref voice.adp 256 3 45

  The command above takes the samples in file "voice.ref", already
  in mu law format, processes the data through the G726 encoder
  only at a rate of 32 bkit/s, saving them into the file
  "voice.rel". The processing starts at block 3 for 45 blocks,
  each block being 256 samples wide.

  Variables:
  ~~~~~~~~~~
  law  	  law to use (either A or u)
  conv    desired processing
  rate    desired rate
  inpfil  input file name;
  outfil  output file name;
  N  	  block size;
  N1  	  first block to process;
  N2  	  no. of blocks to process;

  Exit values:
  ~~~~~~~~~~~~
  0  success (all but VMS);
  1  success (only in VMS);
  2  error opening input file;
  3  error creating output file;
  4  error moving pointer to desired start of conversion;
  5  error reading input file;
  6  error writing to file;
  7  invalid law
  8  invalid conversion
  9  invalid rate

  Original author:
  ~~~~~~~~~~~~~~~~
  Simao Ferraz de Campos Neto
  Comsat Laboratories                  Tel:    +1-301-428-4516
  22300 Comsat Drive                   Fax:    +1-301-428-9287
  Clarksburg MD 20871 - USA            E-mail: simao@ctd.comsat.com

  History:
  ~~~~~~~~
  10/Mar/1995 v1.0  Created based on vbr-g726.c
  22/Feb/1996 v1.1  Removed compilation warnings, included headers as
                    suggested by Kirchherr (FI/DBP Telekom) to run under
		    OpenVMS/AXP <simao@ctd.comsat.com>
  22/Jul/1997 v1.2  Changed static allocation for data arrays to allow longer 
                    frame sizes. Fix based on modifications by R. Kirchherr 
                    <kirchherr@tzd.telekom.de>
  21.Mar.2000 v1.3  Corrected bug when the bitrate is not specified by 
                    the user. Corrected bug that made incorrect
                    calculation on total number of blocks to process
                    when the block size is not a multiple of the file
                    size. <simao.campos@labs.comsat.com>
  ============================================================================
*/

/* ..... General definitions for UGST demo programs ..... */
#include "ugstdemo.h"

/* ..... General include ..... */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <ctype.h>

#if defined(VMS)
#include <unixio.h>
#include <stat.h>
#else                           /* Unix/DOS */
#include <sys/stat.h>
#endif

/* ..... G.726 module as include functions ..... */
#include "g726.h"
#include "g711.h"

/*
 -------------------------------------------------------------------------
 void parse_rate(char *str, short *rate);
 ~~~~~~~~~~~~~~~
 Parses string str with a list of bitrates for the operation of the G726
 algorithm and return a list of them in rate, and the number of rates read.

 Parameters:
 ~~~~~~~~~~~
 str ..... string pointing to a list of dash-separated bitrates to be used.
           Valid examples are: 16 (single rate), 32-24 (duo-rate), etc.
 rate .... string of short numbers with each of the specified rates.

 Return:
 ~~~~~~~
 Returns the number of bitrates for the ADPCM operation, 0 if str is 
 empty or improper, and -1 if there are too many rates requested.

 History:
 ~~~~~~~~
 10.Mar.95 v1.0 Created <simao@ctd.comsat.com>
 -------------------------------------------------------------------------
*/
int parse_rate(str, rate)
char *str;
short **rate;
{
  char *s = str;
  int count = 1, i;

  if (str == NULL)
    return 0;

  while ((s = strchr(s, '-')) != NULL) {
    s++;
    count++;
  }

  /* Allocates memory for the necessary number of rates */
  *rate = (short *) calloc(sizeof(short), count);
  if (*rate == NULL)
    return (-1);

  /* Save rates in the array */
  for (s = strtok(str, "-"), i = 0; i < count; i++) {
    /* Convert to short & save */
    (*rate)[i] = atoi(s);

    /* Classification of rate - return 0 if invalid rate was specified */
    if ((*rate)[i] > 5) {
      if ((*rate)[i] == 40)
        (*rate)[i] = 5;
      else if ((*rate)[i] == 32)
        (*rate)[i] = 4;
      else if ((*rate)[i] == 24)
        (*rate)[i] = 3;
      else if ((*rate)[i] == 16)
        (*rate)[i] = 2;
      else
        return (0);
    }

    /* Update s to the next valid rate number */
    s = strtok(NULL, "-");
  }

  /* Return the number of rates */
  return (count);
}

/* .................... End of parse_rate() ........................... */


/*
 -------------------------------------------------------------------------
 void display_usage(void);
 ~~~~~~~~~~~~~~~~~~
 Display proper usage for the demo program. Generated automatically from
 program documentation.

 History:
 ~~~~~~~~
 10.Mar.95 v1.0 Created <simao>.
 -------------------------------------------------------------------------
*/
#define P(x) printf x
void display_usage()
{
  P(("Version 1.3 of 21/Mar/2000 \n\n"));

  P(("  VBR-G726.C \n"));
  P(("  Demonstration program for UGST/ITU-T G.726 module using the variable\n"));
  P(("  bit rate feature. This version accepts either linear or G.711 A/u-law\n"));
  P(("  input. Since this implementation of the G.726 requires G.711 compressed\n"));
  P(("  samples, linear samples are converted to G.711 format before being\n"));
  P(("  processed. Therefore, the same ammount of quantization distortion should\n"));
  P(("  be expect either way.\n"));
  P(("  Input data is supposed to be aligned at word boundaries, i.e.,\n"));
  P(("  organized in 16-bit words, following the operating system normal\n"));
  P(("  organization (low-byte first for VMS and DOS; high byte first for most\n"));
  P(("  Unix systems). Linear samples are supposed to be 16-bit right-adjusted. \n"));
  P(("  G711 compressed data is supposed to be in the 8 LEAST\n"));
  P(("  significant bits of the word and the ADPCM data is in the LEAST 5\n"));
  P(("  bits. Both are without sign extension.\n"));
  P(("  \n"));
  P(("  Output data will be generated in the same format as decribed above for\n"));
  P(("  the input data.\n"));
  P(("  \n"));
  P(("  Usage:\n"));
  P(("  VBR-G726 [-options] InpFile OutFile \n"));
  P(("             [FrameSize [1stBlock [NoOfBlocks [Reset]]]]\n"));
  P(("  where:\n"));
  P(("  InpFile     is the name of the file to be processed;\n"));
  P(("  OutFile     is the name with the processed data;\n"));
  P(("  FrameSize   is the frame size, in number of samples; the bitrate \n"));
  P(("              will only change in the boundaries of a frame \n"));
  P(("              [default: 16 samples]\n"));
  P(("  1stBlock    is the number of the first block of the input file\n"));
  P(("              to be processed [default: 1st block]\n"));
  P(("  NoOfBlocks  is the number of blocks to be processed, starting on\n"));
  P(("              block \"1stBlock\" [default: all blocks]\n"));
  P(("\n"));
  P(("  Options:\n"));
  P(("  -law #      the letters A or a for G.711 A-law, letter u for \n"));
  P(("              G.711 u-law, or letter l for linear. If linear is\n"));
  P(("              chosen, A-law is used to compress/expand samples to/from\n"));
  P(("              the G.726 routines. Default is A-law.\n"));
  P(("  -rate #     is the bit-rate (in kbit/s): 40, 32, 24 or 16 (in kbit/s); \n"));
  P(("              or a combination of them using dashes (e.g. 32-24 or\n"));
  P(("              16-24-32). Default is 32 kbit/s.\n"));
  P(("  -frame #    Number of samples per frame for switching bit rates.\n"));
  P(("              Default is 16 samples (or 2ms) \n"));
  P(("  -enc        run only the G.726 encoder on the samples \n"));
  P(("              [default: run encoder and decoder]\n"));
  P(("  -dec        run only the G.726 decoder on the samples \n"));
  P(("              [default: run encoder and decoder]\n"));
  P(("  -noreset    don't apply reset to the encoder/decoder\n"));
  P(("  -?/-help    print help message\n"));
  P(("\n"));

  /* Quit program */
  exit(-128);
}

#undef P
/* .................... End of display_usage() ........................... */


void G726_initEncode(G726_state *encoder_state)
{
  memset(encoder_state, 0, sizeof(G726_state));
}

void G726_initDecode(G726_state *decoder_state)
{
  memset(decoder_state, 0, sizeof(G726_state));
}

void g726pack(Byte *packed, short *in, int cnt)
{
  int i;
  for (i = 0; i <= cnt; i += 2, ++packed, ++in) {
    *packed = (*in & 0xF);
    ++in;
    *packed += (*in & 0xF) * 16;
  }
}

void g726unpack(short *out, Byte *packed, int cnt)
{
  int i;
  for (i = 0; i <= cnt; i += 2, ++packed, ++out) {
    *out = (*packed & 0xF);
    ++out;
    *out = (*packed & 0xF0) / 16;
  }
}

/*
   **************************************************************************
   ***                                                                    ***
   ***        Demo-Program for testing the correct implementation         ***
   ***               and to show how to use the programs                  ***
   ***                                                                    ***
   **************************************************************************
*/
int main(argc, argv)
int argc;
char *argv[];
{
  G726_state encoder_state, decoder_state;
  long N = 16, N1 = 1, N2 = 0, cur_blk, smpno;
  short *tmp_buf, *inp_buf, *out_buf, reset = 1;
  Byte tmpb_buf[N], outb_buf[N];
  short inp_type, out_type, *rate = 0;
  char encode = 1, decode = 1, law[4] = "A", def_rate[] = "32";
  int rateno = 1, rate_idx;

  /* General-purpose, progress indication */
  static char quiet = 0, funny[9] = "|/-\\|/-\\";

/* File variables */
  char FileIn[80], FileOut[80];
  FILE *Fi, *Fo;
  int inp, out;
  long start_byte;
#ifdef VMS
  char mrs[15];
#endif

/*
 * ......... PARAMETERS FOR PROCESSING .........
 */

  /* GETTING OPTIONS */

  if (argc < 2)
    display_usage();
  else {
    while (argc > 1 && argv[1][0] == '-')
      if (strcmp(argv[1], "-noreset") == 0) {
        /* No reset */
        reset = 0;

        /* Update argc/argv to next valid option/argument */
        argv++;
        argc--;
      } else if (strcmp(argv[1], "-enc") == 0) {
        /* Encoder-only operation */
        encode = 1;
        decode = 0;

        /* Move argv over the option to the next argument */
        argv++;
        argc--;
      } else if (strcmp(argv[1], "-dec") == 0) {
        /*Decoder-only operation */
        encode = 0;
        decode = 1;

        /* Move argv over the option to the next argument */
        argv++;
        argc--;
      } else if (strcmp(argv[1], "-law") == 0) {
        /* Define law for operation: A, u, or linear */
        switch (toupper(argv[2][0])) {
        case 'A':
          law[0] = '1';
          break;
        case 'U':
          law[0] = '0';
          break;
        case 'L':
          law[0] = '2';
          break;
        default:
          HARAKIRI(" Invalid law (A or u)! Aborted...\n", 7);
        }

        /* Move argv over the option to the next argument */
        argv += 2;
        argc -= 2;
      } else if (strcmp(argv[1], "-frame") == 0) {
        /* Define Frame size for rate change during operation */
        N = atoi(argv[2]);

        /* Move argv over the option to the next argument */
        argv += 2;
        argc -= 2;
      } else if (strcmp(argv[1], "-rate") == 0) {
        /*Define rate(s) for operation */
        rateno = parse_rate(argv[2], &rate);
        if (rateno == 0) {
          fprintf(stderr, "Invalid bitrate list: %s\n", argv[2]);
          exit(9);
        }
        /* Move argv over the option to the next argument */
        argv += 2;
        argc -= 2;
      } else if (strcmp(argv[1], "-q") == 0) {
        /* Don't print progress indicator */
        quiet = 1;

        /* Move argv over the option to the next argument */
        argv++;
        argc--;
      } else if (strcmp(argv[1], "-?") == 0
        || strcmp(argv[1], "-help") == 0) {
        /* Print help */
        display_usage();
      } else {
        fprintf(stderr,
          "ERROR! Invalid option \"%s\" in command line\n\n", argv[1]);
        display_usage();
      }
  }

  /* Now get regular parameters */
  GET_PAR_S(1, "_Input File: .................. ", FileIn);
  GET_PAR_S(2, "_Output File: ................. ", FileOut);
  FIND_PAR_L(3, "_Block Size: .................. ", N, N);
  FIND_PAR_L(4, "_Starting Block: .............. ", N1, N1);
  FIND_PAR_L(5, "_No. of Blocks: ............... ", N2, N2);

  /* Uses default rate if none is given */
  if (rate == 0)
    rateno = parse_rate(def_rate, &rate);

  /* Inform user of the compading law used: 0->u, 1->A, 2->linear */
  fprintf(stderr, "Using %s\n",
    law[0] == '1' ? "A-law" : (law[0] == '0' ? "u-law" : "linear PCM"));

  /* Find starting byte in file */
  start_byte = sizeof(short) * (long) (--N1) * (long) N;

  /* Check if is to process the whole file */
  if (N2 == 0) {
    struct stat st;

    /* ... find the input file size ... */
    stat(FileIn, &st);
    N2 = ceil((st.st_size - start_byte) / (double) (N * sizeof(short)));
  }

  /* Define correct data I/O types */
  if (encode && decode) {
    inp_type = out_type = (law[0] == '2' ? IS_LIN : IS_LOG);
  } else if (encode) {
    inp_type = law[0] == '2' ? IS_LIN : IS_LOG;
    out_type = IS_ADPCM;
  } else {
    inp_type = IS_ADPCM;
    out_type = law[0] == '2' ? IS_LIN : IS_LOG;
  }

  /* Force law to be used *by the ADPCM* to A-law, if input is linear */
  if (law[0] == '2')
    law[0] = '1';

/*
 * ...... MEMORY ALLOCATION .........
 */

  if ((inp_buf = (short *) calloc(N, sizeof(short))) == NULL)
    HARAKIRI("Error in memory allocation!\n", 1);
  if ((out_buf = (short *) calloc(N, sizeof(short))) == NULL)
    HARAKIRI("Error in memory allocation!\n", 1);
  if ((tmp_buf = (short *) calloc(N, sizeof(short))) == NULL)
    HARAKIRI("Error in memory allocation!\n", 1);

/*
 * ......... FILE PREPARATION .........
 */

  /* Opening input file; abort if there's any problem */
  if ((Fi = fopen(FileIn, "rb")) == NULL)
    KILL(FileIn, 2);
  inp = fileno(Fi);

  /* Creates output file */
#ifdef VMS
  sprintf(mrs, "mrs=%d", 512);
#endif
  if ((Fo = fopen(FileOut, WB)) == NULL)
    KILL(FileOut, 3);
  out = fileno(Fo);

  /* Move pointer to 1st block of interest */
  if (fseek(Fi, start_byte, 0) < 0l)
    KILL(FileIn, 4);

/*
 * ......... PROCESSING ACCORDING TO ITU-T G.726 .........
 */
  /* Reset VBR counters */
  rate_idx = 0;

  for (cur_blk = 0; cur_blk < N2; cur_blk++) {
    /* Set the proper rate index */
    rate_idx = cur_blk % rateno;

    /* Print progress flag */
    if (!quiet)
#ifdef DISPLAY_CURRENT_RATE
      fprintf(stderr, "%d-", 8 * rate[rate_idx]);
#else
      fprintf(stderr, "%c\r", funny[cur_blk % 8]);
#endif

    /* Read a block of samples */
    if ((smpno = fread(inp_buf, sizeof(short), N, Fi)) < 0)
      KILL(FileIn, 5);

    /* Compress linear input samples */
    if (inp_type == IS_LIN) {
      /* Compress using A-law */
      alaw_compress(smpno, inp_buf, tmpb_buf);

      /* copy temporary buffer over input buffer */
      // memcpy(inp_buf, tmp_buf, sizeof(short) * smpno);
    }

    /* Check if reset is needed */
    reset = (reset == 1 && cur_blk == 0) ? 1 : 0;

    /* Carry out the desired operation */
    if (encode && !decode)
      G726_encode(tmpb_buf, out_buf, smpno, law,
        rate[rate_idx], reset, &encoder_state);
    else if (decode && !encode)
      G726_decode(inp_buf, outb_buf, smpno, law,
        rate[rate_idx], reset, &decoder_state);
    else if (encode && decode) {
      Byte g726_buf[N/2];
    
      G726_encode(tmpb_buf, tmp_buf, smpno, law,
        rate[rate_idx], reset, &encoder_state);
        // printf ("rate[rate_idx] = %d\n", rate[rate_idx]);
      g726pack(g726_buf, tmp_buf, smpno);
      
      g726unpack(tmp_buf, g726_buf, smpno);
        
      G726_decode(tmp_buf, outb_buf, smpno, law,
        rate[rate_idx], reset, &decoder_state);
    }

    /* Expand linear input samples */
    if (out_type == IS_LIN) {
      /* Compress using A-law */
      alaw_expand(smpno, outb_buf, tmp_buf);

      /* copy temporary buffer over input buffer */
      memcpy(out_buf, tmp_buf, sizeof(short) * smpno);
    }

    /* Write ADPCM output word */
    if ((smpno = fwrite(out_buf, sizeof(short), smpno, Fo)) < 0)
      KILL(FileOut, 6);
  }

/*
 * ......... FINALIZATIONS .........
 */

  /* Free allocated memory */
  free(tmp_buf);
  free(out_buf);
  free(inp_buf);
  free(rate);

  /* Close input and output files */
  fclose(Fi);
  fclose(Fo);

  /* Exit with success for non-vms systems */
#ifndef VMS
  return (0);
#endif
}

/* ............................. end of main() ............................. */
