/**
 * isaccodec.cpp
 * This file is part of the YATE Project http://YATE.null.ro
 *
 * iSAC codec using iSAC library based on WebRTC project.
 *
 * Yet Another Telephony Engine - a fully featured software PBX and IVR
 * Copyright (C) 2011-2014 Null Team
 *
 * This software is distributed under multiple licenses;
 * see the COPYING file in the main directory for licensing
 * information for this specific distribution.
 *
 * This use of this software may be subject to additional restrictions.
 * See the LEGAL file in the main directory for details.
 *
 * 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.
 *
 *
 * Copyright (c) 2011, The WebRTC project authors. All rights reserved.
 *
 * 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.
 *
 *  * Neither the name of Google 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 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
 * HOLDER 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.
 */

#include <yatephone.h>

extern "C" {
#include "signal_processing_library.h"
#ifdef ISAC_FIXED
#include "isacfix.h"
#else
#include "isac.h"
#endif
}

// ISAC frame size (in milliseconds) to set in encoder and format info
// 0: use default (don't set), 30/60ms otherwise
#define ISAC_FRAME_SIZE_MS 30

// Coding mode:
// 0: Channel-adaptive: the bit rate is adjusted by the encoder
// 1: Channel-independent: fixed bit rate
#define ISAC_CODING_ADAPTIVE 0
#define ISAC_CODING_INDEPENDENT 1

#ifndef ISAC_CODING_MODE
#define ISAC_CODING_MODE ISAC_CODING_INDEPENDENT
//#define ISAC_CODING_MODE ISAC_CODING_ADAPTIVE
#endif

// Channel independent: REQUIRED: set it to 32000 (default library value)
// Channel adaptive: set it to 0 to use default
#define ISAC_RATE 32000

using namespace TelEngine;
namespace { // anonymous

class iSACCodec : public DataTranslator
{
public:
    iSACCodec(const char* sFormat, const char* dFormat, bool encoding);
    ~iSACCodec();
    inline bool valid() const
	{ return m_isac != 0; }
    virtual unsigned long Consume(const DataBlock& data, unsigned long tStamp,
	unsigned long flags);
    void timerTick();
private:
    // Retrieve the ISAC error
    inline WebRtc_Word16 isacGetError() const
	{
#ifdef ISAC_FIXED
	    return WebRtcIsacfix_GetErrorCode(m_isac);
#else
	    return WebRtcIsac_GetErrorCode(m_isac);
#endif
	}
    // Check error after encode/decode
    // Forward data if result is greater then 0 and return the number of bytes forwarded
    // Update last error. Output a debug message if error changed
    unsigned long processCodecResult(WebRtc_Word16 result, unsigned int inBytes,
	unsigned long tStamp, unsigned long flags);
    // Initialize the codec structure. Return false on failure
    bool isacInit();
    // Release the ISAC structure
    void isacFree();

    bool m_encoding;                     // Encoder/decoder flag
#ifdef ISAC_FIXED
    ISACFIX_MainStruct* m_isac;          // ISAC library structure
#else
    ISACStruct* m_isac;
#endif
    WebRtc_Word16 m_error;               // Last error
    DataBlock m_outData;                 // Codec output
    WebRtc_Word16 m_mode;                // Encoder mode (chan adaptive/instantaneous)
    unsigned int m_encodeChunk;          // Encoder input data length in bytes
    unsigned long m_tStamp;              // Encoder timestamp
    DataBlock m_buffer;                  // Encoder buffer for incomplete data
    // Statistics
    unsigned long m_inPackets;
    unsigned long m_outPackets;
    unsigned long m_inBytes;
    unsigned long m_outBytes;
    unsigned long m_failedBytes;
};

class iSACFactory : public TranslatorFactory
{
public:
    iSACFactory();
    virtual const TranslatorCaps* getCapabilities() const
	{ return m_caps; }
    virtual DataTranslator* create(const DataFormat& sFormat, const DataFormat& dFormat);
private:
    const TranslatorCaps* m_caps;
};

class iSACModule : public Module
{
public:
    iSACModule();
    ~iSACModule();
    inline void incCount() {
	    Lock mylock(this);
	    m_count++;
	}
    inline void decCount() {
	    Lock mylock(this);
	    m_count--;
	}
    virtual void initialize();
    virtual bool isBusy() const
	{ return (m_count != 0); }
protected:
    virtual void statusParams(String& str);
private:
    int m_count;                         // Current number of codecs
    iSACFactory* m_factory;              // Factory used to create codecs
};


INIT_PLUGIN(iSACModule);

static TranslatorCaps s_caps[] = {
    { 0, 0 },
    { 0, 0 },
    { 0, 0 },
    { 0, 0 },
    { 0, 0 }
};

UNLOAD_PLUGIN(unloadNow)
{
    if (unloadNow)
	return !__plugin.isBusy();
    return true;
}


/*
 * iSACFactory
 */
iSACCodec::iSACCodec(const char* sFormat, const char* dFormat, bool encoding)
    : DataTranslator(sFormat,dFormat), m_encoding(encoding),
    m_isac(0), m_error(0), m_mode(ISAC_CODING_MODE), m_encodeChunk(0), m_tStamp(0),
    m_inPackets(0), m_outPackets(0), m_inBytes(0), m_outBytes(0), m_failedBytes(0)
{
    Debug(&__plugin,DebugAll,"iSACCodec(\"%s\",\"%s\",%scoding) [%p]",
	sFormat,dFormat,m_encoding ? "en" : "de",this);
    __plugin.incCount();
    isacInit();
}

iSACCodec::~iSACCodec()
{
    isacFree();
    Debug(&__plugin,DebugAll,
	"iSACCodec(%scoding) destroyed packets in/out=%lu/%lu bytes in/out/failed=%lu/%lu/%lu [%p]",
	m_encoding ? "en" : "de",m_inPackets,m_outPackets,m_inBytes,m_outBytes,m_failedBytes,this);
    __plugin.decCount();
}

unsigned long iSACCodec::Consume(const DataBlock& data, unsigned long tStamp,
    unsigned long flags)
{
    XDebug(&__plugin,DebugAll,"%scoder::Consume(%u,%lu,%lu) buffer=%u [%p]",
	m_encoding ? "En" : "De",data.length(),tStamp,flags,m_buffer.length(),this);
    m_inBytes += data.length();
    m_inPackets++;
    if (!(valid() && getTransSource())) {
	m_failedBytes += data.length();
	return 0;
    }
    if (data.null() && (flags & DataSilent))
	return getTransSource()->Forward(data,tStamp,flags);
    ref();
    WebRtc_Word16 res = 0;
    WebRtc_Word16* out = (WebRtc_Word16*)m_outData.data();
    unsigned long len = 0;
    if (m_encoding) {
	// NOTE: draft-ietf-avt-rtp-isac-00.txt section 3.4:
	// More than one iSAC payload block MUST NOT be included in an RTP packet by a sender
	// Forward data when encoded, don't accumulate the encoder output

	if (tStamp == invalidStamp())
	    tStamp = 0;
	tStamp -= m_tStamp;
	// Avoid copying data if our buffer is empty
	const DataBlock* inDataBlock = &data;
	if (m_buffer.length()) {
	    tStamp -= (m_buffer.length() / 2);
	    m_buffer += data;
	    inDataBlock = &m_buffer;
	}
	const unsigned char* ptr = (const unsigned char*)inDataBlock->data();
	unsigned int remaining = inDataBlock->length();
	unsigned int tsChunk = m_encodeChunk / 2;
	while (remaining >= m_encodeChunk) {
	    // Encode returns the number of bytes set in output buffer
#ifdef ISAC_FIXED
	    res = WebRtcIsacfix_Encode(m_isac,(const WebRtc_Word16*)ptr,out);
#else
	    res = WebRtcIsac_Encode(m_isac,(const WebRtc_Word16*)ptr,out);
#endif
	    remaining -= m_encodeChunk;
	    ptr += m_encodeChunk;
	    m_tStamp += tsChunk;
	    unsigned long l = processCodecResult(res,m_encodeChunk,tStamp,flags);
	    if (res > 0) {
		tStamp += m_tStamp;
		m_tStamp = 0;
	    }
	    if (!len)
		len = l;
	    else if (len != invalidStamp() && l != invalidStamp())
		len += l;
	}
	if (!remaining)
	    m_buffer.clear();
	else
	    m_buffer.assign((void*)ptr,remaining);
    }
    else {
#ifndef NO_ISAC_PLC
	if (flags & DataMissed) {
	    // guess how many frames were lost
	    int lost = (tStamp - timeStamp()) / 480;
	    if (lost <= 0)
		lost = 1;
	    else if (lost > 2)
		lost = 2;
#ifdef ISAC_FIXED
	    res = WebRtcIsacfix_DecodePlc(m_isac,out,lost);
#else
	    res = WebRtcIsac_DecodePlc(m_isac,out,lost);
#endif
	    DDebug(&__plugin,DebugNote,"Loss Concealment %d samples [%p]",res,this);
	    if (res > 0) {
		flags &= ~DataMissed;
		unsigned long ts = tStamp;
		if (data.length())
		    ts -= res;
		processCodecResult(res*2,0,ts,flags);
	    }
	}
#endif
	if (data.length()) {
	    // NOTE: We must workaround the following issues in WebRtcIsacfix_Decode:
	    // - It doesn't honor the 'const' qualifier of the input buffer on
	    //   little endian machines, it changes it!
	    //   Copy data to out buffer to avoid altering source data for another data consumer
	    // - It makes read/write access past buffer end for odd buffer length
	    const DataBlock* inDataBlock = &data;
	    if (0 != (data.length() & 0x01)) {
		m_buffer.assign(0,data.length() + 1);
		::memcpy(m_buffer.data(),data.data(),data.length());
		inDataBlock = &m_buffer;
	    }
#ifndef BIGENDIAN
	    else {
		m_buffer = data;
		inDataBlock = &m_buffer;
	    }
#endif
	    WebRtc_Word16 speechType = 0;
#ifdef ISAC_FIXED
	    res = WebRtcIsacfix_Decode(m_isac,(const WebRtc_UWord16*)
		    inDataBlock->data(),data.length(),out,&speechType);
#else
	    res = WebRtcIsac_Decode(m_isac,(const WebRtc_UWord16*)
		    inDataBlock->data(),data.length(),out,&speechType);
#endif
	    // Decode returns the number of decoded samples
	    if (res > 0)
		res *= 2;
	    len = processCodecResult(res,data.length(),tStamp,flags);
	}
    }
    deref();
    return len;
}

// Check error after encode/decode
// Forward data if result is greater then 0 and return the number of bytes forwarded
// Update last error. Output a debug message if error changed
unsigned long iSACCodec::processCodecResult(WebRtc_Word16 result, unsigned int inBytes,
    unsigned long tStamp, unsigned long flags)
{
    XDebug(&__plugin,DebugAll,"%scoded %u --> %d tStamp=%lu [%p]",
	m_encoding ? "En" : "De",inBytes,result,tStamp,this);
    if (result >= 0) {
	m_error = 0;
	if (!result)
	    return 0;
	m_outPackets++;
	m_outBytes += (unsigned long)result;
	DataBlock tmp(m_outData.data(),result,false);
	DDebug(&__plugin,DebugAll,"%scoder forwarding %u tStamp=%lu [%p]",
	    m_encoding ? "En" : "De",tmp.length(),tStamp,this);
	unsigned long len = getTransSource()->Forward(tmp,tStamp,flags);
	tmp.clear(false);
	return len;
    }
    m_failedBytes += inBytes;
    WebRtc_Word16 err = isacGetError();
    if (m_error != err) {
	m_error = err;
	Debug(&__plugin,DebugNote,"%scoder failed %u bytes error=%d [%p]",
	    m_encoding ? "En" : "De",inBytes,m_error,this);
    }
    return 0;
}

// Initialize the ISAC structure. Return false on failure
bool iSACCodec::isacInit()
{
    if (m_isac)
	return true;
    // Create the isac structure
    WebRtc_Word16 res = -1;
    int sampleRate = getFormat().sampleRate();
#ifdef ISAC_FIXED
    res = WebRtcIsacfix_Create(&m_isac);
#else
    res = WebRtcIsac_Create(&m_isac);
#endif
    if (res) {
	Debug(&__plugin,DebugWarn,"iSACCodec failed to allocate ISAC data [%p]",this);
	m_isac = 0;
	return false;
    }
    // Init the codec
    if (m_encoding) {
#ifdef ISAC_FIXED
	res = WebRtcIsacfix_EncoderInit(m_isac,m_mode);
#else
	res = WebRtcIsac_EncoderInit(m_isac,m_mode);
	WebRtcIsac_SetEncSampRate(m_isac,sampleRate == 16000 ? kIsacWideband : kIsacSuperWideband);
#endif

	if (sampleRate == 16000) {
	    m_outData.assign(0,400);
	    m_encodeChunk = 320;
	}
#ifndef ISAC_FIXED
	else if (sampleRate == 32000) {
	    m_outData.assign(0,800);
	    m_encodeChunk = 640;
	}
#endif
	else {
	    Debug(&__plugin,DebugWarn,"Bad iSAC sample Rate %d",sampleRate);
	    return false;
	}
    }
    else {
#ifdef ISAC_FIXED
	res = WebRtcIsacfix_DecoderInit(m_isac);
#else
	res = WebRtcIsac_DecoderInit(m_isac);
	WebRtcIsac_SetDecSampRate(m_isac,sampleRate == 16000 ? kIsacWideband : kIsacSuperWideband);
#endif
	// Decode may return 480 or 960 samples
	m_outData.assign(0,1920);
    }
    if (res == 0) {
	if (m_encoding) {
	    // Set frame size if instructed
	    WebRtc_Word16 fs = ISAC_FRAME_SIZE_MS;
	    if (fs) {
		WebRtc_Word16 err = 0;
		// Isac fixed point implementation rate:
		// Channel adaptive: 0 to use default
		// Channel independent use default value (32000)
		WebRtc_Word16 rateBps = sampleRate;
		if (m_mode == ISAC_CODING_INDEPENDENT) {
#ifdef ISAC_FIXED
		    err = WebRtcIsacfix_Control(m_isac,rateBps,fs);
#else
		    err = WebRtcIsac_Control(m_isac,rateBps,fs);
#endif
		}
		else {
		    // Enforce frame size: 1: fix, 0: let the codec change it
		    WebRtc_Word16 efs = 1;
#ifdef ISAC_FIXED
		    err = WebRtcIsacfix_ControlBwe(m_isac,rateBps,fs,efs);
#else
		    err = WebRtcIsac_ControlBwe(m_isac,rateBps,fs,efs);
#endif
		}
		if (err == 0)
		    XDebug(&__plugin,DebugAll,"Encoder set framesize=%dms [%p]",fs,this);
		else
		    Debug(&__plugin,DebugNote,
		        "Encoder failed to set framesize=%dms error=%d [%p]",
			fs,isacGetError(),this);
	    }
	}
	DDebug(&__plugin,DebugAll,"iSACCodec initialized [%p]",this);
	return true;
    }
    m_error = isacGetError();
    Debug(&__plugin,DebugWarn,"iSACCodec failed to initialize error=%d [%p]",
	m_error,this);
    isacFree();
    return false;
}

// Release the ISAC structure
void iSACCodec::isacFree()
{
    if (!m_isac)
	return;
    XDebug(&__plugin,DebugAll,"iSACCodec releasing ISAC [%p]",this);
#ifdef ISAC_FIXED
    WebRtcIsacfix_Free(m_isac);
#else
    WebRtcIsac_Free(m_isac);
#endif
    m_isac = 0;
}


/*
 * iSACFactory
 */
iSACFactory::iSACFactory()
    : TranslatorFactory("isac"),
    m_caps(s_caps)
{
}

DataTranslator* iSACFactory::create(const DataFormat& sFormat, const DataFormat& dFormat)
{
    iSACCodec* codec = 0;
    if (sFormat == YSTRING("slin/16000")) {
	if (dFormat == YSTRING("isac/16000"))
	    codec = new iSACCodec(sFormat,dFormat,true);
    }
    else if (dFormat == YSTRING("slin/16000")) {
	if (sFormat == YSTRING("isac/16000"))
	    codec = new iSACCodec(sFormat,dFormat,false);
    }
#ifndef ISAC_FIXED
    if (sFormat == YSTRING("slin/32000")) {
	if (dFormat == YSTRING("isac/32000"))
	    codec = new iSACCodec(sFormat,dFormat,true);
    }
    else if (dFormat == YSTRING("slin/32000")) {
	if (sFormat == YSTRING("isac/32000"))
	    codec = new iSACCodec(sFormat,dFormat,false);
    }
#endif
    if (codec && !codec->valid())
	TelEngine::destruct(codec);
    return codec;
}


/*
 * iSACModule
 */
iSACModule::iSACModule()
    : Module("isaccodec","misc"),
    m_count(0), m_factory(0)
{
    char ver[65] = {0};
    char splVer[65] = {0};
    const char* type = 0;
    WebRtcSpl_get_version(splVer,64);
#ifdef ISAC_FIXED
    WebRtcIsacfix_version(ver);
    type = "fixed point";
#else
    WebRtcIsac_version(ver);
    type = "floating point";
#endif
    Output("Loaded module iSAC %s - based on WebRTC iSAC library version %s (SPL version %s)",
	type,ver,splVer);
    const FormatInfo* f = FormatRepository::addFormat("isac/16000",0,
	ISAC_FRAME_SIZE_MS * 1000,"audio",16000);
    s_caps[0].src = s_caps[1].dest = f;
    s_caps[0].dest = s_caps[1].src = FormatRepository::getFormat("slin/16000");
    // FIXME: put proper conversion costs
    s_caps[0].cost = s_caps[1].cost = 10;
#ifndef ISAC_FIXED
    const FormatInfo* f32 = FormatRepository::addFormat("isac/32000",0,
	ISAC_FRAME_SIZE_MS * 1000,"audio",32000);
    s_caps[2].src = s_caps[3].dest = f32;
    s_caps[2].dest = s_caps[3].src = FormatRepository::getFormat("slin/32000");
    // FIXME: put proper conversion costs
    s_caps[2].cost = s_caps[3].cost = 10;
#endif
    m_factory = new iSACFactory;
}

iSACModule::~iSACModule()
{
    Output("Unloading module iSAC with %d codecs still in use",m_count);
    TelEngine::destruct(m_factory);
}

void iSACModule::initialize()
{
    static bool s_first = true;
    Output("Initializing module iSAC");
    if (s_first) {
	installRelay(Level);
	installRelay(Status);
	installRelay(Command);
	s_first = false;
    }
}

void iSACModule::statusParams(String& str)
{
    str << "codecs=" << m_count;
}

}; // anonymous namespace

/* vi: set ts=8 sw=4 sts=4 noet: */
