945e1441e3
git-svn-id: http://yate.null.ro/svn/yate/trunk@5184 acf43c95-373e-0410-b603-e72c3f656dc1
576 lines
17 KiB
C++
576 lines
17 KiB
C++
/**
|
|
* 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 Null Team
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*
|
|
* 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: */
|