/*
 * Asterisk -- An open source telephony toolkit.
 *
 * Copyright (C) 2006 Steve Underwood <steveu@coppice.org>
 *
 * Based on the g726 code for Asterisk by Mark Spencer <markster@digium.com>
 *
 * This program is free software, distributed under the terms of
 * the GNU General Public License Version 2. See the LICENSE file
 * at the top of the source tree.
 */


/*! \file
 *
 * \brief codec_g726.c - translate between signed linear and ITU G.726-32kbps
 *
 * \ingroup codecs
 */

#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <spandsp.h>

#include "asterisk.h"

ASTERISK_FILE_VERSION(__FILE__, "$Revision: 7221 $")

#include "asterisk/lock.h"
#include "asterisk/logger.h"
#include "asterisk/module.h"
#include "asterisk/config.h"
#include "asterisk/options.h"
#include "asterisk/translate.h"
#include "asterisk/channel.h"

#define BUFFER_SIZE   8096	/* Size for the translation buffers */

AST_MUTEX_DEFINE_STATIC(localuser_lock);
static int localusecnt = 0;

static const char *tdesc = "ITU G.726-32kbps G.726 transcoder";

static int useplc = 0;

/* 10ms of linear silence, at 8k samples/second */
static const int16_t slin_g726_ex[] =
{
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
};

/* 20ms of G.726 at 32kbps */
static uint8_t g726_slin_ex[] =
{
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

/*
 * Private workspace for translating signed linear signals to G726.
 */

struct g726_encoder_pvt
{
    struct ast_frame f;
    uint8_t offset[AST_FRIENDLY_OFFSET];    /* Space to build offset */
    uint8_t outbuf[BUFFER_SIZE];            /* Encoded G726, two nibbles to a word */
    uint8_t next_flag;
    g726_state_t g726_state;
    int tail;
};

/*
 * Private workspace for translating G726 signals to signed linear.
 */

struct g726_decoder_pvt
{
    struct ast_frame f;
    uint8_t offset[AST_FRIENDLY_OFFSET];	/* Space to build offset */
    int16_t outbuf[BUFFER_SIZE];	        /* Decoded signed linear values */
    g726_state_t g726_state;
    int tail;
    plc_state_t plc;
};

/*
 * G726ToLin_New
 *  Create a new instance of g726_decoder_pvt.
 *
 * Results:
 *  Returns a pointer to the new instance.
 *
 * Side effects:
 *  None.
 */

static struct ast_translator_pvt *g726tolin_new(void)
{
    struct g726_decoder_pvt *tmp;
 
    if ((tmp = malloc(sizeof(struct g726_decoder_pvt))))
    {
	    memset(tmp, 0, sizeof(*tmp));
        localusecnt++;
        tmp->tail = 0;
        plc_init(&tmp->plc);
        g726_init(&(tmp->g726_state), 32000, G726_ENCODING_LINEAR, TRUE);
        ast_update_use_count ();
    }
    return (struct ast_translator_pvt *) tmp;
}

/*
 * LinToG726_New
 *  Create a new instance of g726_encoder_pvt.
 *
 * Results:
 *  Returns a pointer to the new instance.
 *
 * Side effects:
 *  None.
 */

static struct ast_translator_pvt *lintog726_new(void)
{
    struct g726_encoder_pvt *tmp;
  
    if ((tmp = malloc(sizeof(struct g726_encoder_pvt))))
    {
	    memset(tmp, 0, sizeof(*tmp));
        localusecnt++;
        tmp->tail = 0;
        g726_init(&(tmp->g726_state), 32000, G726_ENCODING_LINEAR, TRUE);
        ast_update_use_count ();
    }
    return (struct ast_translator_pvt *) tmp;
}

/*
 * G726ToLin_FrameIn
 *  Fill an input buffer with packed 4-bit G726 values if there is room
 *  left.
 *
 * Results:
 *  Foo
 *
 * Side effects:
 *  tmp->tail is the number of packed values in the buffer.
 */

static int g726tolin_framein(struct ast_translator_pvt *pvt, struct ast_frame *f)
{
    struct g726_decoder_pvt *tmp = (struct g726_decoder_pvt *) pvt;
    int new_samples;
    
    if (f->datalen == 0)
    {
        /* Perform PLC with nominal framesize of 20ms/160 samples */
        if ((tmp->tail + 160) > BUFFER_SIZE)
        {
            ast_log(LOG_WARNING, "Out of buffer space\n");
            return -1;
        }
        if (useplc)
        {
	        plc_fillin(&tmp->plc, tmp->outbuf + tmp->tail, 160);
	        tmp->tail += 160;
	    }
    }
    else
    {
        if ((tmp->tail + f->datalen*2) > BUFFER_SIZE)
        {
            ast_log(LOG_WARNING, "Out of buffer space\n");
            return -1;
        }
        new_samples = g726_decode(&(tmp->g726_state),
                                  tmp->outbuf + tmp->tail,
                                  (const uint8_t *) f->data,
                                  f->datalen);
        if (useplc)
            plc_rx(&tmp->plc, tmp->outbuf + tmp->tail, new_samples);
        tmp->tail += new_samples;
    }
    return 0;
}

/*
 * G726ToLin_FrameOut
 *  Convert 4-bit G726 encoded signals to 16-bit signed linear.
 *
 * Results:
 *  Converted signals are placed in tmp->f.data, tmp->f.datalen
 *  and tmp->f.samples are calculated.
 *
 * Side effects:
 *  None.
 */

static struct ast_frame *g726tolin_frameout(struct ast_translator_pvt *pvt)
{
    struct g726_decoder_pvt *tmp = (struct g726_decoder_pvt *) pvt;

    if (tmp->tail == 0)
        return NULL;

    tmp->f.frametype = AST_FRAME_VOICE;
    tmp->f.subclass = AST_FORMAT_SLINEAR;
    tmp->f.datalen = tmp->tail*sizeof(int16_t);
    tmp->f.samples = tmp->tail;
    tmp->f.mallocd = 0;
    tmp->f.offset = AST_FRIENDLY_OFFSET;
    tmp->f.src = __PRETTY_FUNCTION__;
    tmp->f.data = tmp->outbuf;

    tmp->tail = 0;
    return &tmp->f;
}

/*
 * LinToG726_FrameIn
 *  Fill an input buffer with 16-bit signed linear PCM values.
 *
 * Results:
 *  None.
 *
 * Side effects:
 *  tmp->tail is number of signal values in the input buffer.
 */

static int lintog726_framein(struct ast_translator_pvt *pvt, struct ast_frame *f)
{
    struct g726_encoder_pvt *tmp = (struct g726_encoder_pvt *) pvt;
  
    if ((tmp->tail + f->datalen/(2*sizeof(int16_t)) + 1) > BUFFER_SIZE)
    {
        ast_log(LOG_WARNING, "Out of buffer space\n");
        return -1;
    }
    tmp->tail += g726_encode(&(tmp->g726_state),
                             tmp->outbuf + tmp->tail,
                             f->data,
                             f->datalen/sizeof(int16_t));
    return 0;
}

/*
 * LinToG726_FrameOut
 *  Convert a buffer of raw 16-bit signed linear PCM to a buffer
 *  of 4-bit G726 packed two to a byte (Big Endian).
 *
 * Results:
 *  Foo
 *
 * Side effects:
 *  Leftover inbuf data gets packed, tail gets updated.
 */

static struct ast_frame *lintog726_frameout(struct ast_translator_pvt *pvt)
{
    struct g726_encoder_pvt *tmp = (struct g726_encoder_pvt *) pvt;
  
    if (tmp->tail == 0)
  	    return NULL;

    tmp->f.frametype = AST_FRAME_VOICE;
    tmp->f.subclass = AST_FORMAT_G726;
    tmp->f.samples = tmp->tail*sizeof(int16_t);
    tmp->f.mallocd = 0;
    tmp->f.offset = AST_FRIENDLY_OFFSET;
    tmp->f.src = __PRETTY_FUNCTION__;
    tmp->f.data = tmp->outbuf;
    tmp->f.datalen = tmp->tail;

    tmp->tail = 0;
    return &tmp->f;
}

/*
 * G726ToLin_Sample
 */

static struct ast_frame *g726tolin_sample(void)
{
    static struct ast_frame f;
 
    f.frametype = AST_FRAME_VOICE;
    f.subclass = AST_FORMAT_G726;
    f.datalen = sizeof(g726_slin_ex);
    f.samples = sizeof(g726_slin_ex)*sizeof(int16_t);
    f.mallocd = 0;
    f.offset = 0;
    f.src = __PRETTY_FUNCTION__;
    f.data = g726_slin_ex;
    return &f;
}

/*
 * LinToG726_Sample
 */

static struct ast_frame *lintog726_sample(void)
{
    static struct ast_frame f;

    f.frametype = AST_FRAME_VOICE;
    f.subclass = AST_FORMAT_SLINEAR;
    f.datalen = sizeof(slin_g726_ex);
    /* Assume 8000 samples/second */
    f.samples = sizeof(slin_g726_ex)/2;
    f.mallocd = 0;
    f.offset = 0;
    f.src = __PRETTY_FUNCTION__;
    f.data = (int16_t *) slin_g726_ex;
    return &f;
}

/*
 * G726_Destroy
 *  Destroys a private workspace.
 *
 * Results:
 *  It's gone!
 *
 * Side effects:
 *  None.
 */

static void g726_destroy(struct ast_translator_pvt *pvt)
{
    free(pvt);
    localusecnt--;
    ast_update_use_count();
}

/*
 * The complete translator for G726ToLin.
 */

static struct ast_translator g726tolin =
{
    "g726tolin",
    AST_FORMAT_G726,
    AST_FORMAT_SLINEAR,
    g726tolin_new,
    g726tolin_framein,
    g726tolin_frameout,
    g726_destroy,
    /* NULL */
    g726tolin_sample
};

/*
 * The complete translator for LinToG726.
 */

static struct ast_translator lintog726 =
{
    "lintog726",
    AST_FORMAT_SLINEAR,
    AST_FORMAT_G726,
    lintog726_new,
    lintog726_framein,
    lintog726_frameout,
    g726_destroy,
    /* NULL */
    lintog726_sample
};

static void parse_config(void)
{
    struct ast_config *cfg;
    struct ast_variable *var;
  
    if ((cfg = ast_config_load("codecs.conf")))
    {
        if ((var = ast_variable_browse(cfg, "plc")))
        {
            while (var)
            {
                if (!strcasecmp(var->name, "genericplc"))
                {
                    useplc = ast_true(var->value)  ?  1  :  0;
                    if (option_verbose > 2)
                        ast_verbose(VERBOSE_PREFIX_3 "codec_g726: %susing generic PLC\n", useplc  ?  ""  :  "not ");
                }
                var = var->next;
            }
        }
        ast_config_destroy(cfg);
    }
}

int reload(void)
{
    parse_config();
    return 0;
}

int unload_module(void)
{
    int res;

    ast_mutex_lock(&localuser_lock);
    if ((res = ast_unregister_translator(&lintog726)) == 0)
        res = ast_unregister_translator(&g726tolin);
    if (localusecnt)
        res = -1;
    ast_mutex_unlock(&localuser_lock);
    return res;
}

int load_module(void)
{
    int res;

    parse_config();
    if ((res = ast_register_translator(&g726tolin)) == 0)
        res = ast_register_translator(&lintog726);
    else
        ast_unregister_translator(&g726tolin);
    return res;
}

/*
 * Return a description of this module.
 */
char *description(void)
{
    return (char *) tdesc;
}

int usecount(void)
{
    int res;

    STANDARD_USECOUNT(res);
    return res;
}

char *key(void)
{
    return ASTERISK_GPL_KEY;
}
