- initial import

This commit is contained in:
2018-06-05 11:05:37 +03:00
commit e1a4931375
4673 changed files with 1383093 additions and 0 deletions

View File

@@ -0,0 +1,958 @@
#include "MT_AmrCodec.h"
#include "../helper/HL_ByteBuffer.h"
#include "../helper/HL_Log.h"
#include "../helper/HL_IuUP.h"
#define LOG_SUBSYSTEM "AmrCodec"
#ifdef USE_AMR_CODEC
using namespace MT;
static const uint8_t amr_block_size[16]={ 13, 14, 16, 18, 20, 21, 27, 32,
6 , 0 , 0 , 0 , 0 , 0 , 0 , 1 };
/**
* Constant of AMR-NB frame lengths in bytes.
*/
const uint8_t amrnb_framelen[16] =
{12, 13, 15, 17, 19, 20, 26, 31, 5, 0, 0, 0, 0, 0, 0, 0};
/**
* Constant of AMR-NB frame lengths in bits.
*/
const uint16_t amrnb_framelenbits[9] =
{95, 103, 118, 134, 148, 159, 204, 244, 39};
/**
* Constant of AMR-NB bitrates.
*/
const uint16_t amrnb_bitrates[8] =
{4750, 5150, 5900, 6700, 7400, 7950, 10200, 12200};
/**
* Constant of AMR-WB frame lengths in bytes.
*/
const uint8_t amrwb_framelen[16] =
{17, 23, 32, 37, 40, 46, 50, 58, 60, 5, 0, 0, 0, 0, 0, 0};
/**
* Constant of AMR-WB frame lengths in bits.
*/
const uint16_t amrwb_framelenbits[10] =
{132, 177, 253, 285, 317, 365, 397, 461, 477, 40};
/**
* Constant of AMR-WB bitrates.
*/
const uint16_t amrwb_bitrates[9] =
{6600, 8850, 12650, 14250, 15850, 18250, 19850, 23050, 23850};
// Helper routines
/*static int8_t bitrateToMode(uint16_t bitrate)
{
int8_t mode = -1;
switch (bitrate)
{
// AMR NB
case 4750: mode = 0; break;
case 5150: mode = 1; break;
case 5900: mode = 2; break;
case 6700: mode = 3; break;
case 7400: mode = 4; break;
case 7950: mode = 5; break;
case 10200: mode = 6; break;
case 12200: mode = 7; break;
// AMRWB
case 6600: mode = 0; break;
case 8850: mode = 1; break;
case 12650: mode = 2; break;
case 14250: mode = 3; break;
case 15850: mode = 4; break;
case 18250: mode = 5; break;
case 19850: mode = 6; break;
case 23050: mode = 7; break;
case 23850: mode = 8; break;
}
return mode;
}*/
struct AmrPayloadInfo
{
const uint8_t* mPayload;
int mPayloadLength;
bool mOctetAligned;
bool mInterleaving;
bool mWideband;
uint64_t mCurrentTimestamp;
};
struct AmrFrame
{
uint8_t mFrameType;
uint8_t mMode;
bool mGoodQuality;
uint64_t mTimestamp;
std::shared_ptr<ByteBuffer> mData;
uint8_t mSTI;
};
struct AmrPayload
{
uint8_t mCodeModeRequest;
std::vector<AmrFrame> mFrames;
bool mDiscardPacket;
};
// ARM RTP payload has next structure
// Header
// Table of Contents
// Frames
static AmrPayload parseAmrPayload(AmrPayloadInfo& input)
{
AmrPayload result;
result.mDiscardPacket = false;
// Wrap incoming data with ByteArray to make bit dequeuing easy
ByteBuffer dataIn(input.mPayload, input.mPayloadLength);
BitReader br(input.mPayload, input.mPayloadLength);
result.mCodeModeRequest = br.readBits(4);
//ICELogMedia(<< "CMR: " << result.mCodeModeRequest);
// Consume extra 4 bits for octet aligned profile
if (input.mOctetAligned)
br.readBits(4);
// Skip interleaving flags for now for octet aligned mode
if (input.mInterleaving && input.mOctetAligned)
br.readBits(8);
uint8_t SID_FT = input.mWideband ? 9 : 8;
// Table of contents
uint8_t F, FT, Q;
do
{
F = br.readBit();
FT = br.readBits(4);
Q = br.readBit();
if (FT > SID_FT && FT < 14)
{
ICELogMedia(<< "Discard corrupted packet");
// Discard bad packet
result.mDiscardPacket = true;
return result;
}
// Handle padding for octet alignment
if (input.mOctetAligned)
br.readBits(2);
AmrFrame frame;
frame.mFrameType = FT;
assert (frame.mFrameType < 10);
frame.mMode = FT < SID_FT ? FT : -1;
frame.mGoodQuality = Q == 1;
frame.mTimestamp = input.mCurrentTimestamp;
result.mFrames.push_back(frame);
input.mCurrentTimestamp += input.mWideband ? 320 : 160;
}
while (F != 0);
for (int frameIndex=0; frameIndex < (int)result.mFrames.size(); frameIndex++)
{
AmrFrame& frame = result.mFrames[frameIndex];
int bitsLength = input.mWideband ? amrwb_framelenbits[frame.mFrameType] : amrnb_framelenbits[frame.mFrameType];
int byteLength = input.mWideband ? amrwb_framelen[frame.mFrameType] : amrnb_framelen[frame.mFrameType];
ICELogMedia(<< "New AMR speech frame: frame type = " << FT << ", mode = " << frame.mMode <<
", good quality = " << frame.mGoodQuality << ", timestamp = " << (int)frame.mTimestamp <<
", bits length = " << bitsLength << ", byte length =" << byteLength <<
", remaining packet length = " << (int)dataIn.size() );
if (bitsLength > 0)
{
if (input.mOctetAligned)
{
if ((int)dataIn.size() < byteLength)
{
frame.mGoodQuality = false;
}
else
{
// It is octet aligned scheme, so we are on byte boundary now
int byteOffset = br.count() / 8;
// Copy data of AMR frame
frame.mData = std::make_shared<ByteBuffer>(input.mPayload + byteOffset, byteLength);
}
}
else
{
// Allocate place for copying
frame.mData = std::make_shared<ByteBuffer>();
frame.mData->resize(bitsLength / 8 + ((bitsLength % 8) ? 1 : 0) + 1);
// Add header for decoder
frame.mData->mutableData()[0] = (frame.mFrameType << 3) | (1 << 2);
// Read bits
if (br.readBits(frame.mData->mutableData() + 1, bitsLength /*+ bitsLength*/ ) < (size_t)bitsLength)
frame.mGoodQuality = false;
}
}
}
// Padding bits are skipped
if (br.count() / 8 != br.position() / 8 &&
br.count() / 8 != br.position() / 8 + 1)
throw std::runtime_error("Failed to parse AMR frame");
}
/*static void predecodeAmrFrame(AmrFrame& frame, ByteBuffer& data)
{
// Data are already moved into
}*/
AmrNbCodec::CodecFactory::CodecFactory(const AmrCodecConfig& config)
:mConfig(config)
{
}
const char* AmrNbCodec::CodecFactory::name()
{
return MT_AMRNB_CODECNAME;
}
int AmrNbCodec::CodecFactory::samplerate()
{
return 8000;
}
int AmrNbCodec::CodecFactory::payloadType()
{
return mConfig.mPayloadType;
}
#ifdef USE_RESIP_INTEGRATION
void AmrNbCodec::CodecFactory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
{
}
int AmrNbCodec::CodecFactory::processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
{
return 0;
}
void AmrNbCodec::CodecFactory::create(CodecMap& codecs)
{
codecs[payloadType()] = std::shared_ptr<Codec>(new AmrNbCodec(mConfig));
}
#endif
PCodec AmrNbCodec::CodecFactory::create()
{
return PCodec(new AmrNbCodec(mConfig));
}
AmrNbCodec::AmrNbCodec(const AmrCodecConfig& config)
:mEncoderCtx(nullptr), mDecoderCtx(nullptr), mConfig(config), mCurrentDecoderTimestamp(0),
mSwitchCounter(0), mPreviousPacketLength(0)
{
mEncoderCtx = Encoder_Interface_init(1);
mDecoderCtx = Decoder_Interface_init();
}
AmrNbCodec::~AmrNbCodec()
{
if (mEncoderCtx)
{
Encoder_Interface_exit(mEncoderCtx);
mEncoderCtx = nullptr;
}
if (mDecoderCtx)
{
Decoder_Interface_exit(mDecoderCtx);
mDecoderCtx = nullptr;
}
}
const char* AmrNbCodec::name()
{
return MT_AMRNB_CODECNAME;
}
int AmrNbCodec::pcmLength()
{
return 20 * 16;
}
int AmrNbCodec::rtpLength()
{
return 0;
}
int AmrNbCodec::frameTime()
{
return 20;
}
int AmrNbCodec::samplerate()
{
return 8000;
}
int AmrNbCodec::encode(const void* input, int inputBytes, void* output, int outputCapacity)
{
if (inputBytes % pcmLength())
return 0;
// Declare the data input pointer
const short *dataIn = (const short *)input;
// Declare the data output pointer
unsigned char *dataOut = (unsigned char *)output;
// Find how much RTP frames will be generated
unsigned int frames = inputBytes / pcmLength();
// Generate frames
for (unsigned int i = 0; i < frames; i++)
{
dataOut += Encoder_Interface_Encode(mEncoderCtx, Mode::MRDTX, dataIn, dataOut, 1);
dataIn += pcmLength() / 2;
}
return frames * rtpLength();
}
#define L_FRAME 160
#define AMR_BITRATE_DTX 15
int AmrNbCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
{
if (mConfig.mIuUP)
{
// Try to parse IuUP frame
IuUP::Frame frame;
if (!IuUP::parse2((const uint8_t*)input, inputBytes, frame))
return 0;
// Check if CRC failed - it is check from IuUP data
if (!frame.mHeaderCrcOk || !frame.mPayloadCrcOk)
{
ICELogInfo(<< "CRC check failed.");
return 0;
}
// Build NB frame to decode
ByteBuffer dataToDecode;
dataToDecode.resize(1 + frame.mPayloadSize); // Reserve place
// Copy AMR data
memmove(dataToDecode.mutableData() + 1, frame.mPayload, frame.mPayloadSize);
uint8_t frameType = 0xFF;
for (uint8_t ftIndex = 0; ftIndex <= 9 && frameType == 0xFF; ftIndex++)
if (amrnb_framelen[ftIndex] == frame.mPayloadSize)
frameType = ftIndex;
// Check if frameType comparing is correct
if (frameType == 0xFF)
return 0;
dataToDecode.mutableData()[0] = (frameType << 3) | (1 << 2);
Decoder_Interface_Decode(mDecoderCtx, (const unsigned char*)dataToDecode.data(), (short*)output, 0);
return pcmLength();
}
else
{
if (outputCapacity < pcmLength())
return 0;
if (inputBytes == 0)
{ // PLC part
unsigned char buffer[32];
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
Decoder_Interface_Decode(mDecoderCtx, buffer, (short*)output, 0); // Handle missing data
return pcmLength();
}
AmrPayloadInfo info;
info.mCurrentTimestamp = mCurrentDecoderTimestamp;
info.mOctetAligned = mConfig.mOctetAligned;
info.mPayload = (const uint8_t*)input;
info.mPayloadLength = inputBytes;
info.mWideband = false;
info.mInterleaving = false;
AmrPayload ap = parseAmrPayload(info);
// Save current timestamp
mCurrentDecoderTimestamp = info.mCurrentTimestamp;
// Check if packet is corrupted
if (ap.mDiscardPacket)
return 0;
// Check for output buffer capacity
if (outputCapacity < (int)ap.mFrames.size() * pcmLength())
return 0;
if (ap.mFrames.empty())
{
ICELogCritical(<< "No AMR frames");
}
short* dataOut = (short*)output;
for (AmrFrame& frame: ap.mFrames)
{
if (frame.mData)
{
// Call decoder
Decoder_Interface_Decode(mDecoderCtx, (const unsigned char*)frame.mData->data(), (short*)dataOut, 0);
dataOut += pcmLength() / 2;
}
}
return pcmLength() * ap.mFrames.size();
}
return pcmLength();
}
int AmrNbCodec::plc(int lostFrames, void* output, int outputCapacity)
{
if (outputCapacity < lostFrames * pcmLength())
return 0;
short* dataOut = (short*)output;
for (int i=0; i < lostFrames; i++)
{
unsigned char buffer[32];
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
Decoder_Interface_Decode(mDecoderCtx, buffer, dataOut, 0); // Handle missing data
dataOut += L_FRAME;
}
return lostFrames * pcmLength();
}
int AmrNbCodec::getSwitchCounter() const
{
return mSwitchCounter;
}
// -------- AMR WB codec
AmrWbCodec::CodecFactory::CodecFactory(const AmrCodecConfig& config)
:mConfig(config)
{}
const char* AmrWbCodec::CodecFactory::name()
{
return MT_AMRWB_CODECNAME;
}
int AmrWbCodec::CodecFactory::samplerate()
{
return 16000;
}
int AmrWbCodec::CodecFactory::payloadType()
{
return mConfig.mPayloadType;
}
#ifdef USE_RESIP_INTEGRATION
void AmrWbCodec::CodecFactory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
{
}
int AmrWbCodec::CodecFactory::processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
{
return 0;
}
void AmrWbCodec::CodecFactory::create(CodecMap& codecs)
{
codecs[payloadType()] = std::shared_ptr<Codec>(new AmrWbCodec(mConfig));
}
#endif
PCodec AmrWbCodec::CodecFactory::create()
{
return PCodec(new AmrWbCodec(mConfig));
}
AmrWbCodec::AmrWbCodec(const AmrCodecConfig& config)
:mEncoderCtx(nullptr), mDecoderCtx(nullptr), mConfig(config),
mSwitchCounter(0), mPreviousPacketLength(0)
{
//mEncoderCtx = E_IF_init();
mDecoderCtx = D_IF_init();
mCurrentDecoderTimestamp = 0;
}
AmrWbCodec::~AmrWbCodec()
{
if (mEncoderCtx)
{
//E_IF_exit(mEncoderCtx);
mEncoderCtx = nullptr;
}
if (mDecoderCtx)
{
D_IF_exit(mDecoderCtx);
mDecoderCtx = nullptr;
}
}
const char* AmrWbCodec::name()
{
return MT_AMRWB_CODECNAME;
}
int AmrWbCodec::pcmLength()
{
return 20 * 16 * 2;
}
int AmrWbCodec::rtpLength()
{
return 0; // VBR
}
int AmrWbCodec::frameTime()
{
return 20;
}
int AmrWbCodec::samplerate()
{
return 16000;
}
int AmrWbCodec::encode(const void* input, int inputBytes, void* output, int outputCapacity)
{
if (inputBytes % pcmLength())
return 0;
// Declare the data input pointer
const short *dataIn = (const short *)input;
// Declare the data output pointer
unsigned char *dataOut = (unsigned char *)output;
// Find how much RTP frames will be generated
unsigned int frames = inputBytes / pcmLength();
// Generate frames
for (unsigned int i = 0; i < frames; i++)
{
// dataOut += Encoder_Interface_Encode(mEncoderCtx, Mode::MRDTX, dataIn, dataOut, 1);
// dataIn += pcmLength() / 2;
}
return frames * rtpLength();
}
#define L_FRAME 160
#define AMR_BITRATE_DTX 15
int AmrWbCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
{
if (mConfig.mIuUP)
{
IuUP::Frame frame;
if (!IuUP::parse2((const uint8_t*)input, inputBytes, frame))
return 0;
if (!frame.mHeaderCrcOk || !frame.mPayloadCrcOk)
{
ICELogInfo(<< "CRC check failed.");
return 0;
}
// Build first byte to help decoder
//ICELogDebug(<< "Decoding AMR frame length = " << frame.mPayloadSize);
// Reserve space
ByteBuffer dataToDecode;
dataToDecode.resize(1 + frame.mPayloadSize);
// Copy AMR data
memmove(dataToDecode.mutableData() + 1, frame.mPayload, frame.mPayloadSize);
uint8_t frameType = 0xFF;
for (uint8_t ftIndex = 0; ftIndex <= 9 && frameType == 0xFF; ftIndex++)
if (amrwb_framelen[ftIndex] == frame.mPayloadSize)
frameType = ftIndex;
if (frameType == 0xFF)
return 0;
dataToDecode.mutableData()[0] = (frameType << 3) | (1 << 2);
D_IF_decode(mDecoderCtx, (const unsigned char*)dataToDecode.data(), (short*)output, 0);
return pcmLength();
}
else
{
AmrPayloadInfo info;
info.mCurrentTimestamp = mCurrentDecoderTimestamp;
info.mOctetAligned = mConfig.mOctetAligned;
info.mPayload = (const uint8_t*)input;
info.mPayloadLength = inputBytes;
info.mWideband = true;
info.mInterleaving = false;
AmrPayload ap = parseAmrPayload(info);
// Save current timestamp
mCurrentDecoderTimestamp = info.mCurrentTimestamp;
// Check if packet is corrupted
if (ap.mDiscardPacket)
return 0;
// Check for output buffer capacity
if (outputCapacity < (int)ap.mFrames.size() * pcmLength())
return 0;
short* dataOut = (short*)output;
for (AmrFrame& frame: ap.mFrames)
{
if (frame.mData)
{
D_IF_decode(mDecoderCtx, (const unsigned char*)frame.mData->data(), (short*)dataOut, 0);
dataOut += pcmLength() / 2;
}
}
return pcmLength() * ap.mFrames.size();
}
return 0;
}
int AmrWbCodec::plc(int lostFrames, void* output, int outputCapacity)
{
/* if (outputCapacity < lostFrames * pcmLength())
return 0;
short* dataOut = (short*)output;
for (int i=0; i < lostFrames; i++)
{
unsigned char buffer[32];
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
Decoder_Interface_Decode(mDecoderCtx, buffer, dataOut, 0); // Handle missing data
dataOut += L_FRAME;
}
*/
return lostFrames * pcmLength();
}
int AmrWbCodec::getSwitchCounter() const
{
return mSwitchCounter;
}
// ------------- GSM EFR -----------------
GsmEfrCodec::GsmEfrFactory::GsmEfrFactory(bool iuup, int ptype)
:mIuUP(iuup), mPayloadType(ptype)
{
}
const char* GsmEfrCodec::GsmEfrFactory::name()
{
return MT_GSMEFR_CODECNAME;
}
int GsmEfrCodec::GsmEfrFactory::samplerate()
{
return 8000;
}
int GsmEfrCodec::GsmEfrFactory::payloadType()
{
return mPayloadType;
}
#ifdef USE_RESIP_INTEGRATION
void GsmEfrCodec::GsmEfrFactory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
{
}
int GsmEfrCodec::GsmEfrFactory::processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
{
return 0;
}
void GsmEfrCodec::GsmEfrFactory::create(CodecMap& codecs)
{
codecs[payloadType()] = std::shared_ptr<Codec>(new GsmEfrCodec(mIuUP));
}
#endif
PCodec GsmEfrCodec::GsmEfrFactory::create()
{
return PCodec(new GsmEfrCodec(mIuUP));
}
GsmEfrCodec::GsmEfrCodec(bool iuup)
:mEncoderCtx(nullptr), mDecoderCtx(nullptr), mIuUP(iuup)
{
mEncoderCtx = Encoder_Interface_init(1);
mDecoderCtx = Decoder_Interface_init();
}
GsmEfrCodec::~GsmEfrCodec()
{
if (mEncoderCtx)
{
Encoder_Interface_exit(mEncoderCtx);
mEncoderCtx = nullptr;
}
if (mDecoderCtx)
{
Decoder_Interface_exit(mDecoderCtx);
mDecoderCtx = nullptr;
}
}
const char* GsmEfrCodec::name()
{
return MT_GSMEFR_CODECNAME;
}
int GsmEfrCodec::pcmLength()
{
return 20 * 16;
}
int GsmEfrCodec::rtpLength()
{
return 0;
}
int GsmEfrCodec::frameTime()
{
return 20;
}
int GsmEfrCodec::samplerate()
{
return 8000;
}
int GsmEfrCodec::encode(const void* input, int inputBytes, void* output, int outputCapacity)
{
if (inputBytes % pcmLength())
return 0;
// Declare the data input pointer
const short *dataIn = (const short *)input;
// Declare the data output pointer
unsigned char *dataOut = (unsigned char *)output;
// Find how much RTP frames will be generated
unsigned int frames = inputBytes / pcmLength();
// Generate frames
for (unsigned int i = 0; i < frames; i++)
{
dataOut += Encoder_Interface_Encode(mEncoderCtx, Mode::MRDTX, dataIn, dataOut, 1);
dataIn += pcmLength() / 2;
}
return frames * rtpLength();
}
#define L_FRAME 160
#define AMR_BITRATE_DTX 15
#define GSM_EFR_SAMPLES 160
#define GSM_EFR_FRAME_LEN 31
static void
msb_put_bit(uint8_t *buf, int bn, int bit)
{
int pos_byte = bn >> 3;
int pos_bit = 7 - (bn & 7);
if (bit)
buf[pos_byte] |= (1 << pos_bit);
else
buf[pos_byte] &= ~(1 << pos_bit);
}
static int
msb_get_bit(const uint8_t *buf, int bn)
{
int pos_byte = bn >> 3;
int pos_bit = 7 - (bn & 7);
return (buf[pos_byte] >> pos_bit) & 1;
}
const uint16_t gsm690_12_2_bitorder[244] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 23, 15, 16, 17, 18,
19, 20, 21, 22, 24, 25, 26, 27, 28, 38,
141, 39, 142, 40, 143, 41, 144, 42, 145, 43,
146, 44, 147, 45, 148, 46, 149, 47, 97, 150,
200, 48, 98, 151, 201, 49, 99, 152, 202, 86,
136, 189, 239, 87, 137, 190, 240, 88, 138, 191,
241, 91, 194, 92, 195, 93, 196, 94, 197, 95,
198, 29, 30, 31, 32, 33, 34, 35, 50, 100,
153, 203, 89, 139, 192, 242, 51, 101, 154, 204,
55, 105, 158, 208, 90, 140, 193, 243, 59, 109,
162, 212, 63, 113, 166, 216, 67, 117, 170, 220,
36, 37, 54, 53, 52, 58, 57, 56, 62, 61,
60, 66, 65, 64, 70, 69, 68, 104, 103, 102,
108, 107, 106, 112, 111, 110, 116, 115, 114, 120,
119, 118, 157, 156, 155, 161, 160, 159, 165, 164,
163, 169, 168, 167, 173, 172, 171, 207, 206, 205,
211, 210, 209, 215, 214, 213, 219, 218, 217, 223,
222, 221, 73, 72, 71, 76, 75, 74, 79, 78,
77, 82, 81, 80, 85, 84, 83, 123, 122, 121,
126, 125, 124, 129, 128, 127, 132, 131, 130, 135,
134, 133, 176, 175, 174, 179, 178, 177, 182, 181,
180, 185, 184, 183, 188, 187, 186, 226, 225, 224,
229, 228, 227, 232, 231, 230, 235, 234, 233, 238,
237, 236, 96, 199,
};
int GsmEfrCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
{
/* if (mIuUP)
{
// Try to parse IuUP frame
IuUP::Frame frame;
if (!IuUP::parse2((const uint8_t*)input, inputBytes, frame))
return 0;
// Check if CRC failed - it is check from IuUP data
if (!frame.mHeaderCrcOk || !frame.mPayloadCrcOk)
{
ICELogInfo(<< "CRC check failed.");
return 0;
}
// Build NB frame to decode
ByteBuffer dataToDecode;
dataToDecode.resize(1 + frame.mPayloadSize); // Reserve place
// Copy AMR data
memmove(dataToDecode.mutableData() + 1, frame.mPayload, frame.mPayloadSize);
uint8_t frameType = 0xFF;
for (uint8_t ftIndex = 0; ftIndex <= 9 && frameType == 0xFF; ftIndex++)
if (amrnb_framelen[ftIndex] == frame.mPayloadSize)
frameType = ftIndex;
// Check if frameType comparing is correct
if (frameType == 0xFF)
return 0;
dataToDecode.mutableData()[0] = (frameType << 3) | (1 << 2);
Decoder_Interface_Decode(mDecoderCtx, (const unsigned char*)dataToDecode.data(), (short*)output, 0);
return pcmLength();
}
else */
{
if (outputCapacity < pcmLength())
return 0;
if (inputBytes == 0)
{ // PLC part
unsigned char buffer[32];
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
Decoder_Interface_Decode(mDecoderCtx, buffer, (short*)output, 0); // Handle missing data
}
else
{
// Reorder bytes from input to dst
uint8_t dst[GSM_EFR_FRAME_LEN];
const uint8_t* src = (const uint8_t*)input;
for (int i=0; i<(GSM_EFR_FRAME_LEN-1); i++)
dst[i] = (src[i] << 4) | (src[i+1] >> 4);
dst[GSM_EFR_FRAME_LEN-1] = src[GSM_EFR_FRAME_LEN-1] << 4;
unsigned char in[GSM_EFR_FRAME_LEN + 1];
// Reorder bits
in[0] = 0x3c; /* AMR mode 7 = GSM-EFR, Quality bit is set */
in[GSM_EFR_FRAME_LEN] = 0x0;
for (int i=0; i<244; i++)
{
int si = gsm690_12_2_bitorder[i];
int di = i;
msb_put_bit(in + 1, di, msb_get_bit(dst, si));
}
// Decode
memset(output, 0, pcmLength());
Decoder_Interface_Decode(mDecoderCtx, in, (short*)output, 0);
uint8_t* pcm = (uint8_t*)output;
for (int i=0; i<160; i++)
{
uint16_t w = ((uint16_t*)output)[i];
pcm[(i<<1) ] = w & 0xff;
pcm[(i<<1)+1] = (w >> 8) & 0xff;
}
}
}
return pcmLength();
}
int GsmEfrCodec::plc(int lostFrames, void* output, int outputCapacity)
{
if (outputCapacity < lostFrames * pcmLength())
return 0;
short* dataOut = (short*)output;
for (int i=0; i < lostFrames; i++)
{
unsigned char buffer[32];
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
Decoder_Interface_Decode(mDecoderCtx, buffer, dataOut, 0); // Handle missing data
dataOut += L_FRAME;
}
return lostFrames * pcmLength();
}
#endif

View File

@@ -0,0 +1,162 @@
#ifndef MT_AMRCODEC_H
#define MT_AMRCODEC_H
#include "../config.h"
#include <map>
#include "MT_Codec.h"
#include "../helper/HL_Pointer.h"
#if defined(USE_AMR_CODEC)
# include "opencore-amr/amrnb/interf_enc.h"
# include "opencore-amr/amrnb/interf_dec.h"
# include "opencore-amr/amrwb/if_rom.h"
# include "opencore-amr/amrwb/dec_if.h"
namespace MT
{
struct AmrCodecConfig
{
bool mIuUP;
bool mOctetAligned;
int mPayloadType;
};
class AmrNbCodec : public Codec
{
protected:
void* mEncoderCtx;
void* mDecoderCtx;
AmrCodecConfig mConfig;
unsigned mCurrentDecoderTimestamp;
int mSwitchCounter;
int mPreviousPacketLength;
public:
class CodecFactory: public Factory
{
public:
CodecFactory(const AmrCodecConfig& config);
const char* name() override;
int samplerate() override;
int payloadType() override;
#ifdef USE_RESIP_INTEGRATION
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
void create(CodecMap& codecs) override;
#endif
PCodec create() override;
protected:
AmrCodecConfig mConfig;
};
AmrNbCodec(const AmrCodecConfig& config);
virtual ~AmrNbCodec();
const char* name() override;
int pcmLength() override;
int rtpLength() override;
int frameTime() override;
int samplerate() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int plc(int lostFrames, void* output, int outputCapacity) override;
int getSwitchCounter() const;
};
class AmrWbCodec : public Codec
{
protected:
void* mEncoderCtx;
void* mDecoderCtx;
AmrCodecConfig mConfig;
uint64_t mCurrentDecoderTimestamp;
int mSwitchCounter;
int mPreviousPacketLength;
public:
class CodecFactory: public Factory
{
public:
CodecFactory(const AmrCodecConfig& config);
const char* name() override;
int samplerate() override;
int payloadType() override;
#ifdef USE_RESIP_INTEGRATION
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
void create(CodecMap& codecs) override;
#endif
PCodec create() override;
protected:
AmrCodecConfig mConfig;
};
AmrWbCodec(const AmrCodecConfig& config);
virtual ~AmrWbCodec();
const char* name() override;
int pcmLength() override;
int rtpLength() override;
int frameTime() override;
int samplerate() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int plc(int lostFrames, void* output, int outputCapacity) override;
int getSwitchCounter() const;
};
class GsmEfrCodec : public Codec
{
protected:
void* mEncoderCtx;
void* mDecoderCtx;
bool mIuUP;
public:
class GsmEfrFactory: public Factory
{
public:
GsmEfrFactory(bool iuup, int ptype);
const char* name() override;
int samplerate() override;
int payloadType() override;
#ifdef USE_RESIP_INTEGRATION
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
void create(CodecMap& codecs) override;
#endif
PCodec create() override;
protected:
bool mIuUP;
int mPayloadType;
};
GsmEfrCodec(bool iuup = false);
virtual ~GsmEfrCodec();
const char* name() override;
int pcmLength() override;
int rtpLength() override;
int frameTime() override;
int samplerate() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int plc(int lostFrames, void* output, int outputCapacity) override;
};
} // End of MT namespace
#endif
#endif // MT_AMRCODEC_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,421 @@
/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __AUDIO_CODEC_H
#define __AUDIO_CODEC_H
#include "../config.h"
#include <map>
#include "MT_Codec.h"
#include "../audio/Audio_Resampler.h"
#include "../helper/HL_Pointer.h"
#include "webrtc/ilbcfix/ilbc.h"
#include "webrtc/isac/isacfix.h"
#include "webrtc/g711/g711_interface.h"
extern "C"
{
#include "libgsm/gsm.h"
#include "g722/g722.h"
}
#include "libg729/g729_typedef.h"
#include "libg729/g729_ld8a.h"
#ifdef USE_OPUS_CODEC
# include "opus.h"
#endif
namespace MT
{
class G729Codec: public Codec
{
protected:
CodState* mEncoder = nullptr;
DecState* mDecoder = nullptr;
void decodeFrame(const uint8_t* rtp, int16_t* pcm);
public:
class G729Factory: public Factory
{
public:
const char* name() override;
int channels() override;
int samplerate() override;
int payloadType() override;
#if defined(USE_RESIP_INTEGRATION)
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
#endif
PCodec create() override;
};
G729Codec();
~G729Codec();
const char* name() override;
int pcmLength() override;
int rtpLength() override;
int frameTime() override;
int samplerate() override;
int channels() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int plc(int lostFrames, void* output, int outputCapacity) override;
};
#ifdef USE_OPUS_CODEC
class OpusCodec: public Codec
{
protected:
OpusEncoder *mEncoderCtx;
OpusDecoder *mDecoderCtx;
int mPTime, mSamplerate, mChannels;
Audio::SpeexResampler mDecodeResampler;
public:
struct Params
{
bool mUseDtx, mUseInbandFec, mStereo;
int mPtime, mTargetBitrate, mExpectedPacketLoss;
Params();
#if defined(USE_RESIP_INTEGRATION)
resip::Data toString() const;
void parse(const resip::Data& params);
#endif
};
class OpusFactory: public Factory
{
protected:
int mSamplerate, mChannels, mPType;
OpusCodec::Params mParams;
typedef std::vector<PCodec> CodecList;
CodecList mCodecList;
public:
OpusFactory(int samplerate, int channels, int ptype);
const char* name() override;
int channels() override;
int samplerate() override;
int payloadType() override;
#if defined(USE_RESIP_INTEGRATION)
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
#endif
PCodec create() override;
};
OpusCodec(int samplerate, int channels, int ptime);
~OpusCodec();
void applyParams(const Params& params);
const char* name();
int pcmLength();
int rtpLength();
int frameTime();
int samplerate();
int channels();
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
int plc(int lostFrames, void* output, int outputCapacity);
};
#endif
class IlbcCodec: public Codec
{
protected:
int mPacketTime; /// Single frame time (20 or 30 ms)
iLBC_encinst_t* mEncoderCtx;
iLBC_decinst_t* mDecoderCtx;
public:
class IlbcFactory: public Factory
{
protected:
int mPtime;
int mPType20ms, mPType30ms;
public:
IlbcFactory(int ptype20ms, int ptype30ms);
const char* name();
int samplerate();
int payloadType();
#ifdef USE_RESIP_INTEGRATION
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction);
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction);
void create(CodecMap& codecs);
#endif
PCodec create();
};
IlbcCodec(int packetTime);
virtual ~IlbcCodec();
const char* name();
int pcmLength();
int rtpLength();
int frameTime();
int samplerate();
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
int plc(int lostFrames, void* output, int outputCapacity);
};
class G711Codec: public Codec
{
public:
class AlawFactory: public Factory
{
public:
const char* name();
int samplerate();
int payloadType();
PCodec create();
};
class UlawFactory: public Factory
{
public:
const char* name();
int samplerate();
int payloadType();
PCodec create();
};
enum
{
ALaw = 0, /// a-law codec type
ULaw = 1 /// u-law codec type
};
G711Codec(int type);
~G711Codec();
const char* name();
int pcmLength();
int frameTime();
int rtpLength();
int samplerate();
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
int plc(int lostSamples, void* output, int outputCapacity);
protected:
int mType; /// Determines if it is u-law or a-law codec. Its value is ALaw or ULaw.
};
class IsacCodec: public Codec
{
protected:
int mSamplerate;
ISACFIX_MainStruct* mEncoderCtx;
ISACFIX_MainStruct* mDecoderCtx;
public:
class IsacFactory16K: public Factory
{
public:
IsacFactory16K(int ptype);
const char* name();
int samplerate();
int payloadType();
PCodec create();
protected:
int mPType;
};
class IsacFactory32K: public Factory
{
public:
IsacFactory32K(int ptype);
const char* name();
int samplerate();
int payloadType();
PCodec create();
protected:
int mPType;
};
IsacCodec(int sampleRate);
~IsacCodec();
const char* name();
int pcmLength();
int rtpLength();
int frameTime();
int samplerate();
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
int plc(int lostFrames, void* output, int outputCapacity);
};
/// GSM MIME name
#define GSM_MIME_NAME "gsm"
/// Optional GSM SIP attributes
#define GSM_SIP_ATTR ""
/// GSM codec single frame time in milliseconds
#define GSM_AUDIOFRAME_TIME 20
/// GSM codec single RTP frame size in bytes
#define GSM_RTPFRAME_SIZE_33 33
#define GSM_RTPFRAME_SIZE_32 32
#define GSM_RTPFRAME_SIZE_31 31
/// GSM payload type
#define GSM_PAYLOAD_TYPE 3
/// GSM bitrate (bits/sec)
#define GSM_BITRATE 13000
/*!
* GSM codec wrapper. Based on implementation located in libgsm directory.
* @see IMediaCodec
*/
class GsmCodec: public Codec
{
public:
enum class Type
{
Bytes_33,
Bytes_32,
Bytes_31,
Bytes_65
};
protected:
struct gsm_state * mGSM; /// Pointer to codec context
Type mCodecType;
public:
class GsmFactory: public Factory
{
protected:
int mPayloadType;
Type mCodecType;
public:
GsmFactory(Type codecType = Type::Bytes_33, int pt = GSM_PAYLOAD_TYPE);
const char* name();
int samplerate();
int payloadType();
PCodec create();
};
/*! Default constructor. Initializes codec context's pointer to NULL. */
GsmCodec(Type codecType);
/*! Destructor. */
virtual ~GsmCodec();
const char* name();
int pcmLength();
int rtpLength();
int frameTime();
int samplerate();
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
int plc(int lostFrames, void* output, int outputCapacity);
};
/// GSM MIME name
#define G722_MIME_NAME "g722"
/// Optional GSM SIP attributes
#define G722_SIP_ATTR ""
/// GSM codec single frame time in milliseconds
#define G722_AUDIOFRAME_TIME 20
/// GSM codec single RTP frame size in bytes
#define G722_RTPFRAME_SIZE 80
/// GSM payload type
#define G722_PAYLOAD_TYPE 9
/// GSM bitrate (bits/sec)
#define G722_BITRATE 64000
class G722Codec: public Codec
{
protected:
void* mEncoder;
void* mDecoder;
public:
class G722Factory: public Factory
{
public:
G722Factory();
const char* name();
int samplerate();
int payloadType();
PCodec create();
};
G722Codec();
virtual ~G722Codec();
const char* name();
int pcmLength();
int rtpLength();
int frameTime();
int samplerate();
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
int plc(int lostFrames, void* output, int outputCapacity);
//unsigned GetSamplerate() { return 16000; }
};
class GsmHrCodec: public Codec
{
protected:
void* mDecoder;
public:
class GsmHrFactory: public Factory
{
protected:
int mPtype;
public:
GsmHrFactory(int ptype);
const char* name() override;
int samplerate() override;
int payloadType() override;
PCodec create() override;
};
GsmHrCodec();
virtual ~GsmHrCodec();
const char* name() override;
int pcmLength() override;
int rtpLength() override;
int frameTime() override;
int samplerate() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int plc(int lostFrames, void* output, int outputCapacity) override;
};
}
#endif

View File

@@ -0,0 +1,723 @@
/* Copyright(C) 2007-2018 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "../config.h"
#include "MT_AudioReceiver.h"
#include "MT_AudioCodec.h"
#include "MT_CngHelper.h"
#include "../helper/HL_Log.h"
#include "../audio/Audio_Interface.h"
#include "../audio/Audio_Resampler.h"
#if defined(USE_AMR_CODEC)
# include "MT_AmrCodec.h"
#endif
#define LOG_SUBSYSTEM "AudioReceiver"
//#define DUMP_DECODED
using namespace MT;
// ----------------- RtpBuffer::Packet --------------
RtpBuffer::Packet::Packet(std::shared_ptr<RTPPacket> packet, int timelength, int rate)
:mRtp(packet), mTimelength(timelength), mRate(rate)
{
}
std::shared_ptr<RTPPacket> RtpBuffer::Packet::rtp() const
{
return mRtp;
}
int RtpBuffer::Packet::timelength() const
{
return mTimelength;
}
int RtpBuffer::Packet::rate() const
{
return mRate;
}
// ------------ RtpBuffer ----------------
RtpBuffer::RtpBuffer(Statistics& stat)
:mStat(stat), mSsrc(0), mHigh(RTP_BUFFER_HIGH), mLow(RTP_BUFFER_LOW), mPrebuffer(RTP_BUFFER_PREBUFFER),
mFirstPacketWillGo(true),
mReturnedCounter(0), mAddCounter(0), mFetchedPacket(std::shared_ptr<RTPPacket>(), 0, 0)
{
}
RtpBuffer::~RtpBuffer()
{
ICELogInfo(<< "Number of add packets: " << mAddCounter << ", number of retrieved packets " << mReturnedCounter);
}
void RtpBuffer::setHigh(int milliseconds)
{
mHigh = milliseconds;
}
int RtpBuffer::high()
{
return mHigh;
}
void RtpBuffer::setLow(int milliseconds)
{
mLow = milliseconds;
}
int RtpBuffer::low()
{
return mLow;
}
void RtpBuffer::setPrebuffer(int milliseconds)
{
mPrebuffer = milliseconds;
}
int RtpBuffer::prebuffer()
{
return mPrebuffer;
}
int RtpBuffer::getCount() const
{
Lock l(mGuard);
return mPacketList.size();
}
bool SequenceSort(const RtpBuffer::Packet& p1, const RtpBuffer::Packet& p2)
{
return p1.rtp()->GetExtendedSequenceNumber() < p2.rtp()->GetExtendedSequenceNumber();
}
bool RtpBuffer::add(std::shared_ptr<jrtplib::RTPPacket> packet, int timelength, int rate)
{
if (!packet)
return false;
Lock l(mGuard);
// Update statistics
mStat.mSsrc = packet->GetSSRC();
// Update jitter
ICELogMedia(<< "Adding new packet into jitter buffer");
mAddCounter++;
// Look for maximum&minimal sequence number; check for dublicates
unsigned maxno = 0xFFFFFFFF, minno = 0;
// New sequence number
unsigned newSeqno = packet->GetExtendedSequenceNumber();
for (PacketList::iterator iter = mPacketList.begin(); iter != mPacketList.end(); iter++)
{
Packet& p = *iter;
unsigned seqno = p.rtp()->GetExtendedSequenceNumber();
if (seqno == newSeqno)
{
mStat.mDuplicatedRtp++;
ICELogMedia(<< "Discovered duplicated packet, skipping");
return false;
}
if (seqno > maxno)
maxno = seqno;
if (seqno < minno)
minno = seqno;
}
int available = findTimelength();
if (newSeqno > minno || (available < mHigh))
{
Packet p(packet, timelength, rate);
mPacketList.push_back(p);
std::sort(mPacketList.begin(), mPacketList.end(), SequenceSort);
// Limit by max timelength
available = findTimelength();
while (available > mHigh && mPacketList.size())
{
//ICELogCritical( << "Dropping RTP packet from jitter");
available -= mPacketList.front().timelength();
mPacketList.erase(mPacketList.begin());
}
}
else
{
ICELogMedia(<< "Too old packet, skipping");
mStat.mOldRtp++;
return false;
}
return true;
}
RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
{
Lock l(mGuard);
FetchResult result = FetchResult::NoPacket;
rl.clear();
// See if there is enough information in buffer
int total = findTimelength();
if (total < mLow)
result = FetchResult::NoPacket;
else
{
if (mFetchedPacket.rtp())
{
if (mPacketList.empty())
{
result = FetchResult::NoPacket;
mStat.mPacketLoss++;
}
else
{
// Current sequence number ?
unsigned seqno = mPacketList.front().rtp()->GetExtendedSequenceNumber();
// Gap between new packet and previous on
int gap = seqno - mFetchedPacket.rtp()->GetSequenceNumber() - 1;
gap = std::min(gap, 127);
if (gap > 0 && mPacketList.empty())
{
result = FetchResult::Gap;
mStat.mPacketLoss += gap;
mStat.mLoss[gap]++;
}
else
{
if (gap > 0)
{
mStat.mPacketLoss += gap;
mStat.mLoss[gap]++;
}
result = FetchResult::RegularPacket;
Packet& p = mPacketList.front();
rl.push_back(p.rtp());
// Maybe it is time to replay packet right now ? For case of AMR SID packets
if (mFetchedPacket.rtp() && gap == 0 && mFetchedPacket.timelength() >= 10 && p.timelength() >= 10)
{
// Timestamp difference
int timestampDelta = TimeHelper::getDelta(p.rtp()->GetTimestamp(), mFetchedPacket.rtp()->GetTimestamp());
// Timestamp units per packet
int nrOfPackets = timestampDelta / (p.timelength() * (p.rate() / 1000));
// Add more copies of SID (most probably) packets
for (int i = 0; i < nrOfPackets - 1; i++)
{
//assert(false);
rl.push_back(p.rtp());
}
}
// Save last returned normal packet
mFetchedPacket = mPacketList.front();
// Remove returned packet from the list
mPacketList.erase(mPacketList.begin());
}
}
}
else
{
// See if prebuffer limit is reached
if (findTimelength() >= mPrebuffer)
{
// Normal packet will be returned
result = FetchResult::RegularPacket;
// Put it to output list
rl.push_back(mPacketList.front().rtp());
// Remember returned packet
mFetchedPacket = mPacketList.front();
// Remove returned packet from buffer list
mPacketList.erase(mPacketList.begin());
}
else
{
ICELogMedia(<< "Jitter buffer was not prebuffered yet; resulting no packet");
result = FetchResult::NoPacket;
}
}
}
if (result != FetchResult::NoPacket)
mReturnedCounter++;
return result;
}
int RtpBuffer::findTimelength()
{
int available = 0;
for (unsigned i = 0; i < mPacketList.size(); i++)
available += mPacketList[i].timelength();
return available;
}
int RtpBuffer::getNumberOfReturnedPackets() const
{
return mReturnedCounter;
}
int RtpBuffer::getNumberOfAddPackets() const
{
return mAddCounter;
}
//-------------- Receiver ---------------
Receiver::Receiver(Statistics& stat)
:mStat(stat)
{
}
Receiver::~Receiver()
{
}
//-------------- AudioReceiver ----------------
AudioReceiver::AudioReceiver(const CodecList::Settings& settings, MT::Statistics &stat)
:Receiver(stat), mBuffer(stat), mFrameCount(0), mFailedCount(0), mCodecSettings(settings),
mCodecList(settings)
{
// Init resamplers
mResampler8.start(AUDIO_CHANNELS, 8000, AUDIO_SAMPLERATE);
mResampler16.start(AUDIO_CHANNELS, 16000, AUDIO_SAMPLERATE);
mResampler32.start(AUDIO_CHANNELS, 32000, AUDIO_SAMPLERATE);
mResampler48.start(AUDIO_CHANNELS, 48000, AUDIO_SAMPLERATE);
// Init codecs
mCodecList.fillCodecMap(mCodecMap);
#if defined(DUMP_DECODED)
mDecodedDump = std::make_shared<Audio::WavFileWriter>();
mDecodedDump->open("decoded.wav", 8000 /*G711*/, AUDIO_CHANNELS);
#endif
}
AudioReceiver::~AudioReceiver()
{
#if defined(USE_PVQA_LIBRARY) && !defined(PVQA_SERVER)
if (mPVQA && mPvqaBuffer)
{
mStat.mPvqaMos = calculatePvqaMos(AUDIO_SAMPLERATE, mStat.mPvqaReport);
}
#endif
mResampler8.stop();
mResampler16.stop();
mResampler32.stop();
mResampler48.stop();
mDecodedDump.reset();
}
bool AudioReceiver::add(std::shared_ptr<jrtplib::RTPPacket> p, Codec** codec)
{
// Increase codec counter
mStat.mCodecCount[p->GetPayloadType()]++;
// Check if codec can be handled
CodecMap::iterator codecIter = mCodecMap.find(p->GetPayloadType());
if (codecIter == mCodecMap.end())
{
ICELogMedia(<< "Cannot find codec in available codecs");
return false; // Reject packet with unknown payload type
}
// Check if codec is created actually
if (!codecIter->second)
{
// Look for ptype
for (int codecIndex = 0; codecIndex < mCodecList.count(); codecIndex++)
if (mCodecList.codecAt(codecIndex).payloadType() == p->GetPayloadType())
codecIter->second = mCodecList.codecAt(codecIndex).create();
}
// Return pointer to codec if needed
if (codec)
*codec = codecIter->second.get();
if (mStat.mCodecName.empty())
mStat.mCodecName = codecIter->second.get()->name();
// Estimate time length
int timelen = 0, payloadLength = p->GetPayloadLength(), ptype = p->GetPayloadType();
if (!codecIter->second->rtpLength())
timelen = codecIter->second->frameTime();
else
timelen = int(double(payloadLength) / codecIter->second->rtpLength() * codecIter->second->frameTime() + 0.5);
// Process jitter
mJitterStats.process(p.get(), codecIter->second->samplerate());
mStat.mJitter = (float)mJitterStats.get().getCurrent();
// Check if packet is CNG
if (payloadLength >= 1 && payloadLength <= 6 && (ptype == 0 || ptype == 8))
timelen = mLastPacketTimeLength ? mLastPacketTimeLength : 20;
else
// Check if packet is too short from time length side
if (timelen < 2)
{
// It will cause statistics to report about bad RTP packet
// I have to replay last packet payload here to avoid report about lost packet
mBuffer.add(p, timelen, codecIter->second->samplerate());
return false;
}
// Queue packet to buffer
return mBuffer.add(p, timelen, codecIter->second->samplerate());
}
void AudioReceiver::processDecoded(Audio::DataWindow& output, DecodeOptions options)
{
// Write to audio dump if requested
if (mDecodedDump && mDecodedLength)
mDecodedDump->write(mDecodedFrame, mDecodedLength);
// Resample to target rate
bool resample = !((int)options & (int)DecodeOptions::DontResample);
makeMonoAndResample(resample ? mCodec->samplerate() : 0,
mCodec->channels());
// Update PVQA with stats
#if defined(USE_PVQA_LIBRARY) && !defined(PVQA_SERVER)
updatePvqa(mResampledFrame, mResampledLength);
#endif
// Send to output
output.add(mResampledFrame, mResampledLength);
}
bool AudioReceiver::getAudio(Audio::DataWindow& output, DecodeOptions options, int* rate)
{
bool result = false;
// Get next packet from buffer
RtpBuffer::ResultList rl;
RtpBuffer::FetchResult fr = mBuffer.fetch(rl);
switch (fr)
{
case RtpBuffer::FetchResult::Gap:
ICELogInfo(<< "Gap detected.");
mDecodedLength = mResampledLength = 0;
if (mCngPacket && mCodec)
{
// Synthesize comfort noise. It will be done on AUDIO_SAMPLERATE rate directly to mResampledFrame buffer.
// Do not forget to send this noise to analysis
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength, (short*)mDecodedFrame, false);
}
else
if (mCodec && mFrameCount && !mCodecSettings.mSkipDecode)
{
// Do PLC to mDecodedFrame/mDecodedLength
mDecodedLength = mCodec->plc(mFrameCount, mDecodedFrame, sizeof mDecodedFrame);
}
if (mDecodedLength)
{
processDecoded(output, options);
result = true;
}
break;
case RtpBuffer::FetchResult::NoPacket:
ICELogDebug(<< "No packet available in jitter buffer");
mFailedCount++;
#if defined(USE_PVQA_LIBRARY) && !defined(PVQA_SERVER)
if (mResampledLength > 0)
updatePvqa(nullptr, mResampledLength);
#endif
break;
case RtpBuffer::FetchResult::RegularPacket:
mFailedCount = 0;
for (std::shared_ptr<RTPPacket>& p: rl)
{
assert(p);
// Check if previously CNG packet was detected. Emit CNG audio here if needed.
if ((int)options & (int)DecodeOptions::FillCngGap && mCngPacket && mCodec)
{
// Fill CNG audio is server mode is present
int units = p->GetTimestamp() - mCngPacket->GetTimestamp();
int milliseconds = units / (mCodec->samplerate() / 1000);
if (milliseconds > mLastPacketTimeLength)
{
int frames100ms = milliseconds / 100;
for (int frameIndex = 0; frameIndex < frames100ms; frameIndex++)
{
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), 100, (short*)mDecodedFrame, false);
if (mDecodedLength)
processDecoded(output, options);
}
// Do not forget about tail!
int tail = milliseconds % 100;
if (tail)
{
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), tail, (short*)mDecodedFrame, false);
if (mDecodedLength)
processDecoded(output, options);
}
result = true;
}
}
// Find codec
mCodec = mCodecMap[p->GetPayloadType()];
if (mCodec)
{
if (rate)
*rate = mCodec->samplerate();
// Check if it is CNG packet
if ((p->GetPayloadType() == 0 || p->GetPayloadType() == 8) && p->GetPayloadLength() >= 1 && p->GetPayloadLength() <= 6)
{
mCngPacket = p;
mCngDecoder.decode3389(p->GetPayloadData(), p->GetPayloadLength());
// Emit CNG mLastPacketLength milliseconds
mDecodedLength = mCngDecoder.produce(mCodec->samplerate(), mLastPacketTimeLength, (short*)mDecodedFrame, true);
if (mDecodedLength)
processDecoded(output, options);
result = true;
}
else
{
// Reset CNG packet
mCngPacket.reset();
// Handle here regular RTP packets
// Check if payload length is ok
int tail = mCodec->rtpLength() ? p->GetPayloadLength() % mCodec->rtpLength() : 0;
if (!tail)
{
// Find number of frames
mFrameCount = mCodec->rtpLength() ? p->GetPayloadLength() / mCodec->rtpLength() : 1;
int frameLength = mCodec->rtpLength() ? mCodec->rtpLength() : (int)p->GetPayloadLength();
// Save last packet time length
mLastPacketTimeLength = mFrameCount * mCodec->frameTime();
// Decode
for (int i=0; i<mFrameCount && !mCodecSettings.mSkipDecode; i++)
{
// Decode frame by frame
mDecodedLength = mCodec->decode(p->GetPayloadData() + i*mCodec->rtpLength(),
frameLength, mDecodedFrame, sizeof mDecodedFrame);
if (mDecodedLength)
processDecoded(output, options);
}
result = mFrameCount > 0;
// Check for bitrate counter
#if defined(USE_AMR_CODEC)
processStatisticsWithAmrCodec(mCodec.get());
#endif
}
else
ICELogMedia(<< "RTP packet with tail.");
}
}
}
break;
default:
assert(0);
}
return result;
}
void AudioReceiver::makeMonoAndResample(int rate, int channels)
{
// Make mono from stereo - engine works with mono only for now
mConvertedLength = 0;
if (channels != AUDIO_CHANNELS)
{
if (channels == 1)
mConvertedLength = Audio::ChannelConverter::monoToStereo(mDecodedFrame, mDecodedLength, mConvertedFrame, mDecodedLength * 2);
else
mDecodedLength = Audio::ChannelConverter::stereoToMono(mDecodedFrame, mDecodedLength, mDecodedFrame, mDecodedLength / 2);
}
void* frames = mConvertedLength ? mConvertedFrame : mDecodedFrame;
unsigned length = mConvertedLength ? mConvertedLength : mDecodedLength;
Audio::Resampler* r = NULL;
switch (rate)
{
case 8000: r = &mResampler8; break;
case 16000: r = &mResampler16; break;
case 32000: r = &mResampler32; break;
case 48000: r = &mResampler48; break;
default:
memcpy(mResampledFrame, frames, length);
mResampledLength = length;
return;
}
int processedInput = 0;
mResampledLength = r->processBuffer(frames, length, processedInput, mResampledFrame, r->getDestLength(length));
// processedInput result value is ignored - it is always equal to length as internal sample rate is 8/16/32/48K
}
Codec* AudioReceiver::findCodec(int payloadType)
{
MT::CodecMap::const_iterator codecIter = mCodecMap.find(payloadType);
if (codecIter == mCodecMap.end())
return nullptr;
return codecIter->second.get();
}
#if defined(USE_PVQA_LIBRARY) && !defined(PVQA_SERVER)
void AudioReceiver::initPvqa()
{
// Allocate space for 20 seconds audio
if (!mPvqaBuffer)
{
mPvqaBuffer = std::make_shared<Audio::DataWindow>();
mPvqaBuffer->setCapacity(Audio::Format().sizeFromTime(20000));
}
// Instantiate & open PVQA analyzer
if (!mPVQA)
{
mPVQA = std::make_shared<MT::SevanaPVQA>();
mPVQA->open(PVQA_INTERVAL, MT::SevanaPVQA::Model::Stream);
}
}
void AudioReceiver::updatePvqa(const void *data, int size)
{
if (!mPVQA)
initPvqa();
if (mPVQA)
{
if (data)
mPvqaBuffer->add(data, size);
else
mPvqaBuffer->addZero(size);
Audio::Format fmt;
int frames = (int)fmt.timeFromSize(mPvqaBuffer->filled()) / (PVQA_INTERVAL * 1000);
if (frames > 0)
{
int time4pvqa = (int)(frames * PVQA_INTERVAL * 1000);
int size4pvqa = (int)fmt.sizeFromTime(time4pvqa);
ICELogInfo(<< "Updating PVQA with " << time4pvqa << " milliseconds of audio.");
mPVQA->update(fmt.mRate, fmt.mChannels, mPvqaBuffer->data(), size4pvqa);
mPvqaBuffer->erase(size4pvqa);
}
}
}
float AudioReceiver::calculatePvqaMos(int rate, std::string& report)
{
if (mPVQA && mPvqaBuffer)
{
// Flush remaining audio to analyzer
/*if (mPvqaBuffer->filled())
{
mPVQA->update(rate, AUDIO_CHANNELS, mPvqaBuffer->data(), mPvqaBuffer->filled());
mPvqaBuffer->clear();
}*/
return mPVQA->getResults(report, nullptr, rate, MT::SevanaPVQA::Codec::None);
}
return 0.0f;
}
#endif
#if defined(USE_AMR_CODEC)
void AudioReceiver::processStatisticsWithAmrCodec(Codec* c)
{
AmrNbCodec* nb = dynamic_cast<AmrNbCodec*>(c);
AmrWbCodec* wb = dynamic_cast<AmrWbCodec*>(c);
if (nb != nullptr)
mStat.mBitrateSwitchCounter = nb->getSwitchCounter();
else
if (wb != nullptr)
mStat.mBitrateSwitchCounter = wb->getSwitchCounter();
}
#endif
int AudioReceiver::getSize() const
{
int result = 0;
result += sizeof(*this) + mResampler8.getSize() + mResampler16.getSize() + mResampler32.getSize()
+ mResampler48.getSize();
if (mCodec)
result += mCodec->getSize();
return result;
}
int AudioReceiver::timelengthFor(jrtplib::RTPPacket& p)
{
CodecMap::iterator codecIter = mCodecMap.find(p.GetPayloadType());
if (codecIter == mCodecMap.end())
return 0;
PCodec codec = codecIter->second;
if (codec)
{
int frameCount = p.GetPayloadLength() / codec->rtpLength();
if (p.GetPayloadType() == 9/*G729A silence*/ && p.GetPayloadLength() % codec->rtpLength())
frameCount++;
return frameCount * codec->frameTime();
}
else
return 0;
}
int AudioReceiver::samplerateFor(jrtplib::RTPPacket& p)
{
CodecMap::iterator codecIter = mCodecMap.find(p.GetPayloadType());
if (codecIter != mCodecMap.end())
{
PCodec codec = codecIter->second;
if (codec)
return codec->samplerate();
}
return 8000;
}
// ----------------------- DtmfReceiver -------------------
DtmfReceiver::DtmfReceiver(Statistics& stat)
:Receiver(stat)
{
}
DtmfReceiver::~DtmfReceiver()
{
}
void DtmfReceiver::add(std::shared_ptr<RTPPacket> p)
{
}

View File

@@ -0,0 +1,199 @@
/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __MT_AUDIO_RECEIVER_H
#define __MT_AUDIO_RECEIVER_H
#include "MT_Stream.h"
#include "MT_CodecList.h"
#include "MT_AudioCodec.h"
#include "MT_CngHelper.h"
#include "../helper/HL_Pointer.h"
#include "../helper/HL_Sync.h"
#include "../helper/HL_Optional.hpp"
#include "jrtplib/src/rtppacket.h"
#include "jrtplib/src/rtcppacket.h"
#include "jrtplib/src/rtpsourcedata.h"
#include "../audio/Audio_DataWindow.h"
#include "../audio/Audio_Resampler.h"
#include <map>
#if defined(USE_PVQA_LIBRARY)
# include "MT_SevanaMos.h"
#endif
// #define DUMP_DECODED
namespace MT
{
using jrtplib::RTPPacket;
class RtpBuffer
{
public:
enum class FetchResult
{
RegularPacket,
Gap,
NoPacket
};
// Owns rtp packet data
class Packet
{
public:
Packet(std::shared_ptr<RTPPacket> packet, int timelen, int rate);
std::shared_ptr<RTPPacket> rtp() const;
int timelength() const;
int rate() const;
protected:
std::shared_ptr<RTPPacket> mRtp;
int mTimelength = 0, mRate = 0;
};
RtpBuffer(Statistics& stat);
~RtpBuffer();
unsigned ssrc();
void setSsrc(unsigned ssrc);
void setHigh(int milliseconds);
int high();
void setLow(int milliseconds);
int low();
void setPrebuffer(int milliseconds);
int prebuffer();
int getNumberOfReturnedPackets() const;
int getNumberOfAddPackets() const;
int findTimelength();
int getCount() const;
// Returns false if packet was not add - maybe too old or too new or duplicate
bool add(std::shared_ptr<RTPPacket> packet, int timelength, int rate);
typedef std::vector<std::shared_ptr<RTPPacket>> ResultList;
typedef std::shared_ptr<ResultList> PResultList;
FetchResult fetch(ResultList& rl);
protected:
unsigned mSsrc;
int mHigh, mLow, mPrebuffer;
int mReturnedCounter, mAddCounter;
mutable Mutex mGuard;
typedef std::vector<Packet> PacketList;
PacketList mPacketList;
Statistics& mStat;
bool mFirstPacketWillGo;
jrtplib::RTPSourceStats mRtpStats;
Packet mFetchedPacket;
};
class Receiver
{
public:
Receiver(Statistics& stat);
virtual ~Receiver();
protected:
Statistics& mStat;
};
class AudioReceiver: public Receiver
{
public:
AudioReceiver(const CodecList::Settings& codecSettings, Statistics& stat);
~AudioReceiver();
// Returns false when packet is rejected as illegal. codec parameter will show codec which will be used for decoding.
// Lifetime of pointer to codec is limited by lifetime of AudioReceiver (it is container).
bool add(std::shared_ptr<jrtplib::RTPPacket> p, Codec** codec = nullptr);
// Returns false when there is no rtp data from jitter
enum class DecodeOptions
{
ResampleToMainRate = 0,
DontResample = 1,
FillCngGap = 2
};
bool getAudio(Audio::DataWindow& output, DecodeOptions options = DecodeOptions::ResampleToMainRate, int* rate = nullptr);
// Looks for codec by payload type
Codec* findCodec(int payloadType);
RtpBuffer& getRtpBuffer() { return mBuffer; }
// Returns size of AudioReceiver's instance in bytes (including size of all data + codecs + etc.)
int getSize() const;
// Returns timelength for given packet
int timelengthFor(jrtplib::RTPPacket& p);
// Return samplerate for given packet
int samplerateFor(jrtplib::RTPPacket& p);
protected:
RtpBuffer mBuffer;
CodecMap mCodecMap;
PCodec mCodec;
int mFrameCount = 0;
CodecList::Settings mCodecSettings;
CodecList mCodecList;
JitterStatistics mJitterStats;
std::shared_ptr<jrtplib::RTPPacket> mCngPacket;
CngDecoder mCngDecoder;
// Buffer to hold decoded data
char mDecodedFrame[65536];
int mDecodedLength = 0;
// Buffer to hold data converted to stereo/mono
char mConvertedFrame[32768];
int mConvertedLength = 0;
// Buffer to hold data resampled to AUDIO_SAMPLERATE
char mResampledFrame[65536];
int mResampledLength = 0;
// Last packet time length
int mLastPacketTimeLength = 0;
int mFailedCount;
Audio::Resampler mResampler8, mResampler16,
mResampler32, mResampler48;
Audio::PWavFileWriter mDecodedDump;
// Zero rate will make audio mono but resampling will be skipped
void makeMonoAndResample(int rate, int channels);
// Resamples, sends to analysis, writes to dump and queues to output decoded frames from mDecodedFrame
void processDecoded(Audio::DataWindow& output, DecodeOptions options);
#if defined(USE_PVQA_LIBRARY) && !defined(PVQA_SERVER)
std::shared_ptr<SevanaPVQA> mPVQA;
void initPvqa();
void updatePvqa(const void* data, int size);
float calculatePvqaMos(int rate, std::string& report);
std::shared_ptr<Audio::DataWindow> mPvqaBuffer;
#endif
#if defined(USE_AMR_CODEC)
void processStatisticsWithAmrCodec(Codec* c);
#endif
};
class DtmfReceiver: public Receiver
{
public:
DtmfReceiver(Statistics& stat);
~DtmfReceiver();
void add(std::shared_ptr<RTPPacket> p);
};
}
#endif

View File

@@ -0,0 +1,426 @@
/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MT_AudioStream.h"
#include "MT_Dtmf.h"
#include "../helper/HL_StreamState.h"
#include "../helper/HL_Log.h"
#include "../audio/Audio_Resampler.h"
#include "../audio/Audio_Interface.h"
#include "jrtplib/src/rtpipv4address.h"
#include "jrtplib/src/rtpipv6address.h"
#include "jrtplib/src/rtppacket.h"
#include "jrtplib/src/rtptransmitter.h"
#include "jrtplib/src/rtpsessionparams.h"
#define LOG_SUBSYSTEM "AudioStream"
//#define DUMP_SENDING_AUDIO
using namespace MT;
AudioStream::AudioStream(const CodecList::Settings& settings)
:mPacketTime(0), mEncodedTime(0), mCodecSettings(settings),
mRemoteTelephoneCodec(0), mRtpSession(), mTransmittingPayloadType(-1),
mRtpSender(mStat), mRtpDump(NULL)
{
mOutputBuffer.setCapacity(16384);
mCapturedAudio.setCapacity(16384);
mCaptureResampler8.start(AUDIO_CHANNELS, AUDIO_SAMPLERATE, 8000);
mCaptureResampler16.start(AUDIO_CHANNELS, AUDIO_SAMPLERATE, 16000);
mCaptureResampler32.start(AUDIO_CHANNELS, AUDIO_SAMPLERATE, 32000);
mCaptureResampler48.start(AUDIO_CHANNELS, AUDIO_SAMPLERATE, 48000);
// Configure transmitter
jrtplib::RTPExternalTransmissionParams params(&mRtpSender, 0);
jrtplib::RTPSessionParams sessionParams;
sessionParams.SetAcceptOwnPackets(true);
sessionParams.SetMaximumPacketSize(MT_MAXRTPPACKET);
sessionParams.SetResolveLocalHostname(false);
sessionParams.SetUsePollThread(false);
sessionParams.SetOwnTimestampUnit(1/8000.0);
mRtpSession.Create(sessionParams, &params, jrtplib::RTPTransmitter::ExternalProto);
mRtpDtmfSession.Create(sessionParams, &params, jrtplib::RTPTransmitter::ExternalProto);
// Attach srtp session to sender
mRtpSender.setSrtpSession(&mSrtpSession);
//mRtpDump = new RtpDump("d:\\outgoing.rtp");
//mRtpSender.setDumpWriter(mRtpDump);
#if defined(DUMP_SENDING_AUDIO)
mSendingDump = std::make_shared<WavFileWriter>();
mSendingDump->open("sending_audio.wav", 8000, 1);
#endif
}
AudioStream::~AudioStream()
{
ICELogInfo(<< "Delete AudioStream instance");
if (mSendingDump)
{
mSendingDump->close();
mSendingDump.reset();
}
// Delete used rtp streams
for (AudioStreamMap::iterator streamIter = mStreamMap.begin(); streamIter != mStreamMap.end(); ++streamIter)
delete streamIter->second;
mStreamMap.clear();
if (mRtpDtmfSession.IsActive())
mRtpDtmfSession.Destroy();
if (mRtpSession.IsActive())
mRtpSession.Destroy();
if (mRtpDump)
{
mRtpDump->flush();
delete mRtpDump;
}
mCaptureResampler8.stop();
mCaptureResampler16.stop();
mCaptureResampler32.stop();
mCaptureResampler48.stop();
ICELogInfo(<< "Encoded " << mEncodedTime << " milliseconds of audio");
if (mDumpStreams.mStreamForRecordingIncoming)
mDumpStreams.mStreamForRecordingIncoming->close();
if (mDumpStreams.mStreamForReadingOutgoing)
mDumpStreams.mStreamForReadingOutgoing->close();
if (mFinalStatistics)
*mFinalStatistics = mStat;
ICELogInfo(<< mStat.toShortString());
}
void AudioStream::setDestination(const RtpPair<InternetAddress>& dest)
{
Lock l(mMutex);
Stream::setDestination(dest);
mRtpSender.setDestination(dest);
}
void AudioStream::setTransmittingCodec(Codec::Factory& factory, int payloadType)
{
ICELogInfo(<< "Selected codec " << factory.name() << "/" << factory.samplerate() << " for transmitting");
Lock l(mMutex);
mTransmittingCodec = factory.create();
mTransmittingPayloadType = payloadType;
if (mRtpSession.IsActive())
mRtpSession.SetTimestampUnit(1.0 / mTransmittingCodec->samplerate());
}
PCodec AudioStream::transmittingCodec()
{
Lock l(mMutex);
return mTransmittingCodec;
}
void AudioStream::addData(const void* buffer, int bytes)
{
assert(bytes == AUDIO_MIC_BUFFER_SIZE);
// Read predefined audio if configured
if (mDumpStreams.mStreamForReadingOutgoing)
{
if (mDumpStreams.mStreamForReadingOutgoing->isOpened())
mDumpStreams.mStreamForReadingOutgoing->read(const_cast<void*>(buffer), bytes);
}
// Read mirrored audio if needed
if (mMirror && mMirrorPrebuffered)
mMirrorBuffer.read(const_cast<void*>(buffer), bytes);
if (mMediaObserver)
mMediaObserver->onMedia(buffer, bytes, MT::Stream::MediaDirection::Outgoing, this, mMediaObserverTag);
Codec* codec = NULL;
{
Lock l(mMutex);
codec = mTransmittingCodec.get();
if (!codec)
return;
}
// Resample
unsigned dstlen = unsigned(float(codec->samplerate() / float(AUDIO_SAMPLERATE)) * bytes);
Audio::Resampler* r = NULL;
switch (codec->samplerate())
{
case 8000: r = &mCaptureResampler8; break;
case 16000: r = &mCaptureResampler16; break;
case 32000: r = &mCaptureResampler32; break;
case 48000: r = &mCaptureResampler48; break;
default:
assert(0);
}
int processedInput = 0;
dstlen = r->processBuffer(buffer, bytes, processedInput, mResampleBuffer, dstlen);
// ProcessedInput output value is ignored - because sample rate of input is always 8/16/32/48K - so all buffer is processed
// See if we need stereo <-> mono conversions
unsigned stereolen = 0;
if (codec->channels() != AUDIO_CHANNELS)
{
if (codec->channels() == 2)
stereolen = Audio::ChannelConverter::monoToStereo(mResampleBuffer, dstlen, mStereoBuffer, dstlen * 2);
else
dstlen = Audio::ChannelConverter::stereoToMono(mResampleBuffer, dstlen, mResampleBuffer, dstlen / 2);
}
// See if inband dtmf audio should be sent instead
ByteBuffer dtmf;
if (mDtmfContext.type() == DtmfContext::Dtmf_Inband && mDtmfContext.getInband(AUDIO_MIC_BUFFER_LENGTH, codec->samplerate(), dtmf))
mCapturedAudio.add(dtmf.data(), dtmf.size());
else
mCapturedAudio.add(stereolen ? mStereoBuffer : mResampleBuffer, stereolen ? stereolen : dstlen);
// See if it is time to send RFC2833 tone
ByteBuffer rfc2833, stopPacket;
if (mDtmfContext.type() == DtmfContext::Dtmf_Rfc2833 && mDtmfContext.getRfc2833(AUDIO_MIC_BUFFER_LENGTH, rfc2833, stopPacket))
{
if (rfc2833.size())
mRtpDtmfSession.SendPacket(rfc2833.data(), rfc2833.size(), mRemoteTelephoneCodec, true, AUDIO_MIC_BUFFER_LENGTH * 8);
if (stopPacket.size())
{
for (int i=0; i<3; i++)
mRtpDtmfSession.SendPacket(stopPacket.data(), stopPacket.size(), mRemoteTelephoneCodec, true, AUDIO_MIC_BUFFER_LENGTH * 8);
}
}
int processed = 0;
int encodedTime = 0;
int packetTime = mPacketTime ? mPacketTime : codec->frameTime();
// Make stereo version if required
for (int i=0; i<mCapturedAudio.filled() / mTransmittingCodec->pcmLength(); i++)
{
if (mSendingDump)
mSendingDump->write((const char*)mCapturedAudio.data() + codec->pcmLength() * i, codec->pcmLength());
int produced;
produced = codec->encode((const char*)mCapturedAudio.data() + codec->pcmLength()*i,
codec->pcmLength(), mFrameBuffer, MT_MAXAUDIOFRAME);
// Counter of processed input bytes of raw pcm data from microphone
processed += codec->pcmLength();
encodedTime += codec->frameTime();
mEncodedTime += codec->frameTime();
if (produced)
{
mEncodedAudio.appendBuffer(mFrameBuffer, produced);
if (packetTime <= encodedTime)
{
// Time to send packet
ICELogMedia(<< "Sending RTP packet pt = " << mTransmittingPayloadType << ", plength = " << (int)mEncodedAudio.size());
mRtpSession.SendPacketEx(mEncodedAudio.data(), mEncodedAudio.size(), mTransmittingPayloadType, false,
packetTime * codec->samplerate()/1000, 0, NULL, 0);
mEncodedAudio.clear();
encodedTime = 0;
}
}
}
mCapturedAudio.erase(processed);
}
void AudioStream::copyDataTo(Audio::Mixer& mixer, int needed)
{
// Local audio mixer - used to send audio to media observer
Audio::Mixer localMixer;
Audio::DataWindow forObserver;
// Iterate
for (auto& streamIter: mStreamMap)
{
Audio::DataWindow w;
w.setCapacity(32768);
SingleAudioStream* sas = streamIter.second;
if (sas)
{
sas->copyPcmTo(w, needed);
// Provide mirroring if needed
if (mMirror)
{
mMirrorBuffer.add(w.data(), w.filled());
if (!mMirrorPrebuffered)
mMirrorPrebuffered = mMirrorBuffer.filled() >= MT_MIRROR_PREBUFFER;
}
if (!(state() & (int)StreamState::Receiving))
w.zero(needed);
// Check if we do not need input from this stream
if (w.filled())
{
if (mDumpStreams.mStreamForRecordingIncoming)
{
if (mDumpStreams.mStreamForRecordingIncoming->isOpened())
mDumpStreams.mStreamForRecordingIncoming->write(w.data(), w.filled());
}
mixer.addPcm(this, streamIter.first, w, AUDIO_SAMPLERATE, false);
if (mMediaObserver)
localMixer.addPcm(this, streamIter.first, w, AUDIO_SAMPLERATE, false);
}
}
}
if (mMediaObserver)
{
localMixer.mixAndGetPcm(forObserver);
mMediaObserver->onMedia(forObserver.data(), forObserver.capacity(), MT::Stream::MediaDirection::Incoming, this, mMediaObserverTag);
}
}
void AudioStream::dataArrived(PDatagramSocket s, const void* buffer, int length, InternetAddress& source)
{
jrtplib::RTPIPv6Address addr6;
jrtplib::RTPIPv4Address addr4;
jrtplib::RTPExternalTransmissionInfo* info = dynamic_cast<jrtplib::RTPExternalTransmissionInfo*>(mRtpSession.GetTransmissionInfo());
assert(info);
// Drop RTP packets if stream is not receiving now; let RTCP go
if (!(state() & (int)StreamState::Receiving) && RtpHelper::isRtp(buffer, length))
{
ICELogMedia(<< "Stream is not allowed to receive RTP stream. Ignore the packet");
return;
}
// Copy incoming data to temp buffer to perform possible srtp unprotect
int receiveLength = length;
memcpy(mReceiveBuffer, buffer, length);
bool srtpResult;
if (mSrtpSession.active())
{
if (RtpHelper::isRtp(mReceiveBuffer, receiveLength))
srtpResult = mSrtpSession.unprotectRtp(mReceiveBuffer, &receiveLength);
else
srtpResult = mSrtpSession.unprotectRtcp(mReceiveBuffer, &receiveLength);
if (!srtpResult)
{
ICELogCritical(<<"Cannot decrypt SRTP packet.");
return;
}
}
switch (source.family())
{
case AF_INET:
addr4.SetIP(source.sockaddr4()->sin_addr.s_addr);
addr4.SetPort(source.port());
ICELogMedia(<< "Injecting RTP/RTCP packet into jrtplib");
info->GetPacketInjector()->InjectRTPorRTCP(mReceiveBuffer, receiveLength, addr4);
break;
case AF_INET6:
addr6.SetIP(source.sockaddr6()->sin6_addr);
addr6.SetPort(source.port());
ICELogMedia(<< "Injecting RTP/RTCP packet into jrtplib");
info->GetPacketInjector()->InjectRTPorRTCP(mReceiveBuffer, receiveLength, addr6);
break;
default:
assert(0);
}
mStat.mReceived += length;
if (RtpHelper::isRtp(mReceiveBuffer, receiveLength))
{
if (!mStat.mFirstRtpTime.is_initialized())
mStat.mFirstRtpTime = std::chrono::system_clock::now();
mStat.mReceivedRtp++;
}
else
mStat.mReceivedRtcp++;
mRtpSession.Poll(); // maybe it is extra with external transmitter
bool hasData = mRtpSession.GotoFirstSourceWithData();
while (hasData)
{
std::shared_ptr<jrtplib::RTPPacket> packet(mRtpSession.GetNextPacket());
if (packet)
{
ICELogMedia(<< "jrtplib returned packet");
// Find right handler for rtp stream
SingleAudioStream* rtpStream = nullptr;
AudioStreamMap::iterator streamIter = mStreamMap.find(packet->GetSSRC());
if (streamIter == mStreamMap.end())
mStreamMap[packet->GetSSRC()] = rtpStream = new SingleAudioStream(mCodecSettings, mStat);
else
rtpStream = streamIter->second;
// Process incoming data packet
rtpStream->process(packet);
double rtt = mRtpSession.GetCurrentSourceInfo()->INF_GetRoundtripTime().GetDouble();
if (rtt > 0)
mStat.mRttDelay.process(rtt);
}
hasData = mRtpSession.GotoNextSourceWithData();
}
}
void AudioStream::setState(unsigned state)
{
Stream::setState(state);
}
void AudioStream::setTelephoneCodec(int payloadType)
{
mRemoteTelephoneCodec = payloadType;
}
void AudioStream::setSocket(const RtpPair<PDatagramSocket>& socket)
{
Stream::setSocket(socket);
mRtpSender.setSocket(socket);
}
DtmfContext& AudioStream::queueOfDtmf()
{
return mDtmfContext;
}
void AudioStream::readFile(const Audio::PWavFileReader& stream, MediaDirection direction)
{
switch (direction)
{
case MediaDirection::Outgoing: mDumpStreams.mStreamForReadingOutgoing = stream; break;
case MediaDirection::Incoming: mDumpStreams.mStreamForReadingIncoming = stream; break;
}
}
void AudioStream::writeFile(const Audio::PWavFileWriter& writer, MediaDirection direction)
{
switch (direction)
{
case MediaDirection::Outgoing: mDumpStreams.mStreamForRecordingOutgoing = writer; break;
case MediaDirection::Incoming: mDumpStreams.mStreamForRecordingIncoming = writer; break;
}
}
void AudioStream::setupMirror(bool enable)
{
if (!mMirror && enable)
{
mMirrorBuffer.setCapacity(MT_MIRROR_CAPACITY);
mMirrorPrebuffered = false;
}
mMirror = enable;
}
void AudioStream::setFinalStatisticsOutput(Statistics* stats)
{
mFinalStatistics = stats;
}

View File

@@ -0,0 +1,110 @@
/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __MT_AUDIOSTREAM_H
#define __MT_AUDIOSTREAM_H
#include "../config.h"
#include "MT_Stream.h"
#include "MT_NativeRtpSender.h"
#include "MT_SingleAudioStream.h"
#include "MT_Dtmf.h"
#include "../helper/HL_VariantMap.h"
#include "../helper/HL_ByteBuffer.h"
#include "../helper/HL_NetworkSocket.h"
#include "../helper/HL_Rtp.h"
#include "../audio/Audio_DataWindow.h"
#include "../audio/Audio_Mixer.h"
#include "../audio/Audio_Resampler.h"
#include "ice/ICESync.h"
#include "jrtplib/src/rtpsession.h"
#include "jrtplib/src/rtpexternaltransmitter.h"
#include "audio/Audio_WavFile.h"
namespace MT
{
class AudioStream: public Stream
{
public:
AudioStream(const CodecList::Settings& codecSettings);
~AudioStream();
void setDestination(const RtpPair<InternetAddress>& dest) override;
//void setPacketTime(int packetTime);
void setTransmittingCodec(Codec::Factory& factory, int payloadType) override;
PCodec transmittingCodec();
// Called to queue data captured from microphone.
// Buffer holds 16bits PCM data with AUDIO_SAMPLERATE rate and AUDIO_CHANNELS channels.
void addData(const void* buffer, int length);
// Called to get data to speaker (or mixer)
void copyDataTo(Audio::Mixer& mixer, int needed);
// Called to process incoming rtp packet
void dataArrived(PDatagramSocket s, const void* buffer, int length, InternetAddress& source) override;
void setSocket(const RtpPair<PDatagramSocket>& socket) override;
void setState(unsigned state) override;
void setTelephoneCodec(int payloadType);
DtmfContext& queueOfDtmf();
void readFile(const Audio::PWavFileReader& stream, MediaDirection direction) override;
void writeFile(const Audio::PWavFileWriter& writer, MediaDirection direction) override;
void setupMirror(bool enable) override;
void setFinalStatisticsOutput(Statistics* stats);
protected:
Audio::DataWindow mCapturedAudio; // Data from microphone
Audio::DataWindow mStereoCapturedAudio;
char mIncomingPcmBuffer[AUDIO_MIC_BUFFER_SIZE]; // Temporary buffer to allow reading from file
char mResampleBuffer[AUDIO_MIC_BUFFER_SIZE*8]; // Temporary buffer to hold data
char mStereoBuffer[AUDIO_MIC_BUFFER_SIZE*8]; // Temporary buffer to hold data converted to stereo
PCodec mTransmittingCodec; // Current encoding codec
int mTransmittingPayloadType; // Payload type to mark outgoing packets
int mPacketTime; // Required packet time
char mFrameBuffer[MT_MAXAUDIOFRAME]; // Temporary buffer to hold results of encoder
ByteBuffer mEncodedAudio; // Encoded frame(s)
int mEncodedTime; // Time length of encoded audio
const CodecList::Settings& mCodecSettings; // Configuration for stream
Mutex mMutex; // Mutex
int mRemoteTelephoneCodec; // Payload for remote telephone codec
jrtplib::RTPSession mRtpSession; // Rtp session
jrtplib::RTPSession mRtpDtmfSession; // Rtp dtmf session
NativeRtpSender mRtpSender;
AudioStreamMap mStreamMap; // Map of media streams. Key is RTP's SSRC value.
Audio::DataWindow mOutputBuffer;
RtpDump* mRtpDump;
Audio::Resampler mCaptureResampler8,
mCaptureResampler16,
mCaptureResampler32,
mCaptureResampler48;
DtmfContext mDtmfContext;
char mReceiveBuffer[MAX_VALID_UDPPACKET_SIZE];
struct
{
Audio::PWavFileWriter mStreamForRecordingIncoming,
mStreamForRecordingOutgoing;
Audio::PWavFileReader mStreamForReadingIncoming,
mStreamForReadingOutgoing;
} mDumpStreams;
Audio::PWavFileWriter mSendingDump;
bool mMirror = false;
bool mMirrorPrebuffered = false;
Audio::DataWindow mMirrorBuffer;
Statistics* mFinalStatistics = nullptr;
bool decryptSrtp(void* data, int* len);
};
};
#endif

132
src/engine/media/MT_Box.cpp Normal file
View File

@@ -0,0 +1,132 @@
/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MT_Box.h"
#include "MT_AudioStream.h"
#include "MT_SrtpHelper.h"
#include "../helper/HL_VariantMap.h"
#include "../helper/HL_Exception.h"
#include "../helper/HL_StreamState.h"
#include "../helper/HL_Log.h"
#define LOG_SUBSYSTEM "MT::Box"
using namespace MT;
Terminal::Terminal(const CodecList::Settings& settings)
:mCodecList(settings)
{
ICELogDebug(<< "Opening Terminal instance");
// Alloc memory for captured audio
mCapturedAudio.setCapacity(AUDIO_MIC_BUFFER_COUNT * AUDIO_MIC_BUFFER_SIZE);
// Init srtp library
SrtpSession::initSrtp();
}
Terminal::~Terminal()
{
ICELogDebug(<< "Closing Terminal instance");
mAudioPair.reset();
}
PStream Terminal::createStream(int type, VariantMap& config)
{
PStream result;
switch (type)
{
case Stream::Audio:
result = PStream(new AudioStream(MT::CodecList::Settings::DefaultSettings));
mAudioList.add(result);
break;
case Stream::Video:
default:
throw Exception(ERR_NOT_IMPLEMENTED);
}
return result;
}
void Terminal::freeStream(PStream stream)
{
if (AudioStream* audio = dynamic_cast<AudioStream*>(stream.get()))
{
ICELogDebug(<< "Unregister audio stream from mixer.");
mAudioMixer.unregisterChannel(audio);
ICELogDebug(<< "Remove audio stream from list.");
mAudioList.remove(stream);
}
}
CodecList& Terminal::codeclist()
{
return mCodecList;
}
Audio::PDevicePair Terminal::audio()
{
return mAudioPair;
}
void Terminal::setAudio(const Audio::PDevicePair& audio)
{
mAudioPair = audio;
if (mAudioPair)
mAudioPair->setDelegate(this);
}
void Terminal::deviceChanged(Audio::DevicePair* dp)
{
}
void Terminal::onMicData(const Audio::Format& f, const void* buffer, int length)
{
//ICELogMedia(<< "Got " << length << " bytes from microphone");
// See if it is enough data to feed streams
mCapturedAudio.add(buffer, length);
if (mCapturedAudio.filled() < AUDIO_MIC_BUFFER_SIZE)
return;
StreamList sl;
mAudioList.copyTo(&sl);
// Iterate streams. See what of them requires microphone data.
for (int frameIndex=0; frameIndex < mCapturedAudio.filled() / AUDIO_MIC_BUFFER_SIZE; frameIndex++)
{
for (int i=0; i<sl.size(); i++)
{
if (AudioStream* stream = dynamic_cast<AudioStream*>(sl.streamAt(i).get()))
{
if (stream->state() & (int)StreamState::Sending)
stream->addData(mCapturedAudio.data(), AUDIO_MIC_BUFFER_SIZE);
}
}
mCapturedAudio.erase(AUDIO_MIC_BUFFER_SIZE);
}
}
void Terminal::onSpkData(const Audio::Format& f, void* buffer, int length)
{
ICELogMedia(<< "Speaker requests " << length << " bytes");
if (mAudioMixer.available() < length)
{
Lock l(mAudioList.getMutex());
for (int i=0; i<mAudioList.size(); i++)
{
if (AudioStream* stream = dynamic_cast<AudioStream*>(mAudioList.streamAt(i).get()))
{
//if (stream->state() & STATE_RECEIVING)
stream->copyDataTo(mAudioMixer, length - mAudioMixer.available());
}
}
}
mAudioMixer.getPcm(buffer, length);
}

47
src/engine/media/MT_Box.h Normal file
View File

@@ -0,0 +1,47 @@
/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __MT_BOX_H
#define __MT_BOX_H
#include <set>
#include "MT_Stream.h"
#include "MT_CodecList.h"
#include "../audio/Audio_Interface.h"
#include "../audio/Audio_DevicePair.h"
#include "../audio/Audio_Mixer.h"
#include "../helper/HL_VariantMap.h"
namespace MT
{
class Terminal: public Audio::DevicePair::Delegate
{
public:
Terminal(const CodecList::Settings& codecSettings);
~Terminal();
CodecList& codeclist();
PStream createStream(int type, VariantMap& config);
void freeStream(PStream s);
Audio::PDevicePair audio();
void setAudio(const Audio::PDevicePair& audio);
protected:
StreamList mAudioList;
std::mutex mAudioListMutex;
CodecList mCodecList;
Audio::PDevicePair mAudioPair;
Audio::Mixer mAudioMixer;
Audio::DataWindow mCapturedAudio;
void deviceChanged(Audio::DevicePair* dp);
void onMicData(const Audio::Format& f, const void* buffer, int length);
void onSpkData(const Audio::Format& f, void* buffer, int length);
};
}
#endif

View File

@@ -0,0 +1,181 @@
#include "../config.h"
#include "MT_CngHelper.h"
#include <stdlib.h>
#include <assert.h>
#include <stdlib.h>
#include <math.h>
#define NOISE_AMPL 8031
#ifndef TARGET_WIN
typedef short __int16;
typedef int __int32;
#endif
namespace MT
{
static int GenerateRandom(int maxvalue)
{
assert(maxvalue <= RAND_MAX);
int result = rand();
result = (int)(result / ((float)RAND_MAX / maxvalue) + 0.5);
if (result > maxvalue)
result = maxvalue;
return result;
}
class LPFilter
{
public:
static const int NCoef = 1;
static const int DCgain = 128;
__int16 ACoef[NCoef+1];
__int16 BCoef[NCoef+1];
__int32 y[NCoef+1] = {0, 0};
__int16 x[NCoef+1] = {0, 0};
LPFilter()
{
ACoef[0] = 16707; ACoef[1] = 16707;
BCoef[0] = 32767; BCoef[1] = -32511;
}
~LPFilter()
{
}
__int16 Do(__int16 sample)
{
int n;
//shift the old samples
for(n=NCoef; n>0; n--) {
x[n] = x[n-1];
y[n] = y[n-1];
}
//Calculate the new output
x[0] = sample;
y[0] = ACoef[0] * x[0];
for(n=1; n<=NCoef; n++)
y[0] += ACoef[n] * x[n] - BCoef[n] * y[n];
y[0] /= BCoef[0];
return y[0] / DCgain;
}
};
class HPFilter
{
public:
static const int NCoef = 1;
static const int DCgain = 128;
__int16 ACoef[NCoef+1];
__int16 BCoef[NCoef+1];
__int32 y[NCoef+1] = {0, 0};
__int16 x[NCoef+1] = {0, 0};
HPFilter()
{
ACoef[0] = 16384; ACoef[1] = -16384;
BCoef[0] = 32767; BCoef[1] = 0;
}
~HPFilter()
{
}
__int16 Do(__int16 sample)
{
int n;
//shift the old samples
for(n=NCoef; n>0; n--) {
x[n] = x[n-1];
y[n] = y[n-1];
}
//Calculate the new output
x[0] = sample;
y[0] = ACoef[0] * x[0];
for(n=1; n<=NCoef; n++)
y[0] += ACoef[n] * x[n] - BCoef[n] * y[n];
y[0] /= BCoef[0];
return y[0] / DCgain;
}
};
// --------------------- CngHelper ----------------------------
int CngHelper::parse3389(const void* buf, int size, int rate, int requestLengthInMilliseconds, short* output)
{
assert(buf);
assert(output);
if (!size || !requestLengthInMilliseconds)
return 0;
int outputSize = requestLengthInMilliseconds * (rate / 1000);
// Cast pointer to input data
const unsigned char* dataIn = (const unsigned char*)buf;
// Get noise level
unsigned char noiseLevel = *dataIn;
float linear = float(1.0 / noiseLevel ? noiseLevel : 1);
// Generate white noise for 16KHz sample rate
LPFilter lpf; HPFilter hpf;
for (int sampleIndex = 0; sampleIndex < outputSize; sampleIndex++)
{
output[sampleIndex] = GenerateRandom(NOISE_AMPL*2) - NOISE_AMPL;
output[sampleIndex] = lpf.Do(output[sampleIndex]);
output[sampleIndex] = hpf.Do(output[sampleIndex]);
output[sampleIndex] = (short)((float)output[sampleIndex] * linear);
}
return outputSize * 2;
}
// ------------------- CngDecoder --------------------
CngDecoder::CngDecoder()
{
WebRtcCng_CreateDec(&mContext);
if (mContext)
WebRtcCng_InitDec(mContext);
}
CngDecoder::~CngDecoder()
{
if (mContext)
WebRtcCng_FreeDec(mContext);
}
void CngDecoder::decode3389(const void *buf, int size)
{
if (mContext)
WebRtcCng_UpdateSid(mContext, (WebRtc_UWord8*)buf, size);
}
int CngDecoder::produce(int rate, int requestedMilliseconds, short *output, bool newPeriod)
{
WebRtcCng_Generate(mContext, output, requestedMilliseconds * rate / 1000, newPeriod ? 1 : 0);
return requestedMilliseconds * rate / 1000 * 2;
}
}

View File

@@ -0,0 +1,29 @@
#ifndef __MT_CNG_HELPER_H
#define __MT_CNG_HELPER_H
#include "webrtc/cng/webrtc_cng.h"
namespace MT
{
class CngHelper
{
public:
/* Parses RTP 3389 payload and produces CNG to audio buffer. Returns size of produced audio in bytes. */
static int parse3389(const void* buf, int size, int rate, int requestLengthInMilliseconds, short* output);
};
class CngDecoder
{
protected:
CNG_dec_inst* mContext;
public:
CngDecoder();
~CngDecoder();
void decode3389(const void* buf, int size);
int produce(int rate, int requestedMilliseconds, short* output, bool newPeriod);
};
}
#endif

View File

@@ -0,0 +1,41 @@
/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MT_Codec.h"
using namespace MT;
int Codec::Factory::channels()
{
return 1;
}
#ifdef USE_RESIP_INTEGRATION
void Codec::Factory::create(CodecMap& codecs)
{
codecs[payloadType()] = std::shared_ptr<Codec>(create());
}
void Codec::Factory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
{
codecs.push_back(resipCodec());
}
resip::Codec Codec::Factory::resipCodec()
{
resip::Codec c(this->name(), this->payloadType(), this->samplerate());
return c;
}
int Codec::Factory::processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
{
for (resip::SdpContents::Session::Medium::CodecContainer::const_iterator codecIter = codecs.begin(); codecIter != codecs.end(); ++codecIter)
{
if (resipCodec() == *codecIter)
return codecIter->payloadType();
}
return -1;
}
#endif

View File

@@ -0,0 +1,64 @@
/* Copyright(C) 2007-2016 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __MT_CODEC_H
#define __MT_CODEC_H
#ifdef USE_RESIP_INTEGRATION
# include "resiprocate/resip/stack/SdpContents.hxx"
#endif
#include "../helper/HL_Types.h"
#include <map>
#include "../helper/HL_Pointer.h"
namespace MT
{
class Codec;
typedef std::shared_ptr<Codec> PCodec;
class CodecMap: public std::map<int, PCodec>
{
};
class Codec
{
public:
class Factory
{
public:
virtual ~Factory() {}
virtual const char* name() = 0;
virtual int samplerate() = 0;
virtual int payloadType() = 0;
virtual PCodec create() = 0;
virtual int channels();
#ifdef USE_RESIP_INTEGRATION
typedef std::map<int, PCodec > CodecMap;
virtual void create(CodecMap& codecs);
virtual void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction);
// Returns payload type from chosen codec if success. -1 is returned for negative result.
virtual int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction);
resip::Codec resipCodec();
#endif
};
virtual ~Codec() {}
virtual const char* name() = 0;
virtual int samplerate() = 0;
virtual float timestampUnit() { return float(1.0 / samplerate()); }
virtual int pcmLength() = 0;
virtual int frameTime() = 0;
virtual int rtpLength() = 0;
virtual int channels() { return 1; }
virtual int encode(const void* input, int inputBytes, void* output, int outputCapacity) = 0;
virtual int decode(const void* input, int inputBytes, void* output, int outputCapacity) = 0;
virtual int plc(int lostFrames, void* output, int outputCapacity) = 0;
// Returns size of codec in memory
virtual int getSize() const { return 0; };
};
}
#endif

View File

@@ -0,0 +1,160 @@
/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "../config.h"
#include "MT_CodecList.h"
#include "MT_AudioCodec.h"
#include "MT_AmrCodec.h"
#include <algorithm>
using namespace MT;
CodecList::Settings CodecList::Settings::DefaultSettings;
CodecList::CodecList(const Settings& settings)
:mSettings(settings)
{
//mFactoryList.push_back(new OpusCodec::OpusFactory(16000, 1));
#ifdef USE_OPUS_CODEC
if (settings.mOpusSpec.empty())
{
mFactoryList.push_back(new OpusCodec::OpusFactory(48000, 2, MT_OPUS_CODEC_PT));
}
else
{
for (auto spec: settings.mOpusSpec)
{
mFactoryList.push_back(new OpusCodec::OpusFactory(spec.mRate, spec.mChannels, spec.mPayloadType));
}
}
#endif
#ifdef USE_AMR_CODEC
for (int pt: mSettings.mAmrWbPayloadType)
mFactoryList.push_back(new AmrWbCodec::CodecFactory({mSettings.mWrapIuUP, false, pt}));
for (int pt: mSettings.mAmrWbOctetPayloadType)
mFactoryList.push_back(new AmrWbCodec::CodecFactory({mSettings.mWrapIuUP, true, pt}));
for (int pt: mSettings.mAmrNbPayloadType)
mFactoryList.push_back(new AmrNbCodec::CodecFactory({mSettings.mWrapIuUP, false, pt}));
for (int pt: mSettings.mAmrNbOctetPayloadType)
mFactoryList.push_back(new AmrNbCodec::CodecFactory({mSettings.mWrapIuUP, true, pt}));
mFactoryList.push_back(new GsmEfrCodec::GsmEfrFactory(mSettings.mWrapIuUP, mSettings.mGsmEfrPayloadType));
#endif
//mFactoryList.push_back(new IsacCodec::IsacFactory16K(mSettings.mIsac16KPayloadType));
//mFactoryList.push_back(new IlbcCodec::IlbcFactory(mSettings.mIlbc20PayloadType, mSettings.mIlbc30PayloadType));
mFactoryList.push_back(new G711Codec::AlawFactory());
mFactoryList.push_back(new G711Codec::UlawFactory());
mFactoryList.push_back(new GsmCodec::GsmFactory(mSettings.mGsmFrPayloadLength == 32 ? GsmCodec::Type::Bytes_32 : GsmCodec::Type::Bytes_33, mSettings.mGsmFrPayloadType));
mFactoryList.push_back(new G722Codec::G722Factory());
mFactoryList.push_back(new G729Codec::G729Factory());
#ifndef TARGET_ANDROID
mFactoryList.push_back(new GsmHrCodec::GsmHrFactory(mSettings.mGsmHrPayloadType));
#endif
}
CodecList::~CodecList()
{
for (FactoryList::size_type i=0; i<mFactoryList.size(); i++)
delete mFactoryList[i];
}
int CodecList::count() const
{
return (int)mFactoryList.size();
}
Codec::Factory& CodecList::codecAt(int index) const
{
return *mFactoryList[index];
}
int CodecList::findCodec(const std::string &name) const
{
for (int i=0; i<count(); i++)
{
if (codecAt(i).name() == name)
return i;
}
return -1;
}
void CodecList::fillCodecMap(CodecMap& cm)
{
for (auto& factory: mFactoryList)
{
// Create codec here. Although they are not needed right now - they can be needed to find codec's info.
cm[factory->payloadType()] = factory->create();
}
}
CodecListPriority::CodecListPriority()
{
}
CodecListPriority::~CodecListPriority()
{
}
bool CodecListPriority::isNegativePriority(const CodecListPriority::Item& item)
{
return item.mPriority < 0;
}
bool CodecListPriority::compare(const Item& item1, const Item& item2)
{
return item1.mPriority < item2.mPriority;
}
void CodecListPriority::setupFrom(PVariantMap vmap)
{
CodecList::Settings settings;
CodecList cl(settings);
//mPriorityList.resize(cl.count());
bool emptyVmap = vmap ? vmap->empty() : true;
if (emptyVmap)
{
for (int i=0; i<cl.count(); i++)
{
Item item;
item.mCodecIndex = i;
item.mPriority = i;
mPriorityList.push_back(item);
}
}
else
{
for (int i=0; i<cl.count(); i++)
{
Item item;
item.mCodecIndex = i;
item.mPriority = vmap->exists(i) ? vmap->at(i).asInt() : -1;
mPriorityList.push_back(item);
}
// Remove -1 records
mPriorityList.erase(std::remove_if(mPriorityList.begin(), mPriorityList.end(), isNegativePriority), mPriorityList.end());
// Sort by priority
std::sort(mPriorityList.begin(), mPriorityList.end(), compare);
}
}
int CodecListPriority::count(const CodecList &cl) const
{
return mPriorityList.size();
}
Codec::Factory& CodecListPriority::codecAt(const CodecList& cl, int index) const
{
return cl.codecAt(mPriorityList[index].mCodecIndex);
}

View File

@@ -0,0 +1,95 @@
/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __MT_CODEC_LIST_H
#define __MT_CODEC_LIST_H
#include "../config.h"
#ifdef USE_RESIP_INTEGRATION
# include "resiprocate/resip/stack/SdpContents.hxx"
#endif
#include "MT_Codec.h"
#include <vector>
#include <set>
#include "../helper/HL_VariantMap.h"
#define ALL_CODECS_STRING "OPUS,ISAC,ILBC,PCMU,PCMA,G722,GSM"
namespace MT
{
class CodecList
{
public:
struct Settings
{
bool mWrapIuUP = false;
bool mSkipDecode = false;
// AMR payload types
std::set<int> mAmrWbPayloadType = { MT_AMRWB_PAYLOADTYPE };
std::set<int> mAmrNbPayloadType = { MT_AMRNB_PAYLOADTYPE };
std::set<int> mAmrWbOctetPayloadType = { MT_AMRWB_OCTET_PAYLOADTYPE };
std::set<int> mAmrNbOctetPayloadType = { MT_AMRNB_OCTET_PAYLOADTYPE };
bool isAmrWb(int ptype) const { return mAmrWbOctetPayloadType.count(ptype) > 0 || mAmrWbPayloadType.count(ptype) > 0; }
bool isAmrNb(int ptype) const { return mAmrNbOctetPayloadType.count(ptype) > 0 || mAmrNbPayloadType.count(ptype) > 0; }
int mIsac16KPayloadType = MT_ISAC16K_PAYLOADTYPE;
int mIsac32KPayloadType = MT_ISAC32K_PAYLOADTYPE;
int mIlbc20PayloadType = MT_ILBC20_PAYLOADTYPE;
int mIlbc30PayloadType = MT_ILBC30_PAYLOADTYPE;
int mGsmFrPayloadType = 3; // GSM is codec with fixed payload type. But sometimes it has to be overwritten.
int mGsmFrPayloadLength = 33; // Expected GSM payload length
int mGsmHrPayloadType = MT_GSMHR_PAYLOADTYPE;
int mGsmEfrPayloadType = MT_GSMEFR_PAYLOADTYPE;
struct OpusSpec
{
int mPayloadType = 0;
int mRate = 0;
int mChannels = 0;
};
std::vector<OpusSpec> mOpusSpec;
static Settings DefaultSettings;
};
CodecList(const Settings& settings);
~CodecList();
int count() const;
Codec::Factory& codecAt(int index) const;
int findCodec(const std::string& name) const;
void fillCodecMap(CodecMap& cm);
protected:
typedef std::vector<Codec::Factory*> FactoryList;
FactoryList mFactoryList;
Settings mSettings;
};
class CodecListPriority
{
public:
CodecListPriority();
~CodecListPriority();
void setupFrom(PVariantMap vmap);
int count(const CodecList& cl) const;
Codec::Factory& codecAt(const CodecList& cl, int index) const;
protected:
struct Item
{
int mCodecIndex;
int mPriority;
};
std::vector<Item> mPriorityList;
static bool isNegativePriority(const CodecListPriority::Item& item);
static bool compare(const Item& item1, const Item& item2);
};
}
#endif

View File

@@ -0,0 +1,889 @@
/* Copyright(C) 2007-2017 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "../config.h"
#include "MT_Dtmf.h"
#ifdef TARGET_WIN
# include <WinSock2.h>
#endif
#include <assert.h>
#include <math.h>
#include <memory.h>
using namespace MT;
void DtmfBuilder::buildRfc2833(int tone, int duration, int volume, bool endOfEvent, void* output)
{
assert(duration);
assert(output);
assert(tone);
unsigned char toneValue = 0;
if (tone >= '0' && tone <='9')
toneValue = tone - '0';
else
if (tone >= 'A' && tone <='D' )
toneValue = tone - 'A' + 12;
else
if (tone == '*')
toneValue = 10;
else
if (tone == '#')
toneValue = 11;
char* packet = (char*)output;
packet[0] = toneValue;
packet[1] = 1 | (volume << 2);
if (endOfEvent)
packet[1] |= 128;
else
packet[1] &= 127;
unsigned short durationValue = htons(duration);
memcpy(packet + 2, &durationValue, 2);
}
#pragma region Inband DTMF support
#include <math.h>
#ifndef TARGET_WIN
# include <ctype.h>
# if !defined(TARGET_ANDROID) && !defined(TARGET_WIN)
# include <xlocale.h>
# endif
#endif
static bool sineTabInit = false;
static double sinetab[1 << 11];
static inline double sine(unsigned int ptr)
{
return sinetab[ptr >> (32-11)];
}
#define TWOPI (2.0 * 3.14159265358979323846)
#define MAXSTR 512
#define SINEBITS 11
#define SINELEN (1 << SINEBITS)
#define TWO32 4294967296.0 /* 2^32 */
static double amptab[2] = { 8191.75, 16383.5 };
static inline int ifix(double x)
{
return (x >= 0.0) ? (int) (x+0.5) : (int) (x-0.5);
}
// given frequency f, return corresponding phase increment
static inline int phinc(double f)
{
return ifix(TWO32 * f / (double) AUDIO_SAMPLERATE);
}
static char dtmfSymbols[16] = {
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'A',
'B',
'C',
'D',
'*',
'#'
};
char PDTMFEncoder_DtmfChar(int i)
{
if (i < 16)
return dtmfSymbols[i];
else
return 0;
}
// DTMF frequencies as per http://www.commlinx.com.au/DTMF_frequencies.htm
static double dtmfFreqs[16][2] = {
{ 941.0, 1336.0 }, // 0
{ 697.0, 1209.0 }, // 1
{ 697.0, 1336.0 }, // 2
{ 697.0, 1477.0 }, // 3
{ 770.0, 1209.0 }, // 4
{ 770.0, 1336.0 }, // 5
{ 770.0, 1477.0 }, // 6
{ 852.0, 1209.0 }, // 7
{ 852.0, 1336.0 }, // 8
{ 852.0, 1477.0 }, // 9
{ 697.0, 1633.0 }, // A
{ 770.0, 1633.0 }, // B
{ 852.0, 1633.0 }, // C
{ 941.0, 1633.0 }, // D
{ 941.0, 1209.0 }, // *
{ 941.0, 1477.0 } // #
};
static Mutex LocalDtmfMutex;
void PDTMFEncoder_MakeSineTable()
{
Lock lock(LocalDtmfMutex);
if (!sineTabInit) {
for (int k = 0; k < SINELEN; k++) {
double th = TWOPI * (double) k / (double) SINELEN;
double v = sin(th);
sinetab[k] = v;
}
sineTabInit = true;
}
}
void PDTMFEncoder_AddTone(double f1, double f2, unsigned ms1, unsigned ms2, unsigned rate, short* result)
{
int ak = 0;
PDTMFEncoder_MakeSineTable();
int dataPtr = 0;
double amp = amptab[ak];
int phinc1 = phinc(f1), phinc2 = phinc(f2);
int ns1 = ms1 * (rate/1000);
int ns2 = ms2 * (rate/1000);
unsigned int ptr1 = 0, ptr2 = 0;
ptr1 += phinc1 * ns1;
ptr2 += phinc2 * ns1;
for (int n = ns1; n < ns2; n++) {
double val = amp * (sine(ptr1) + sine(ptr2));
int ival = ifix(val);
if (ival < -32768)
ival = -32768;
else if (val > 32767)
ival = 32767;
result[dataPtr++] = ival / 2;
ptr1 += phinc1;
ptr2 += phinc2;
}
}
void PDTMFEncoder_AddTone(char _digit, unsigned startTime, unsigned finishTime, unsigned rate, short* result)
{
char digit = (char)toupper(_digit);
if ('0' <= digit && digit <= '9')
digit = digit - '0';
else if ('A' <= digit && digit <= 'D')
digit = digit + 10 - 'A';
else if (digit == '*')
digit = 14;
else if (digit == '#')
digit = 15;
else
return ;
PDTMFEncoder_AddTone(dtmfFreqs[(int)digit][0], dtmfFreqs[(int)digit][1], startTime, finishTime, rate, result);
}
#pragma endregion
void DtmfBuilder::buildInband(int tone, int startTime, int finishTime, int rate, short* buf)
{
PDTMFEncoder_AddTone(tone, startTime, finishTime, rate, buf);
}
#pragma region DtmfContext
DtmfContext::DtmfContext()
:mType(Dtmf_Rfc2833)
{
}
DtmfContext::~DtmfContext()
{
}
void DtmfContext::setType(Type t)
{
mType = t;
}
DtmfContext::Type DtmfContext::type()
{
return mType;
}
void DtmfContext::startTone(int tone, int volume)
{
Lock l(mGuard);
// Stop current tone if needed
if (mQueue.size())
stopTone();
mQueue.push_back(Dtmf(tone, volume, 0));
}
void DtmfContext::stopTone()
{
Lock l(mGuard);
// Switch to "emit 3 terminating packets" mode
if (mQueue.size())
{
switch (mType)
{
case Dtmf_Rfc2833:
mQueue.front().mStopped = true;
mQueue.erase(mQueue.begin());
break;
case Dtmf_Inband:
if (!mQueue.front().mFinishCount)
mQueue.front().mFinishCount = MT_DTMF_END_PACKETS;
break;
}
}
}
void DtmfContext::queueTone(int tone, int volume, int duration)
{
Lock l(mGuard);
mQueue.push_back(Dtmf(tone, volume, duration));
}
void DtmfContext::clearAllTones()
{
Lock l(mGuard);
mQueue.clear();
}
bool DtmfContext::getInband(int milliseconds, int rate, ByteBuffer& output)
{
Lock l(mGuard);
if (!mQueue.size() || mType != Dtmf_Inband)
return false;
//
Dtmf& d = mQueue.front();
output.resize(milliseconds * rate / 1000 * 2);
DtmfBuilder::buildInband(d.mTone, d.mCurrentTime, d.mCurrentTime + milliseconds, rate, (short*)output.mutableData());
d.mCurrentTime += milliseconds;
return true;
}
bool DtmfContext::getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& stopPacket)
{
Lock l(mGuard);
if (!mQueue.size() || mType != Dtmf_Rfc2833)
return false;
Dtmf& d = mQueue.front();
// See if tone has enough duration to produce another packet
if (d.mDuration > 0)
{
// Emit rfc2833 packet
output.resize(4);
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData());
d.mDuration -= milliseconds;
if(d.mDuration <= 0)
d.mStopped = true;
}
else
if (!d.mStopped)
{
output.resize(4);
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData());
}
else
output.clear();
if (d.mStopped)
{
stopPacket.resize(4);
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, true, stopPacket.mutableData());
}
else
stopPacket.clear();
if (d.mStopped)
mQueue.erase(mQueue.begin());
return true;
}
typedef struct
{
float v2;
float v3;
float fac;
} goertzel_state_t;
#define MAX_DTMF_DIGITS 128
typedef struct
{
int hit1;
int hit2;
int hit3;
int hit4;
int mhit;
goertzel_state_t row_out[4];
goertzel_state_t col_out[4];
goertzel_state_t row_out2nd[4];
goertzel_state_t col_out2nd[4];
goertzel_state_t fax_tone;
goertzel_state_t fax_tone2nd;
float energy;
int current_sample;
char digits[MAX_DTMF_DIGITS + 1];
int current_digits;
int detected_digits;
int lost_digits;
int digit_hits[16];
int fax_hits;
} dtmf_detect_state_t;
typedef struct
{
float fac;
} tone_detection_descriptor_t;
void zap_goertzel_update(goertzel_state_t *s, int16_t x[], int samples);
float zap_goertzel_result (goertzel_state_t *s);
void zap_dtmf_detect_init(dtmf_detect_state_t *s);
int zap_dtmf_detect(dtmf_detect_state_t *s, int16_t amp[], int samples, int isradio);
int zap_dtmf_get(dtmf_detect_state_t *s, char *buf, int max);
DTMFDetector::DTMFDetector()
:mState(NULL)
{
mState = malloc(sizeof(dtmf_detect_state_t));
memset(mState, 0, sizeof(dtmf_detect_state_t));
zap_dtmf_detect_init((dtmf_detect_state_t*)mState);
}
DTMFDetector::~DTMFDetector()
{
if (mState)
free(mState);
}
std::string DTMFDetector::streamPut(unsigned char* samples, unsigned int size)
{
char buf[16]; buf[0] = 0;
if (zap_dtmf_detect((dtmf_detect_state_t*)mState, (int16_t*)samples, size/2, 0))
zap_dtmf_get((dtmf_detect_state_t*)mState, buf, 15);
return buf;
}
void DTMFDetector::resetState()
{
zap_dtmf_detect_init((dtmf_detect_state_t*)mState);
}
#ifndef TRUE
# define FALSE 0
# define TRUE (!FALSE)
#endif
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
//#define USE_3DNOW
/* Basic DTMF specs:
*
* Minimum tone on = 40ms
* Minimum tone off = 50ms
* Maximum digit rate = 10 per second
* Normal twist <= 8dB accepted
* Reverse twist <= 4dB accepted
* S/N >= 15dB will detect OK
* Attenuation <= 26dB will detect OK
* Frequency tolerance +- 1.5% will detect, +-3.5% will reject
*/
#define SAMPLE_RATE 8000.0
#define DTMF_THRESHOLD 8.0e7
#define FAX_THRESHOLD 8.0e7
#define FAX_2ND_HARMONIC 2.0 /* 4dB */
#define DTMF_NORMAL_TWIST 6.3 /* 8dB */
#define DTMF_REVERSE_TWIST ((isradio) ? 4.0 : 2.5) /* 4dB normal */
#define DTMF_RELATIVE_PEAK_ROW 6.3 /* 8dB */
#define DTMF_RELATIVE_PEAK_COL 6.3 /* 8dB */
#define DTMF_2ND_HARMONIC_ROW ((isradio) ? 1.7 : 2.5) /* 4dB normal */
#define DTMF_2ND_HARMONIC_COL 63.1 /* 18dB */
static tone_detection_descriptor_t dtmf_detect_row[4];
static tone_detection_descriptor_t dtmf_detect_col[4];
static tone_detection_descriptor_t dtmf_detect_row_2nd[4];
static tone_detection_descriptor_t dtmf_detect_col_2nd[4];
static tone_detection_descriptor_t fax_detect;
static tone_detection_descriptor_t fax_detect_2nd;
static float dtmf_row[] =
{
697.0, 770.0, 852.0, 941.0
};
static float dtmf_col[] =
{
1209.0, 1336.0, 1477.0, 1633.0
};
static float fax_freq = 1100.0;
static char dtmf_positions[] = "123A" "456B" "789C" "*0#D";
static void goertzel_init(goertzel_state_t *s,
tone_detection_descriptor_t *t)
{
s->v2 =
s->v3 = 0.0;
s->fac = t->fac;
}
/*- End of function --------------------------------------------------------*/
#if defined(USE_3DNOW)
static inline void _dtmf_goertzel_update(goertzel_state_t *s,
float x[],
int samples)
{
int n;
float v;
int i;
float vv[16];
vv[4] = s[0].v2;
vv[5] = s[1].v2;
vv[6] = s[2].v2;
vv[7] = s[3].v2;
vv[8] = s[0].v3;
vv[9] = s[1].v3;
vv[10] = s[2].v3;
vv[11] = s[3].v3;
vv[12] = s[0].fac;
vv[13] = s[1].fac;
vv[14] = s[2].fac;
vv[15] = s[3].fac;
//v1 = s->v2;
//s->v2 = s->v3;
//s->v3 = s->fac*s->v2 - v1 + x[0];
__asm__ __volatile__ (
" femms;\n"
" movq 16(%%edx),%%mm2;\n"
" movq 24(%%edx),%%mm3;\n"
" movq 32(%%edx),%%mm4;\n"
" movq 40(%%edx),%%mm5;\n"
" movq 48(%%edx),%%mm6;\n"
" movq 56(%%edx),%%mm7;\n"
" jmp 1f;\n"
" .align 32;\n"
" 1: ;\n"
" prefetch (%%eax);\n"
" movq %%mm3,%%mm1;\n"
" movq %%mm2,%%mm0;\n"
" movq %%mm5,%%mm3;\n"
" movq %%mm4,%%mm2;\n"
" pfmul %%mm7,%%mm5;\n"
" pfmul %%mm6,%%mm4;\n"
" pfsub %%mm1,%%mm5;\n"
" pfsub %%mm0,%%mm4;\n"
" movq (%%eax),%%mm0;\n"
" movq %%mm0,%%mm1;\n"
" punpckldq %%mm0,%%mm1;\n"
" add $4,%%eax;\n"
" pfadd %%mm1,%%mm5;\n"
" pfadd %%mm1,%%mm4;\n"
" dec %%ecx;\n"
" jnz 1b;\n"
" movq %%mm2,16(%%edx);\n"
" movq %%mm3,24(%%edx);\n"
" movq %%mm4,32(%%edx);\n"
" movq %%mm5,40(%%edx);\n"
" femms;\n"
:
: "c" (samples), "a" (x), "d" (vv)
: "memory", "eax", "ecx");
s[0].v2 = vv[4];
s[1].v2 = vv[5];
s[2].v2 = vv[6];
s[3].v2 = vv[7];
s[0].v3 = vv[8];
s[1].v3 = vv[9];
s[2].v3 = vv[10];
s[3].v3 = vv[11];
}
#endif
/*- End of function --------------------------------------------------------*/
void zap_goertzel_update(goertzel_state_t *s,
int16_t x[],
int samples)
{
int i;
float v1;
for (i = 0; i < samples; i++)
{
v1 = s->v2;
s->v2 = s->v3;
s->v3 = s->fac*s->v2 - v1 + x[i];
}
}
/*- End of function --------------------------------------------------------*/
float zap_goertzel_result (goertzel_state_t *s)
{
return s->v3*s->v3 + s->v2*s->v2 - s->v2*s->v3*s->fac;
}
/*- End of function --------------------------------------------------------*/
void zap_dtmf_detect_init (dtmf_detect_state_t *s)
{
int i;
float theta;
s->hit1 =
s->hit2 = 0;
for (i = 0; i < 4; i++)
{
theta = float(2.0*M_PI*(dtmf_row[i]/SAMPLE_RATE));
dtmf_detect_row[i].fac = float(2.0*cos(theta));
theta = float(2.0*M_PI*(dtmf_col[i]/SAMPLE_RATE));
dtmf_detect_col[i].fac = float(2.0*cos(theta));
theta = float(2.0*M_PI*(dtmf_row[i]*2.0/SAMPLE_RATE));
dtmf_detect_row_2nd[i].fac = float(2.0*cos(theta));
theta = float(2.0*M_PI*(dtmf_col[i]*2.0/SAMPLE_RATE));
dtmf_detect_col_2nd[i].fac = float(2.0*cos(theta));
goertzel_init (&s->row_out[i], &dtmf_detect_row[i]);
goertzel_init (&s->col_out[i], &dtmf_detect_col[i]);
goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
s->energy = 0.0;
}
/* Same for the fax dector */
theta = float(2.0*M_PI*(fax_freq/SAMPLE_RATE));
fax_detect.fac = float(2.0 * cos(theta));
goertzel_init (&s->fax_tone, &fax_detect);
/* Same for the fax dector 2nd harmonic */
theta = float(2.0*M_PI*(fax_freq * 2.0/SAMPLE_RATE));
fax_detect_2nd.fac = float(2.0 * cos(theta));
goertzel_init (&s->fax_tone2nd, &fax_detect_2nd);
s->current_sample = 0;
s->detected_digits = 0;
s->lost_digits = 0;
s->digits[0] = '\0';
s->mhit = 0;
}
/*- End of function --------------------------------------------------------*/
int zap_dtmf_detect (dtmf_detect_state_t *s,
int16_t amp[],
int samples,
int isradio)
{
float row_energy[4];
float col_energy[4];
float fax_energy;
float fax_energy_2nd;
float famp;
float v1;
int i;
int j;
int sample;
int best_row;
int best_col;
int hit;
int limit;
hit = 0;
for (sample = 0; sample < samples; sample = limit)
{
/* 102 is optimised to meet the DTMF specs. */
if ((samples - sample) >= (102 - s->current_sample))
limit = sample + (102 - s->current_sample);
else
limit = samples;
#if defined(USE_3DNOW)
_dtmf_goertzel_update (s->row_out, amp + sample, limit - sample);
_dtmf_goertzel_update (s->col_out, amp + sample, limit - sample);
_dtmf_goertzel_update (s->row_out2nd, amp + sample, limit2 - sample);
_dtmf_goertzel_update (s->col_out2nd, amp + sample, limit2 - sample);
/* XXX Need to fax detect for 3dnow too XXX */
#warning "Fax Support Broken"
#else
/* The following unrolled loop takes only 35% (rough estimate) of the
time of a rolled loop on the machine on which it was developed */
for (j = sample; j < limit; j++)
{
famp = amp[j];
s->energy += famp*famp;
/* With GCC 2.95, the following unrolled code seems to take about 35%
(rough estimate) as long as a neat little 0-3 loop */
v1 = s->row_out[0].v2;
s->row_out[0].v2 = s->row_out[0].v3;
s->row_out[0].v3 = s->row_out[0].fac*s->row_out[0].v2 - v1 + famp;
v1 = s->col_out[0].v2;
s->col_out[0].v2 = s->col_out[0].v3;
s->col_out[0].v3 = s->col_out[0].fac*s->col_out[0].v2 - v1 + famp;
v1 = s->row_out[1].v2;
s->row_out[1].v2 = s->row_out[1].v3;
s->row_out[1].v3 = s->row_out[1].fac*s->row_out[1].v2 - v1 + famp;
v1 = s->col_out[1].v2;
s->col_out[1].v2 = s->col_out[1].v3;
s->col_out[1].v3 = s->col_out[1].fac*s->col_out[1].v2 - v1 + famp;
v1 = s->row_out[2].v2;
s->row_out[2].v2 = s->row_out[2].v3;
s->row_out[2].v3 = s->row_out[2].fac*s->row_out[2].v2 - v1 + famp;
v1 = s->col_out[2].v2;
s->col_out[2].v2 = s->col_out[2].v3;
s->col_out[2].v3 = s->col_out[2].fac*s->col_out[2].v2 - v1 + famp;
v1 = s->row_out[3].v2;
s->row_out[3].v2 = s->row_out[3].v3;
s->row_out[3].v3 = s->row_out[3].fac*s->row_out[3].v2 - v1 + famp;
v1 = s->col_out[3].v2;
s->col_out[3].v2 = s->col_out[3].v3;
s->col_out[3].v3 = s->col_out[3].fac*s->col_out[3].v2 - v1 + famp;
v1 = s->col_out2nd[0].v2;
s->col_out2nd[0].v2 = s->col_out2nd[0].v3;
s->col_out2nd[0].v3 = s->col_out2nd[0].fac*s->col_out2nd[0].v2 - v1 + famp;
v1 = s->row_out2nd[0].v2;
s->row_out2nd[0].v2 = s->row_out2nd[0].v3;
s->row_out2nd[0].v3 = s->row_out2nd[0].fac*s->row_out2nd[0].v2 - v1 + famp;
v1 = s->col_out2nd[1].v2;
s->col_out2nd[1].v2 = s->col_out2nd[1].v3;
s->col_out2nd[1].v3 = s->col_out2nd[1].fac*s->col_out2nd[1].v2 - v1 + famp;
v1 = s->row_out2nd[1].v2;
s->row_out2nd[1].v2 = s->row_out2nd[1].v3;
s->row_out2nd[1].v3 = s->row_out2nd[1].fac*s->row_out2nd[1].v2 - v1 + famp;
v1 = s->col_out2nd[2].v2;
s->col_out2nd[2].v2 = s->col_out2nd[2].v3;
s->col_out2nd[2].v3 = s->col_out2nd[2].fac*s->col_out2nd[2].v2 - v1 + famp;
v1 = s->row_out2nd[2].v2;
s->row_out2nd[2].v2 = s->row_out2nd[2].v3;
s->row_out2nd[2].v3 = s->row_out2nd[2].fac*s->row_out2nd[2].v2 - v1 + famp;
v1 = s->col_out2nd[3].v2;
s->col_out2nd[3].v2 = s->col_out2nd[3].v3;
s->col_out2nd[3].v3 = s->col_out2nd[3].fac*s->col_out2nd[3].v2 - v1 + famp;
v1 = s->row_out2nd[3].v2;
s->row_out2nd[3].v2 = s->row_out2nd[3].v3;
s->row_out2nd[3].v3 = s->row_out2nd[3].fac*s->row_out2nd[3].v2 - v1 + famp;
/* Update fax tone */
v1 = s->fax_tone.v2;
s->fax_tone.v2 = s->fax_tone.v3;
s->fax_tone.v3 = s->fax_tone.fac*s->fax_tone.v2 - v1 + famp;
v1 = s->fax_tone.v2;
s->fax_tone2nd.v2 = s->fax_tone2nd.v3;
s->fax_tone2nd.v3 = s->fax_tone2nd.fac*s->fax_tone2nd.v2 - v1 + famp;
}
#endif
s->current_sample += (limit - sample);
if (s->current_sample < 102)
continue;
/* Detect the fax energy, too */
fax_energy = zap_goertzel_result(&s->fax_tone);
/* We are at the end of a DTMF detection block */
/* Find the peak row and the peak column */
row_energy[0] = zap_goertzel_result (&s->row_out[0]);
col_energy[0] = zap_goertzel_result (&s->col_out[0]);
for (best_row = best_col = 0, i = 1; i < 4; i++)
{
row_energy[i] = zap_goertzel_result (&s->row_out[i]);
if (row_energy[i] > row_energy[best_row])
best_row = i;
col_energy[i] = zap_goertzel_result (&s->col_out[i]);
if (col_energy[i] > col_energy[best_col])
best_col = i;
}
hit = 0;
/* Basic signal level test and the twist test */
if (row_energy[best_row] >= DTMF_THRESHOLD
&&
col_energy[best_col] >= DTMF_THRESHOLD
&&
col_energy[best_col] < row_energy[best_row]*DTMF_REVERSE_TWIST
&&
col_energy[best_col]*DTMF_NORMAL_TWIST > row_energy[best_row])
{
/* Relative peak test */
for (i = 0; i < 4; i++)
{
if ((i != best_col && col_energy[i]*DTMF_RELATIVE_PEAK_COL > col_energy[best_col])
||
(i != best_row && row_energy[i]*DTMF_RELATIVE_PEAK_ROW > row_energy[best_row]))
{
break;
}
}
/* ... and second harmonic test */
if (i >= 4
&&
(row_energy[best_row] + col_energy[best_col]) > 42.0*s->energy
&&
zap_goertzel_result (&s->col_out2nd[best_col])*DTMF_2ND_HARMONIC_COL < col_energy[best_col]
&&
zap_goertzel_result (&s->row_out2nd[best_row])*DTMF_2ND_HARMONIC_ROW < row_energy[best_row])
{
hit = dtmf_positions[(best_row << 2) + best_col];
/* Look for two successive similar results */
/* The logic in the next test is:
We need two successive identical clean detects, with
something different preceeding it. This can work with
back to back differing digits. More importantly, it
can work with nasty phones that give a very wobbly start
to a digit. */
if (hit == s->hit3 && s->hit3 != s->hit2)
{
s->mhit = hit;
s->digit_hits[(best_row << 2) + best_col]++;
s->detected_digits++;
if (s->current_digits < MAX_DTMF_DIGITS)
{
s->digits[s->current_digits++] = hit;
s->digits[s->current_digits] = '\0';
}
else
{
s->lost_digits++;
}
}
}
}
if (!hit && (fax_energy >= FAX_THRESHOLD) && (fax_energy > s->energy * 21.0)) {
fax_energy_2nd = zap_goertzel_result(&s->fax_tone2nd);
if (fax_energy_2nd * FAX_2ND_HARMONIC < fax_energy) {
#if 0
printf("Fax energy/Second Harmonic: %f/%f\n", fax_energy, fax_energy_2nd);
#endif
/* XXX Probably need better checking than just this the energy XXX */
hit = 'f';
s->fax_hits++;
} /* Don't reset fax hits counter */
} else {
if (s->fax_hits > 5) {
s->mhit = 'f';
s->detected_digits++;
if (s->current_digits < MAX_DTMF_DIGITS)
{
s->digits[s->current_digits++] = hit;
s->digits[s->current_digits] = '\0';
}
else
{
s->lost_digits++;
}
}
s->fax_hits = 0;
}
s->hit1 = s->hit2;
s->hit2 = s->hit3;
s->hit3 = hit;
/* Reinitialise the detector for the next block */
for (i = 0; i < 4; i++)
{
goertzel_init (&s->row_out[i], &dtmf_detect_row[i]);
goertzel_init (&s->col_out[i], &dtmf_detect_col[i]);
goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
}
goertzel_init (&s->fax_tone, &fax_detect);
goertzel_init (&s->fax_tone2nd, &fax_detect_2nd);
s->energy = 0.0;
s->current_sample = 0;
}
if ((!s->mhit) || (s->mhit != hit))
{
s->mhit = 0;
return(0);
}
return (hit);
}
/*- End of function --------------------------------------------------------*/
int zap_dtmf_get (dtmf_detect_state_t *s,
char *buf,
int max)
{
if (max > s->current_digits)
max = s->current_digits;
if (max > 0)
{
memcpy (buf, s->digits, max);
memmove (s->digits, s->digits + max, s->current_digits - max);
s->current_digits -= max;
}
buf[max] = '\0';
return max;
}

View File

@@ -0,0 +1,97 @@
/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MT_DTMF
#define MT_DTMF
#include "../config.h"
#include <vector>
#include <string>
#include "../helper/HL_ByteBuffer.h"
#include "../helper/HL_Sync.h"
namespace MT
{
class DtmfBuilder
{
public:
// Output should be 4 bytes length
static void buildRfc2833(int tone, int duration, int volume, bool endOfEvent, void* output);
// Buf receives PCM audio
static void buildInband(int tone, int startTime, int finishTime, int rate, short* buf);
};
struct Dtmf
{
Dtmf(): mTone(0), mDuration(0), mVolume(0), mFinishCount(3), mCurrentTime(0), mStopped(false) {}
Dtmf(int tone, int volume, int duration): mTone(tone), mVolume(volume), mDuration(duration), mFinishCount(3), mCurrentTime(0), mStopped(false) {}
int mStopped;
int mTone;
int mDuration; // It is zero for tones generated by startTone()..stopTone() calls.
int mVolume;
int mFinishCount;
int mCurrentTime;
};
typedef std::vector<Dtmf> DtmfQueue;
class DtmfContext
{
public:
enum Type
{
Dtmf_Inband,
Dtmf_Rfc2833
};
DtmfContext();
~DtmfContext();
void setType(Type t);
Type type();
void startTone(int tone, int volume);
void stopTone();
void queueTone(int tone, int volume, int duration);
void clearAllTones();
// Returns true if result was sent to output. Output will be resized automatically.
bool getInband(int milliseconds, int rate, ByteBuffer& output);
bool getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& stopPacket);
protected:
Mutex mGuard;
Type mType;
DtmfQueue mQueue;
};
class DTMFDetector
{
public:
/*! The default constructor. Allocates space for detector context. */
DTMFDetector();
/*! The destructor. Free the detector context's memory. */
~DTMFDetector();
/*! This method receives the input PCM 16-bit data and returns found DTMF event(s) in string representation.
* @param samples Input PCM buffer pointer.
* @param size Size of input buffer in bytes
* @return Found DTMF event(s) in string representation. The returned value has variable length.
*/
std::string streamPut(unsigned char* samples, unsigned int size);
void resetState();
protected:
void* mState; /// DTMF detector context
};
}
#endif

View File

@@ -0,0 +1,121 @@
/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MT_NativeRtpSender.h"
#include <assert.h>
using namespace MT;
NativeRtpSender::NativeRtpSender(Statistics& stat)
:mDumpWriter(NULL), mStat(stat), mSrtpSession(NULL)
{
}
NativeRtpSender::~NativeRtpSender()
{
}
bool NativeRtpSender::SendRTP(const void *data, size_t len)
{
if (mTarget.mRtp.isEmpty() || !mSocket.mRtp)
return false;
if (mDumpWriter)
mDumpWriter->add(data, len);
// Copy data to intermediary buffer bigger that original
int sendLength = len;
memcpy(mSendBuffer, data, len);
// Encrypt SRTP if needed
if (mSrtpSession)
{
if (mSrtpSession->active())
{
if (!mSrtpSession->protectRtp(mSendBuffer, &sendLength))
return false;
}
}
mSocket.mRtp->sendDatagram(mTarget.mRtp, mSendBuffer, sendLength);
mStat.mSentRtp++;
mStat.mSent += len;
return true;
}
/** This member function will be called when an RTCP packet needs to be transmitted. */
bool NativeRtpSender::SendRTCP(const void *data, size_t len)
{
if (mTarget.mRtp.isEmpty() || !mSocket.mRtcp)
return false;
// Copy data to intermediary buffer bigger that original
int sendLength = len;
memcpy(mSendBuffer, data, len);
// Encrypt SRTP if needed
if (mSrtpSession)
{
if (mSrtpSession->active())
{
if (!mSrtpSession->protectRtcp(mSendBuffer, &sendLength))
return false;
}
}
mSocket.mRtcp->sendDatagram(mTarget.mRtcp, mSendBuffer, sendLength);
mStat.mSentRtcp++;
mStat.mSent += len;
return true;
}
/** Used to identify if an RTPAddress instance originated from this sender (to be able to detect own packets). */
bool NativeRtpSender::ComesFromThisSender(const jrtplib::RTPAddress *a)
{
return false;
}
void NativeRtpSender::setDestination(RtpPair<InternetAddress> target)
{
mTarget = target;
}
RtpPair<InternetAddress> NativeRtpSender::destination()
{
return mTarget;
}
void NativeRtpSender::setSocket(const RtpPair<PDatagramSocket>& socket)
{
mSocket = socket;
}
RtpPair<PDatagramSocket>& NativeRtpSender::socket()
{
return mSocket;
}
void NativeRtpSender::setDumpWriter(RtpDump *dump)
{
mDumpWriter = dump;
}
RtpDump* NativeRtpSender::dumpWriter()
{
return mDumpWriter;
}
void NativeRtpSender::setSrtpSession(SrtpSession* srtp)
{
mSrtpSession = srtp;
}
SrtpSession* NativeRtpSender::srtpSession()
{
return mSrtpSession;
}

View File

@@ -0,0 +1,58 @@
/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __MT_NATIVE_RTP_SENDER_H
#define __MT_NATIVE_RTP_SENDER_H
#include "../config.h"
#include "jrtplib/src/rtpexternaltransmitter.h"
#include "srtp/include/srtp.h"
#include "../helper/HL_NetworkSocket.h"
#include "../helper/HL_InternetAddress.h"
#include "../helper/HL_Rtp.h"
#include "../helper/HL_SocketHeap.h"
#include "MT_Stream.h"
#include "MT_SrtpHelper.h"
namespace MT
{
class NativeRtpSender: public jrtplib::RTPExternalSender
{
public:
NativeRtpSender(Statistics& stat);
~NativeRtpSender();
/** This member function will be called when RTP data needs to be transmitted. */
bool SendRTP(const void *data, size_t len);
/** This member function will be called when an RTCP packet needs to be transmitted. */
bool SendRTCP(const void *data, size_t len);
/** Used to identify if an RTPAddress instance originated from this sender (to be able to detect own packets). */
bool ComesFromThisSender(const jrtplib::RTPAddress *a);
void setDestination(RtpPair<InternetAddress> destination);
RtpPair<InternetAddress> destination();
void setSocket(const RtpPair<PDatagramSocket>& socket);
RtpPair<PDatagramSocket>& socket();
void setDumpWriter(RtpDump* dump);
RtpDump* dumpWriter();
void setSrtpSession(SrtpSession* srtp);
SrtpSession* srtpSession();
protected:
RtpPair<PDatagramSocket> mSocket;
RtpPair<InternetAddress> mTarget;
Statistics& mStat;
RtpDump* mDumpWriter;
SrtpSession* mSrtpSession;
char mSendBuffer[MAX_VALID_UDPPACKET_SIZE];
};
}
#endif

View File

@@ -0,0 +1,919 @@
//#include "config.h"
#include "MT_SevanaMos.h"
#if defined(USE_PVQA_LIBRARY)
#if defined(PVQA_SERVER)
# include <boost/filesystem.hpp>
# include <boost/algorithm/string.hpp>
using namespace boost::filesystem;
#endif
#include "../engine/helper/HL_Log.h"
#include "../engine/helper/HL_CsvReader.h"
#include "../engine/helper/HL_String.h"
#include "../engine/audio/Audio_WavFile.h"
#include <assert.h>
#include <fstream>
#include <streambuf>
#include <iostream>
#include <atomic>
#if defined(PVQA_SERVER)
extern std::string IntervalCacheDir;
#endif
#define LOG_SUBSYSTEM "Sevana"
#ifdef WIN32
# define popen _popen
# define pclose _pclose
#endif
#define PVQA_ECHO_DETECTOR_NAME "ECHO"
//#define PVQA_ECHO_DETECTOR_NAME "EchoM-00"
namespace MT {
#if !defined(MOS_BEST_COLOR)
# define MOS_BEST_COLOR 0x11FF11
# define MOS_BAD_COLOR 0x000000
#endif
static std::string execCommand(const char* cmd)
{
std::shared_ptr<FILE> pipe(popen(cmd, "r"), pclose);
if (!pipe) return "ERROR";
char buffer[128];
std::string result = "";
while (!feof(pipe.get()))
{
if (fgets(buffer, 128, pipe.get()) != NULL)
result += buffer;
}
return result;
}
void SevanaMosUtility::run(const std::string& pcmPath, const std::string& intervalPath,
std::string& estimation, std::string& intervals)
{
#if defined(PVQA_SERVER)
path sevana = current_path() / "sevana";
#if defined(TARGET_LINUX) || defined(TARGET_OSX)
path exec = sevana / "pvqa";
#else
path exec = sevana / "pvqa.exe";
#endif
path lic = sevana / "pvqa.lic";
path cfg = sevana / "settings.cfg";
estimation.clear();
char cmdbuffer[1024];
sprintf(cmdbuffer, "%s %s analysis %s %s %s 0.799", exec.string().c_str(), lic.string().c_str(),
intervalPath.c_str(), cfg.string().c_str(), pcmPath.c_str());
std::string output = execCommand(cmdbuffer);
//ICELogDebug(<< "Got PVQA analyzer output: " << output);
std::string line;
std::istringstream is(output);
while (std::getline(is, line))
{
std::string::size_type mosPosition = line.find("MOS = ");
if ( mosPosition != std::string::npos)
{
estimation = line.substr(mosPosition + 6);
boost::algorithm::trim(estimation);
}
}
if (!estimation.size())
{
// Dump utility output if estimation failed
ICELogCritical(<< "PVQA failed with message: " << output);
return;
}
// Read intervals report file
if (boost::filesystem::exists(intervalPath) && !estimation.empty())
{
std::ifstream t(intervalPath);
std::string str((std::istreambuf_iterator<char>(t)),
std::istreambuf_iterator<char>());
intervals = str;
}
#endif
}
float getSevanaMos(const std::string& audioPath, const std::string& intervalReportPath,
std::string& intervalReport)
{
// Find Sevana MOS estimation
ICELogDebug( << "Running MOS utitlity on resulted PCM file " << audioPath );
try
{
std::string buffer;
SevanaMosUtility::run(audioPath, intervalReportPath, buffer, intervalReport);
ICELogDebug( << "MOS utility is finished on PCM file " << audioPath );
return (float)atof(buffer.c_str());
}
catch(std::exception& e)
{
ICELogCritical( << "MOS utility failed on PCM file " << audioPath << ". Error msg: " << e.what() );
return 0.0;
}
}
// ------------------- SevanaPVQA -------------------
void* SevanaPVQA::mLibraryConfiguration = nullptr;
int SevanaPVQA::mLibraryErrorCode = 0;
std::atomic_int SevanaPVQA::mInstanceCounter;
std::atomic_uint_least64_t SevanaPVQA::mAllProcessedMilliseconds;
bool SevanaPVQA::mPvqaLoaded = false;
std::string SevanaPVQA::getVersion()
{
return PVQA_GetVersion();
}
#if defined(TARGET_ANDROID)
void SevanaPVQA::setupAndroidEnvironment(void *environment, void *appcontext)
{
PVQA_SetupAndroidEnvironment(environment, appcontext);
}
#endif
bool SevanaPVQA::initializeLibrary(const std::string& pathToLicenseFile, const std::string& pathToConfigFile)
{
mPvqaLoaded = false;
ICELogInfo(<< "Sevana PVQA is about to be initialized.");
// Initialize PVQA library
if (!mLibraryConfiguration)
{
mInstanceCounter = 0;
mLibraryErrorCode = PVQA_InitLib(const_cast<char*>(pathToLicenseFile.c_str()));
if (mLibraryErrorCode)
{
ICELogCritical(<< "Problem when initializing PVQA library. Error code: " << mLibraryErrorCode
<< ". Path to license file is " << pathToLicenseFile
<< ". Path to config file is " << pathToConfigFile);
return false;
}
mLibraryConfiguration = PVQA_LoadCFGFile(const_cast<char*>(pathToConfigFile.c_str()), &mLibraryErrorCode);
if (!mLibraryConfiguration)
{
PVQA_ReleaseLib();
ICELogCritical(<< "Problem with PVQA configuration file.");
return false;
}
mPvqaLoaded = true;
}
return true;
}
bool SevanaPVQA::isInitialized()
{
return mPvqaLoaded;
}
int SevanaPVQA::getLibraryError()
{
return mLibraryErrorCode;
}
void SevanaPVQA::releaseLibrary()
{
PVQA_ReleaseLib();
}
SevanaPVQA::SevanaPVQA()
{
}
SevanaPVQA::~SevanaPVQA()
{
close();
}
void SevanaPVQA::open(double interval, Model model)
{
if (!isInitialized())
{
ICELogCritical(<< "PVQA library is not initialized.");
return;
}
if (mOpenFailed)
{
ICELogCritical(<< "Open failed already, reject this attempt.");
return;
}
if (mContext)
{
ICELogCritical(<< "Already opened (context is not nullptr).");
return;
}
ICELogDebug(<<"Attempt to create PVQA instance.");
mProcessedSamples = 0;
mModel = model;
mIntervalLength = interval;
mAudioLineInitialized = false;
mContext = PVQA_CreateAudioQualityAnalyzer(mLibraryConfiguration);
if (!mContext)
{
ICELogCritical(<< "Failed to create PVQA instance. Instance counter: " << mInstanceCounter);
mOpenFailed = true;
return;
}
mInstanceCounter++;
int rescode = 0;
rescode = PVQA_AudioQualityAnalyzerSetIntervalLength(mContext, interval);
if (rescode)
{
ICELogCritical(<< "Failed to set interval length on PVQA instance. Result code: " << rescode);
close();
mOpenFailed = true;
return;
}
if (mModel == Model::Stream)
{
rescode = PVQA_OnStartStreamData(mContext);
if (rescode)
{
ICELogCritical(<< "Failed to start streaming analysis on PVQA instance. Result code: " << rescode);
close();
mOpenFailed = true;
return;
}
}
ICELogDebug(<<"PVQA instance is created. Instance counter: " << mInstanceCounter);
}
void SevanaPVQA::close()
{
if (mContext)
{
ICELogDebug(<< "Attempt to destroy PVQA instance.");
PVQA_ReleaseAudioQualityAnalyzer(mContext);
mInstanceCounter--;
ICELogDebug(<< "PVQA instance destroyed. Current instance counter: " << mInstanceCounter);
mContext = nullptr;
mOpenFailed = false;
}
}
bool SevanaPVQA::isOpen() const
{
return mContext != nullptr;
}
void SevanaPVQA::update(int samplerate, int channels, const void *pcmBuffer, int pcmLength)
{
if (!mContext)
{
ICELogCritical(<< "No PVQA context.");
return;
}
// Model is assert here as it can be any if context is not created.
assert (mModel == Model::Stream);
TPVQA_AudioItem item;
item.dNChannels = channels;
item.dSampleRate = samplerate;
item.dNSamples = pcmLength / 2 / channels;
item.pSamples = (short*)pcmBuffer;
int rescode = PVQA_OnAddStreamAudioData(mContext, &item);
if (rescode)
{
ICELogCritical(<< "Failed to stream data to PVQA instance. Result code: " << rescode);
}
int milliseconds = pcmLength / 2 / channels / (samplerate / 1000);
mProcessedMilliseconds += milliseconds;
mAllProcessedMilliseconds += milliseconds;
}
SevanaPVQA::DetectorsList SevanaPVQA::getDetectorsNames(const std::string& report)
{
DetectorsList result;
if (!report.empty())
{
std::istringstream iss(report);
CsvReader reader(iss);
reader.readLine(result.mNames);
result.mStartIndex = 2;
// Remove first columns
if (result.mStartIndex < (int)result.mNames.size() - 1)
{
result.mNames.erase(result.mNames.begin(), result.mNames.begin() + result.mStartIndex);
// Remove last column
result.mNames.erase(result.mNames.begin() + result.mNames.size() - 1);
for (auto& name: result.mNames)
name = StringHelper::trim(name);
}
}
return result;
}
float SevanaPVQA::getResults(std::string& report, const EchoData** echo, int samplerate, Codec codec)
{
if (!mContext)
{
ICELogCritical(<< "No PVQA context.");
return 0.0;
}
if (mModel == Model::Stream)
{
if (mProcessedMilliseconds == 0)
{
ICELogCritical(<< "No audio in PVQA.");
return -1;
}
if (PVQA_OnFinalizeStream(mContext, (long)samplerate))
{
ICELogCritical(<< "Failed to finalize results from PVQA.");
return -1;
}
ICELogInfo(<< "Processed " << mProcessedMilliseconds << " milliseconds.");
}
TPVQA_Results results;
if (PVQA_FillQualityResultsStruct(mContext, &results))
{
ICELogCritical(<< "Failed to get results from PVQA.");
return -1;
}
int reportLength = PVQA_GetQualityStringSize(mContext);
if (reportLength)
{
char* buffer = (char*)alloca(reportLength + 1);
if (PVQA_FillQualityString(mContext, buffer))
{
ICELogCritical(<< "Failed to fill intervals report.");
}
else
report = buffer;
}
#if defined(TARGET_LINUX) && defined(PVQA_WITH_ECHO_DATA)
if (mModel == SevanaPVQA::Model::Stream && echo)
{
// Return echo detector counters
// Get list of names for echo detector - for debugging only
std::vector<std::string> names;
int errCode = 0;
const char** iNames = (const char **)PVQA_GetProcessorValuesNamesList(mContext, PVQA_ECHO_DETECTOR_NAME, &errCode);
if (!errCode && iNames)
{
int nameIndex = 0;
for(const char * locName = iNames[nameIndex]; locName; locName = iNames[++nameIndex])
names.push_back(locName);
// Get values for echo detector
*echo = PVQA_GetProcessorValuesList(mContext, PVQA_ECHO_DETECTOR_NAME, 0, mProcessedMilliseconds, "values", &errCode);
// For debugging only
/*if (*echo)
{
for (const auto& row: **echo)
{
std::cout << "<";
for (const auto& v: row)
std::cout << v << " ";
std::cout << ">" << std::endl;
}
}*/
// No need to delete maxValues - it will be deleted on PVQA analyzer context freeing.
}
}
#endif
// Limit maximal value of MOS depending on codec
float result = (float)results.dMOSLike;
float mv = 5.0;
switch (codec)
{
case Codec::G711: mv = 4.1f; break;
case Codec::G729: mv = 3.92f; break;
default:
mv = 5.0;
}
return std::min(result, mv);
}
void SevanaPVQA::setPathToDumpFile(const std::string& path)
{
mDumpWavPath = path;
}
float SevanaPVQA::process(int samplerate, int channels, const void *pcmBuffer, int pcmLength, std::string &report, Codec codec)
{
//std::cout << "Sent " << pcmLength << " bytes of audio to analyzer." << std::endl;
assert (mModel == Model::Interval);
if (!mContext)
return 0.0;
/*if (!mAudioLineInitialized)
{
mAudioLineInitialized = true;
if (PVQA_AudioQualityAnalyzerCreateDelayLine(mContext, samplerate, channels, 20))
ICELogCritical(<< "Failed to create delay line.");
}*/
TPVQA_AudioItem item;
item.dNChannels = channels;
item.dSampleRate = samplerate;
item.dNSamples = pcmLength / 2 / channels;
item.pSamples = (short*)pcmBuffer;
//std::cout << "Sending chunk of audio with rate = " << samplerate << ", channels = " << channels << ", number of samples " << item.dNSamples << std::endl;
/*
if (!mDumpWavPath.empty())
{
WavFileWriter writer;
writer.open(mDumpWavPath, samplerate, channels);
writer.write(item.pSamples, item.dNSamples * 2 * channels);
writer.close();
ICELogCritical(<< "Sending chunk of audio with rate = " << samplerate << ", channels = " << channels << ", number of samples " << item.dNSamples);
}
*/
int code = PVQA_OnTestAudioData(mContext, &item);
if (code)
{
ICELogCritical(<< "Failed to run PVQA on audio buffer with code " << code);
return 0.0;
}
/*
if (item.pSamples != pcmBuffer || item.dNSamples != pcmLength / 2 / channels || item.dSampleRate != samplerate || item.dNChannels != channels)
{
ICELogCritical(<< "PVQA changed input parameters!!!!");
}
*/
// Increase counter of processed samples
mProcessedSamples += pcmLength / channels / 2;
int milliseconds = pcmLength / channels / 2 / (samplerate / 1000);
mProcessedMilliseconds += milliseconds;
// Overall counter
mAllProcessedMilliseconds += milliseconds;
// Get results
return getResults(report, nullptr, samplerate, codec);
}
struct RgbColor
{
uint8_t mRed = 0;
uint8_t mGreen = 0;
uint8_t mBlue = 0;
static RgbColor parse(uint32_t rgb)
{
RgbColor result;
result.mBlue = (uint8_t)(rgb & 0xff);
result.mGreen = (uint8_t)((rgb >> 8) & 0xff);
result.mRed = (uint8_t)((rgb >> 16) & 0xff);
return result;
}
std::string toHex() const
{
char result[7];
sprintf(result, "%02x%02x%02x", int(mRed), int(mGreen), int(mBlue));
return std::string(result);
}
};
int SevanaPVQA::getSize() const
{
int result = 0;
result += sizeof(*this);
// TODO: add PVQA analyzer size
return result;
}
std::string SevanaPVQA::mosToColor(float mos)
{
// Limit MOS value by 5.0
mos = mos > 5.0f ? 5.0f : mos;
mos = mos < 1.0f ? 1.0f : mos;
// Split to components
RgbColor start = RgbColor::parse(MOS_BEST_COLOR), end = RgbColor::parse(MOS_BAD_COLOR);
float mosFraction = (mos - 1.0f) / 4.0f;
end.mBlue += (start.mBlue - end.mBlue) * mosFraction;
end.mGreen += (start.mGreen - end.mGreen) * mosFraction;
end.mRed += (start.mRed - end.mRed) * mosFraction;
return end.toHex();
}
} // end of namespace MT
#endif
#if defined(USE_AQUA_LIBRARY)
#include <string.h>
#include "helper/HL_String.h"
#include <sstream>
#include <json/json.h>
namespace MT
{
int SevanaAqua::initializeLibrary(const std::string& pathToLicenseFile)
{
//char buffer[pathToLicenseFile.length() + 1];
//strcpy(buffer, pathToLicenseFile.c_str());
return SSA_InitLib(const_cast<char*>(pathToLicenseFile.data()));
}
void SevanaAqua::releaseLibrary()
{
SSA_ReleaseLib();
}
std::string SevanaAqua::FaultsReport::toText() const
{
std::ostringstream oss;
if (mSignalAdvancedInMilliseconds > -4999.0)
oss << "Signal advanced in milliseconds: " << mSignalAdvancedInMilliseconds << std::endl;
if (mMistimingInPercents > -4999.0)
oss << "Mistiming in percents: " << mMistimingInPercents << std::endl;
for (ResultMap::const_iterator resultIter = mResultMap.begin(); resultIter != mResultMap.end(); resultIter++)
{
oss << resultIter->first << ":\t\t\t" << resultIter->second.mSource << " : \t" << resultIter->second.mDegrated << " \t" << resultIter->second.mUnit << std::endl;
}
return oss.str();
}
Json::Value SevanaAqua::FaultsReport::toJson() const
{
std::ostringstream oss;
Json::Value result;
result["Mistiming"] = mMistimingInPercents;
result["SignalAdvanced"] = mSignalAdvancedInMilliseconds;
Json::Value items;
for (ResultMap::const_iterator resultIter = mResultMap.begin(); resultIter != mResultMap.end(); resultIter++)
{
Json::Value item;
item["name"] = resultIter->first;
item["source"] = resultIter->second.mSource;
item["degrated"] = resultIter->second.mDegrated;
item["unit"] = resultIter->second.mUnit;
items.append(item);
}
result["items"] = items;
return result;
}
std::string SevanaAqua::getVersion()
{
TSSA_AQuA_Info* info = SSA_GetPAQuAInfo();
if (info)
return info->dVersionString;
return "";
}
SevanaAqua::SevanaAqua()
{
open();
}
SevanaAqua::~SevanaAqua()
{
close();
}
void SevanaAqua::open()
{
std::unique_lock<std::mutex> l(mMutex);
if (mContext)
return;
mContext = SSA_CreateAudioQualityAnalyzer();
if (!mContext)
;
//setParam("OutputFormats", "json");
}
void SevanaAqua::close()
{
std::unique_lock<std::mutex> l(mMutex);
if (!mContext)
return;
SSA_ReleaseAudioQualityAnalyzer(mContext);
mContext = nullptr;
}
bool SevanaAqua::isOpen() const
{
return mContext != nullptr;
}
void SevanaAqua::setTempPath(const std::string& temp_path)
{
mTempPath = temp_path;
}
std::string SevanaAqua::getTempPath() const
{
return mTempPath;
}
SevanaAqua::CompareResult SevanaAqua::compare(AudioBuffer& reference, AudioBuffer& test)
{
// Clear previous temporary file
if (!mTempPath.empty())
::remove(mTempPath.c_str());
// Result value
CompareResult r;
std::unique_lock<std::mutex> l(mMutex);
if (!mContext)
return r;
// Make analysis
TSSA_AQuA_AudioData aad;
aad.dSrcData.dNChannels = reference.mChannels;
aad.dSrcData.dSampleRate = reference.mRate;
aad.dSrcData.pSamples = (short*)reference.mData;
aad.dSrcData.dNSamples = (long)reference.mSize / 2 / reference.mChannels;
aad.dTstData.dNChannels = test.mChannels;
aad.dTstData.dSampleRate = test.mRate;
aad.dTstData.pSamples = (short*)test.mData;
aad.dTstData.dNSamples = (long)test.mSize / 2 / test.mChannels;
int rescode;
rescode = SSA_OnTestAudioData(mContext, &aad);
if (rescode)
return r;
// Get results
int len = SSA_GetQualityStringSize(mContext);
char* qs = (char*)alloca(len + 10);
SSA_FillQualityString(mContext, qs);
//std::cout << qs << std::endl;
std::istringstream iss(qs);
while (!iss.eof())
{
std::string l;
std::getline(iss, l);
// Split by :
std::vector<std::string> p;
StringHelper::split(l, p, "\t");
if (p.size() == 3)
{
p[1] = StringHelper::trim(p[1]);
p[2] = StringHelper::trim(p[2]);
r.mReport[p[1]] = p[2];
}
}
len = SSA_GetSrcSignalSpecSize(mContext);
float* srcSpecs = new float[len];
SSA_FillSrcSignalSpecArray(mContext, srcSpecs);
Json::Value src_spec_signal;
for(int i=0; i<16 && i<len; i++)
src_spec_signal.append(srcSpecs[i]);
delete[] srcSpecs;
r.mReport["SrcSpecSignal"] = src_spec_signal;
len = SSA_GetTstSignalSpecSize(mContext);
float* tstSpecs = new float[len];
SSA_FillTstSignalSpecArray(mContext, tstSpecs);
Json::Value tst_spec_signal;
for(int i=0; i<16 && i<len; i++)
tst_spec_signal.append(tstSpecs[i]);
r.mReport["TstSpecSignal"] = tst_spec_signal;
delete[] tstSpecs;
char* faults_str = nullptr;
int faults_str_len = 0;
if (mTempPath.empty())
{
faults_str_len = SSA_GetFaultsAnalysisStringSize(mContext);
if (faults_str_len > 0) {
faults_str = new char[faults_str_len + 1];
SSA_FillFaultsAnalysisString(mContext, faults_str);
faults_str[faults_str_len] = 0;
}
}
char* pairs_str = nullptr;
int pairs_str_len = SSA_GetSpecPairsStringSize(mContext);
if (pairs_str_len > 0)
{
char *pairs_str = new char[pairs_str_len + 1];
SSA_FillSpecPairsString(mContext, pairs_str, pairs_str_len);
pairs_str[pairs_str_len] = 0;
}
TSSA_AQuA_Results iResults;
SSA_FillQualityResultsStruct(mContext, &iResults);
r.mReport["dPercent"] = iResults.dPercent;
r.mReport["dMOSLike"] = iResults.dMOSLike;
if (faults_str_len > 0)
{
std::istringstream iss(faults_str);
r.mFaults = loadFaultsReport(iss);
}
else
if (!mTempPath.empty())
{
std::ifstream ifs(mTempPath.c_str());
r.mFaults = loadFaultsReport(ifs);
}
delete[] faults_str; faults_str = nullptr;
delete[] pairs_str; pairs_str = nullptr;
r.mMos = (float)iResults.dMOSLike;
return r;
}
void SevanaAqua::configureWith(const Config& config)
{
if (!mContext)
return;
for (auto& item: config)
{
const std::string& name = item.first;
const std::string& value = item.second;
if (!SSA_SetAnyString(mContext, const_cast<char *>(name.c_str()), const_cast<char *>(value.c_str())))
throw std::runtime_error(std::string("SSA_SetAnyString returned failed for pair ") + name + " " + value);
}
}
SevanaAqua::Config SevanaAqua::parseConfig(const std::string& line)
{
Config result;
// Split command line to parts
std::vector<std::string> pl;
StringHelper::split(line, pl, "-");
for (const std::string& s: pl)
{
std::string::size_type p = s.find(' ');
if (p != std::string::npos)
{
std::string name = StringHelper::trim(s.substr(0, p));
std::string value = StringHelper::trim(s.substr(p + 1));
result[name] = value;
}
}
return result;
}
SevanaAqua::PFaultsReport SevanaAqua::loadFaultsReport(std::istream& input)
{
PFaultsReport result = std::make_shared<FaultsReport>();
std::string line;
std::vector<std::string> parts;
// Parse output
while (!input.eof())
{
std::getline(input, line);
if (line.size() < 3)
continue;
std::string::size_type p = line.find(":");
if (p != std::string::npos)
{
std::string name = StringHelper::trim(line.substr(0, p));
FaultsReport::Result r;
// Split report line to components
parts.clear();
StringHelper::split(line.substr(p + 1), parts, " \t");
// Remove empty components
parts.erase(std::remove_if(parts.begin(), parts.end(), [](const std::string& item){return item.empty();}), parts.end());
if (parts.size() >= 2)
{
r.mSource = parts[0];
r.mDegrated = parts[1];
if (parts.size()> 2)
r.mUnit = parts[2];
result->mResultMap[name] = r;
}
}
else
{
p = line.find("ms.");
if (p != std::string::npos)
{
parts.clear();
StringHelper::split(line, parts, " \t");
if (parts.size() >= 3)
{
if (parts.back() == "ms.")
result->mSignalAdvancedInMilliseconds = std::atof(parts[parts.size() - 2].c_str());
}
}
else
{
p = line.find("percent.");
if (p != std::string::npos)
{
parts.clear();
StringHelper::split(line, parts, " \t");
if (parts.size() >= 3)
{
if (parts.back() == "percent.")
result->mMistimingInPercents = std::atof(parts[parts.size() - 2].c_str());
}
}
}
}
}
return result;
}
} // end of namespace MT
// It is to workaround old AQuA NDK build - it has reference to ftime
/*#if defined(TARGET_ANDROID)
#include <sys/timeb.h>
// This was removed from POSIX 2008.
int ftime(struct timeb* tb) {
struct timeval tv;
struct timezone tz;
if (gettimeofday(&tv, &tz) < 0)
return -1;
tb->time = tv.tv_sec;
tb->millitm = (tv.tv_usec + 500) / 1000;
if (tb->millitm == 1000) {
++tb->time;
tb->millitm = 0;
}
tb->timezone = tz.tz_minuteswest;
tb->dstflag = tz.tz_dsttime;
return 0;
}
#endif*/
#endif

View File

@@ -0,0 +1,208 @@
#ifndef _SEVANA_MOS_H
#define _SEVANA_MOS_H
#include <string>
#include <vector>
#include <memory>
#include <mutex>
#include <atomic>
#include <map>
#if defined(USE_PVQA_LIBRARY)
# include "pvqa.h"
# if !defined(PVQA_INTERVAL)
# define PVQA_INTERVAL (0.68)
# endif
#endif
#if defined(USE_AQUA_LIBRARY)
# include "aqua.h"
# include <json/json.h>
#endif
namespace MT
{
enum class ReportType
{
PlainText,
Html
};
#if defined(USE_PVQA_LIBRARY)
class SevanaMosUtility
{
public:
// Returns MOS estimation as text representation of float value or "failed" word.
static void run(const std::string& pcmPath, const std::string& intervalPath,
std::string& estimation, std::string& intervals);
};
extern float getSevanaMos(const std::string& audioPath, const std::string& intervalReportPath,
std::string& intervalReport);
class SevanaPVQA
{
public:
enum class Model
{
Stream,
Interval
};
protected:
static void* mLibraryConfiguration;
static int mLibraryErrorCode;
static std::atomic_int mInstanceCounter;
static std::atomic_uint_least64_t mAllProcessedMilliseconds;
static bool mPvqaLoaded;
void* mContext = nullptr;
Model mModel = Model::Interval;
double mIntervalLength = 0.68;
uint64_t mProcessedSamples = 0,
mProcessedMilliseconds = 0;
bool mAudioLineInitialized = false;
std::string mDumpWavPath;
bool mOpenFailed = false;
public:
static std::string getVersion();
// Required to call before any call to SevanaPVQA instance methods
#if defined(TARGET_ANDROID)
static void setupAndroidEnvironment(void* environment, void* appcontext);
#endif
static bool initializeLibrary(const std::string& pathToLicenseFile, const std::string& pathToConfigFile);
static bool isInitialized();
static int getLibraryError();
static void releaseLibrary();
static int getInstanceCounter() { return mInstanceCounter; }
static uint64_t getProcessedMilliseconds() { return mAllProcessedMilliseconds; }
SevanaPVQA();
~SevanaPVQA();
void open(double interval, Model model);
void close();
bool isOpen() const;
// Update/Get model
void update(int samplerate, int channels, const void* pcmBuffer, int pcmLength);
typedef std::vector<std::vector<float>> EchoData;
enum class Codec
{
None,
G711,
ILBC,
G722,
G729,
GSM,
AMRNB,
AMRWB,
OPUS
};
float getResults(std::string& report, const EchoData** echo, int samplerate, Codec codec);
// Report is interval report. Names are output detector names. startIndex is column's start index in interval report of first detector.
struct DetectorsList
{
std::vector<std::string> mNames;
int mStartIndex = 0;
};
static DetectorsList getDetectorsNames(const std::string& report);
// Get MOS in one shot
void setPathToDumpFile(const std::string& path);
float process(int samplerate, int channels, const void* pcmBuffer, int pcmLength, std::string& report, Codec codec);
Model getModel() const { return mModel; }
int getSize() const;
static std::string mosToColor(float mos);
};
typedef std::shared_ptr<SevanaPVQA> PSevanaPVQA;
#endif
#if defined(USE_AQUA_LIBRARY)
class SevanaAqua
{
protected:
void* mContext = nullptr;
std::mutex mMutex;
std::string mTempPath;
public:
// Returns 0 (zero) on successful initialization, otherwise it is error code
static int initializeLibrary(const std::string& pathToLicenseFile);
static void releaseLibrary();
static std::string getVersion();
SevanaAqua();
~SevanaAqua();
void open();
void close();
bool isOpen() const;
void setTempPath(const std::string& temp_path);
std::string getTempPath() const;
typedef std::map<std::string, std::string> Config;
void configureWith(const Config& config);
static Config parseConfig(const std::string& line);
// Report is returned in JSON format
struct AudioBuffer
{
int mRate = 8000;
int mChannels = 1;
void* mData = nullptr;
int mSize = 0; // In bytes
};
struct FaultsReport
{
float mSignalAdvancedInMilliseconds = -5000.0;
float mMistimingInPercents = -5000.0;
struct Result
{
std::string mSource, mDegrated, mUnit;
};
typedef std::map<std::string, Result> ResultMap;
ResultMap mResultMap;
Json::Value toJson() const;
std::string toText() const;
};
typedef std::shared_ptr<FaultsReport> PFaultsReport;
static PFaultsReport loadFaultsReport(std::istream& input);
// Compare in one shot. Report will include text representation of json report.
struct CompareResult
{
float mMos = 0.0f;
Json::Value mReport;
PFaultsReport mFaults;
};
CompareResult compare(AudioBuffer& reference, AudioBuffer& test);
};
typedef std::shared_ptr<SevanaAqua> PSevanaAqua;
#endif
}
#endif

View File

@@ -0,0 +1,42 @@
/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MT_SingleAudioStream.h"
#include "MT_CodecList.h"
#include "resip/stack/SdpContents.hxx"
#include "../engine/helper/HL_Log.h"
#define LOG_SUBSYSTEM "SingleAudioStream"
using namespace MT;
SingleAudioStream::SingleAudioStream(const CodecList::Settings& codecSettings, Statistics& stat)
:mReceiver(codecSettings, stat), mDtmfReceiver(stat)
{
}
SingleAudioStream::~SingleAudioStream()
{
}
void SingleAudioStream::process(std::shared_ptr<jrtplib::RTPPacket> packet)
{
ICELogMedia(<< "Processing incoming RTP/RTCP packet");
if (packet->GetPayloadType() == resip::Codec::TelephoneEvent.payloadType())
mDtmfReceiver.add(packet);
else
mReceiver.add(packet);
}
void SingleAudioStream::copyPcmTo(Audio::DataWindow& output, int needed)
{
while (output.filled() < needed)
if (!mReceiver.getAudio(output))
break;
if (output.filled() < needed)
ICELogCritical(<< "Not enough data for speaker's mixer");
}

View File

@@ -0,0 +1,31 @@
/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __MT_SSRC_STREAM_H
#define __MT_SSRC_STREAM_H
#include "jrtplib/src/rtppacket.h"
#include <map>
#include "MT_Codec.h"
#include "MT_WebRtc.h"
#include "MT_AudioReceiver.h"
namespace MT
{
class SingleAudioStream
{
public:
SingleAudioStream(const CodecList::Settings& codecSettings, Statistics& stat);
~SingleAudioStream();
void process(std::shared_ptr<jrtplib::RTPPacket> packet);
void copyPcmTo(Audio::DataWindow& output, int needed);
protected:
DtmfReceiver mDtmfReceiver;
AudioReceiver mReceiver;
};
typedef std::map<unsigned, SingleAudioStream*> AudioStreamMap;
}
#endif

View File

@@ -0,0 +1,231 @@
/* Copyright(C) 2007-2016 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MT_SrtpHelper.h"
#include "../helper/HL_Log.h"
#include "../helper/HL_Exception.h"
#include "../helper/HL_Rtp.h"
#include <assert.h>
// --- SrtpStream ---
void initSrtpStream(SrtpStream& s, unsigned ssrc, SrtpSuite suite)
{
s.second.ssrc.type = ssrc_specific;
s.second.ssrc.value = ntohl(ssrc);
s.second.next = NULL;
switch (suite)
{
case SRTP_AES_128_AUTH_80:
crypto_policy_set_aes_cm_128_hmac_sha1_80(&s.second.rtp);
crypto_policy_set_aes_cm_128_hmac_sha1_80(&s.second.rtcp);
break;
case SRTP_AES_256_AUTH_80:
crypto_policy_set_aes_cm_256_hmac_sha1_80(&s.second.rtp);
crypto_policy_set_aes_cm_256_hmac_sha1_80(&s.second.rtcp);
break;
default:
assert(0);
}
}
SrtpSession::SrtpSession()
:mInboundSession(NULL), mOutboundSession(NULL)
{
mInboundSession = NULL;
mOutboundSession = NULL;
mSuite = SRTP_NONE;
memset(&mInboundPolicy, 0, sizeof mInboundPolicy);
mInboundPolicy.ssrc.type = ssrc_specific;
memset(&mOutboundPolicy, 0, sizeof mOutboundPolicy);
mOutboundPolicy.ssrc.type = ssrc_specific;
// Generate outgoing keys
mOutgoingKey[SRTP_AES_128_AUTH_80-1].first = PByteBuffer(new ByteBuffer());
mOutgoingKey[SRTP_AES_128_AUTH_80-1].first->resize(30);
crypto_get_random((unsigned char*)mOutgoingKey[SRTP_AES_128_AUTH_80-1].first->mutableData(), 30);
mOutgoingKey[SRTP_AES_256_AUTH_80-1].first = PByteBuffer(new ByteBuffer());
mOutgoingKey[SRTP_AES_256_AUTH_80-1].first->resize(46);
crypto_get_random((unsigned char*)mOutgoingKey[SRTP_AES_256_AUTH_80-1].first->mutableData(), 46);
}
SrtpSession::~SrtpSession()
{
}
void SrtpSession::addSsrc(unsigned ssrc, SsrcDirection d)
{
Lock l(mGuard);
assert(mSuite != SRTP_NONE);
// Look in map - if the srtp stream for this ssrc is created already
SrtpStreamMap::iterator streamIter;
SrtpStream s;
switch (d)
{
case sdIncoming:
streamIter = mIncomingMap.find(ssrc);
if (streamIter != mIncomingMap.end())
return;
initSrtpStream(s, ssrc, mSuite);
s.second.key = (unsigned char*)mIncomingKey.first->mutableData();
mIncomingMap[ssrc] = s;
srtp_add_stream(mInboundSession, &s.second);
return;
case sdOutgoing:
streamIter = mOutgoingMap.find(ssrc);
if (streamIter != mOutgoingMap.end())
return;
initSrtpStream(s, ssrc, mSuite);
s.second.key = (unsigned char*)mOutgoingKey[int(mSuite)-1].first->mutableData();
mOutgoingMap[ssrc] = s;
srtp_add_stream(mOutboundSession, &s.second);
return;
}
}
void SrtpSession::open(ByteBuffer& incomingKey, SrtpSuite suite)
{
Lock l(mGuard);
// Check if session is here already
if (mInboundSession || mOutboundSession)
return;
// Save used SRTP suite
mSuite = suite;
// Save key
mIncomingKey.first = PByteBuffer(new ByteBuffer(incomingKey));
// Update policy
switch (suite)
{
case SRTP_AES_128_AUTH_80:
crypto_policy_set_aes_cm_128_hmac_sha1_80(&mInboundPolicy.rtp);
crypto_policy_set_aes_cm_128_hmac_sha1_80(&mInboundPolicy.rtcp);
crypto_policy_set_aes_cm_128_hmac_sha1_80(&mOutboundPolicy.rtp);
crypto_policy_set_aes_cm_128_hmac_sha1_80(&mOutboundPolicy.rtcp);
break;
case SRTP_AES_256_AUTH_80:
crypto_policy_set_aes_cm_256_hmac_sha1_80(&mInboundPolicy.rtp);
crypto_policy_set_aes_cm_256_hmac_sha1_80(&mInboundPolicy.rtcp);
crypto_policy_set_aes_cm_256_hmac_sha1_80(&mOutboundPolicy.rtp);
crypto_policy_set_aes_cm_256_hmac_sha1_80(&mOutboundPolicy.rtcp);
break;
case SRTP_NONE:
break;
}
mOutboundPolicy.key = (unsigned char*)mOutgoingKey[int(suite)-1].first->mutableData();
mInboundPolicy.key = (unsigned char*)mIncomingKey.first->mutableData();
// Create SRTP session
err_status_t err;
err = srtp_create(&mOutboundSession, &mOutboundPolicy);
if (err)
throw Exception(ERR_SRTP, err);
err = srtp_create(&mInboundSession, &mInboundPolicy);
if (err)
throw Exception(ERR_SRTP, err);
}
bool SrtpSession::active()
{
Lock l(mGuard);
return mInboundSession != 0 && mOutboundSession != 0;
}
void SrtpSession::close()
{
Lock l(mGuard);
if (mOutboundSession)
{
srtp_dealloc(mOutboundSession);
mOutboundSession = NULL;
}
if (mInboundSession)
{
srtp_dealloc(mInboundSession);
mInboundSession = NULL;
}
}
SrtpKeySalt& SrtpSession::outgoingKey(SrtpSuite suite)
{
Lock l(mGuard);
assert(suite > SRTP_NONE && suite <= SRTP_LAST);
return mOutgoingKey[int(suite)-1];
}
bool SrtpSession::protectRtp(void* buffer, int* length)
{
addSsrc(RtpHelper::findSsrc(buffer, *length), sdOutgoing);
Lock l(mGuard);
if (mOutboundSession)
return srtp_protect(mOutboundSession, buffer, length) == 0;
else
return false;
}
bool SrtpSession::protectRtcp(void* buffer, int* length)
{
addSsrc(RtpHelper::findSsrc(buffer, *length), sdOutgoing);
Lock l(mGuard);
if (mOutboundSession)
return srtp_protect_rtcp(mOutboundSession, buffer, length) == 0;
else
return false;
}
bool SrtpSession::unprotectRtp(void* buffer, int* length)
{
addSsrc(RtpHelper::findSsrc(buffer, *length), sdIncoming);
Lock l(mGuard);
if (mInboundSession)
return srtp_unprotect(mInboundSession, buffer, length) == 0;
else
return false;
}
bool SrtpSession::unprotectRtcp(void* buffer, int* length)
{
addSsrc(RtpHelper::findSsrc(buffer, *length), sdIncoming);
Lock l(mGuard);
if (mInboundSession)
return srtp_unprotect_rtcp(mInboundSession, buffer, (int*)length) == 0;
else
return false;
}
static bool GSrtpInitialized = false;
void SrtpSession::initSrtp()
{
if (GSrtpInitialized)
return;
err_status_t err = srtp_init();
if (err != err_status_ok)
throw Exception(ERR_SRTP, err);
GSrtpInitialized = true;
}

View File

@@ -0,0 +1,73 @@
/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __MT_SRTP_HELPER_H
#define __MT_SRTP_HELPER_H
#include <string>
#include <vector>
#include <map>
#include "srtp/include/srtp.h"
#include "../helper/HL_Sync.h"
#include "../helper/HL_ByteBuffer.h"
#define SRTP_SUITE_NAME_2 "AES_CM_256_HMAC_SHA1_80"
#define SRTP_SUITE_NAME_1 "AES_CM_128_HMAC_SHA1_80"
enum SrtpSuite
{
SRTP_NONE,
SRTP_AES_128_AUTH_80,
SRTP_AES_256_AUTH_80,
SRTP_LAST = SRTP_AES_256_AUTH_80
};
typedef std::pair<PByteBuffer, PByteBuffer> SrtpKeySalt;
typedef std::pair<unsigned, srtp_policy_t> SrtpStream;
class SrtpSession
{
public:
SrtpSession();
~SrtpSession();
enum SsrcDirection
{
sdIncoming,
sdOutgoing
};
SrtpKeySalt& outgoingKey(SrtpSuite suite);
void open(ByteBuffer& incomingKey, SrtpSuite suite);
void close();
bool active();
/* bufferPtr is RTP packet data i.e. header + payload. Buffer must be big enough to hold encrypted data. */
bool protectRtp(void* buffer, int* length);
bool protectRtcp(void* buffer, int* length);
bool unprotectRtp(void* buffer, int* length);
bool unprotectRtcp(void* buffer, int* length);
static void initSrtp();
protected:
srtp_t mInboundSession,
mOutboundSession;
SrtpKeySalt mIncomingKey,
mOutgoingKey[SRTP_LAST];
srtp_policy_t mInboundPolicy;
srtp_policy_t mOutboundPolicy;
SrtpSuite mSuite;
typedef std::map<unsigned, SrtpStream> SrtpStreamMap;
SrtpStreamMap mIncomingMap, mOutgoingMap;
Mutex mGuard;
void addSsrc(unsigned ssrc, SsrcDirection d);
};
#endif

View File

@@ -0,0 +1,276 @@
#include <math.h>
#include "MT_Statistics.h"
#include "audio/Audio_Interface.h"
#include "helper/HL_Log.h"
#define LOG_SUBSYSTEM "Statistics"
using namespace MT;
void JitterStatistics::process(jrtplib::RTPPacket* packet, int rate)
{
jrtplib::RTPTime receiveTime = packet->GetReceiveTime();
uint32_t timestamp = packet->GetTimestamp();
if (!mLastJitter.is_initialized())
{
mReceiveTime = receiveTime;
mReceiveTimestamp = timestamp;
mLastJitter = 0.0;
}
else
{
double delta = (receiveTime.GetDouble() - mReceiveTime.GetDouble()) - double(timestamp - mReceiveTimestamp) / rate;
if (fabs(delta) > mMaxDelta)
mMaxDelta = fabs(delta);
mLastJitter = mLastJitter.value() + (fabs(delta) - mLastJitter.value()) / 16.0;
mReceiveTime = receiveTime;
mReceiveTimestamp = timestamp;
mJitter.process(mLastJitter.value());
}
}
// ---------------------------- Statistics ------------------------------------
Statistics::Statistics()
:mReceived(0), mSent(0), mReceivedRtp(0), mSentRtp(0),
mReceivedRtcp(0), mSentRtcp(0), mDuplicatedRtp(0), mOldRtp(0), mIllegalRtp(0),
mPacketLoss(0), mJitter(0.0), mAudioTime(0), mSsrc(0)
{
#if defined(USE_AMR_CODEC)
mBitrateSwitchCounter = 0;
#endif
memset(mLoss, 0, sizeof mLoss);
// It is to keep track of statistics instance via grep | wc -l
//ICELogDebug(<< "Create statistics instance.");
}
Statistics::~Statistics()
{
}
void Statistics::reset()
{
mReceived = 0;
mSent = 0;
mReceivedRtp = 0;
mSentRtp = 0;
mReceivedRtcp = 0;
mSentRtcp = 0;
mDuplicatedRtp = 0;
mOldRtp = 0;
mPacketLoss = 0;
mIllegalRtp = 0;
mJitter = 0.0;
mAudioTime = 0;
memset(mLoss, 0, sizeof mLoss);
}
/*
double calculate_mos_g711(double ppl, double burstr, int version) {
double r;
double bpl = 8.47627; //mos = -4.23836 + 0.29873 * r - 0.00416744 * r * r + 0.0000209855 * r * r * r;
double mos;
if(ppl == 0 or burstr == 0) {
return 4.5;
}
if(ppl > 0.5) {
return 1;
}
switch(version) {
case 1:
case 2:
default:
// this mos is calculated for G.711 and PLC
bpl = 17.2647;
r = 93.2062077233 - 95.0 * (ppl*100/(ppl*100/burstr + bpl));
mos = 2.06405 + 0.031738 * r - 0.000356641 * r * r + 2.93143 * pow(10,-6) * r * r * r;
if(mos < 1)
return 1;
if(mos > 4.5)
return 4.5;
}
return mos;
}
double calculate_mos(double ppl, double burstr, int codec, unsigned int received) {
if(codec == PAYLOAD_G729) {
if(opt_mos_g729) {
if(received < 100) {
return 3.92;
}
return (double)mos_g729((long double)ppl, (long double)burstr);
} else {
if(received < 100) {
return 4.5;
}
return calculate_mos_g711(ppl, burstr, 2);
}
} else {
if(received < 100) {
return 4.5;
}
return calculate_mos_g711(ppl, burstr, 2);
}
}
*/
void Statistics::calculateBurstr(double* burstr, double* lossr) const
{
int lost = 0;
int bursts = 0;
for (int i = 0; i < 128; i++)
{
lost += i * mLoss[i];
bursts += mLoss[i];
}
if (lost < 5)
{
// ignore such small packet loss
*lossr = *burstr = 0;
return;
}
if (mReceivedRtp > 0 && bursts > 0)
{
*burstr = (double)((double)lost / (double)bursts) / (double)(1.0 / (1.0 - (double)lost / (double)mReceivedRtp));
if (*burstr < 0)
*burstr = -*burstr;
else if (*burstr < 1)
*burstr = 1;
}
else
*burstr = 0;
//printf("total loss: %d\n", lost);
if (mReceivedRtp > 0)
*lossr = (double)((double)lost / (double)mReceivedRtp);
else
*lossr = 0;
}
double Statistics::calculateMos(double maximalMos) const
{
// calculate lossrate and burst rate
double burstr, lossr;
calculateBurstr(&burstr, &lossr);
double r;
double bpl = 8.47627; //mos = -4.23836 + 0.29873 * r - 0.00416744 * r * r + 0.0000209855 * r * r * r;
double mos;
if (mReceivedRtp < 100)
return 0.0;
if (lossr == 0 || burstr == 0)
{
return maximalMos;
}
if (lossr > 0.5)
return 1;
bpl = 17.2647;
r = 93.2062077233 - 95.0 * (lossr * 100 / (lossr * 100 / burstr + bpl));
mos = 2.06405 + 0.031738 * r - 0.000356641 * r * r + 2.93143 * pow(10, -6) * r * r * r;
if (mos < 1)
return 1;
if (mos > maximalMos)
return maximalMos;
return mos;
}
Statistics& Statistics::operator += (const Statistics& src)
{
mReceived += src.mReceived;
mSent += src.mSent;
mReceivedRtp += src.mReceivedRtp;
mSentRtp += src.mSentRtp;
mReceivedRtcp += src.mReceivedRtcp;
mSentRtcp += src.mSentRtcp;
mDuplicatedRtp += src.mDuplicatedRtp;
mOldRtp += src.mOldRtp;
mPacketLoss += src.mPacketLoss;
mAudioTime += src.mAudioTime;
for (auto codecStat: src.mCodecCount)
{
if (mCodecCount.find(codecStat.first) == mCodecCount.end())
mCodecCount[codecStat.first] = codecStat.second;
else
mCodecCount[codecStat.first] += codecStat.second;
}
mJitter = src.mJitter;
mRttDelay = src.mRttDelay;
if (!src.mCodecName.empty())
mCodecName = src.mCodecName;
// Find minimal
if (mFirstRtpTime.is_initialized())
{
if (src.mFirstRtpTime.is_initialized())
{
if (mFirstRtpTime.value() > src.mFirstRtpTime.value())
mFirstRtpTime = src.mFirstRtpTime;
}
}
else
if (src.mFirstRtpTime.is_initialized())
mFirstRtpTime = src.mFirstRtpTime;
#if defined(USE_AMR_CODEC)
mBitrateSwitchCounter += src.mBitrateSwitchCounter;
#endif
mRemotePeer = src.mRemotePeer;
mSsrc = src.mSsrc;
return *this;
}
Statistics& Statistics::operator -= (const Statistics& src)
{
mReceived -= src.mReceived;
mSent -= src.mSent;
mReceivedRtp -= src.mReceivedRtp;
mIllegalRtp -= src.mIllegalRtp;
mSentRtp -= src.mSentRtp;
mReceivedRtcp -= src.mReceivedRtcp;
mSentRtcp -= src.mSentRtcp;
mDuplicatedRtp -= src.mDuplicatedRtp;
mOldRtp -= src.mOldRtp;
mPacketLoss -= src.mPacketLoss;
mAudioTime -= src.mAudioTime;
for (auto codecStat: src.mCodecCount)
{
if (mCodecCount.find(codecStat.first) != mCodecCount.end())
mCodecCount[codecStat.first] -= codecStat.second;
}
return *this;
}
std::string Statistics::toShortString() const
{
std::ostringstream oss;
oss << "Received: " << mReceivedRtp
<< ", lost: " << mPacketLoss
<< ", sent: " << mSentRtp;
return oss.str();
}

View File

@@ -0,0 +1,150 @@
#ifndef _MT_STATISTICS_H
#define _MT_STATISTICS_H
#include <chrono>
#include <map>
#include "audio/Audio_DataWindow.h"
#include "helper/HL_Optional.hpp"
#include "jrtplib/src/rtptimeutilities.h"
#include "jrtplib/src/rtppacket.h"
#include "MT_SevanaMos.h"
using std::experimental::optional;
namespace MT
{
template<typename T>
struct Average
{
int mCount = 0;
T mSum = 0;
T getAverage() const
{
if (!mCount)
return 0;
return mSum / mCount;
}
void process(T value)
{
mCount++;
mSum += value;
}
};
template<typename T, int minimum = 100000, int maximum = 0>
struct ProbeStats
{
T mMin = minimum;
T mMax = maximum;
Average<T> mAverage;
T mCurrent = minimum;
void process(T value)
{
if (mMin > value)
mMin = value;
if (mMax < value)
mMax = value;
mCurrent = value;
mAverage.process(value);
}
bool isInitialized() const
{
return mAverage.mCount > 0;
}
T getCurrent() const
{
if (isInitialized())
return mCurrent;
else
return 0;
}
};
template<typename T>
struct StreamStats
{
T mChunk;
T mTotal;
};
class JitterStatistics
{
public:
void process(jrtplib::RTPPacket* packet, int samplerate);
ProbeStats<double> get() const { return mJitter; }
double getMaxDelta() const { return mMaxDelta; }
protected:
// Jitter calculation
jrtplib::RTPTime mReceiveTime = jrtplib::RTPTime(0,0);
uint32_t mReceiveTimestamp = 0;
optional<double> mLastJitter;
ProbeStats<double> mJitter;
double mMaxDelta = 0.0;
};
class Statistics
{
public:
int mReceived, // Received traffic in bytes
mSent, // Sent traffic in bytes
mReceivedRtp, // Number of received rtp packets
mSentRtp, // Number of sent rtp packets
mReceivedRtcp, // Number of received rtcp packets
mSentRtcp, // Number of sent rtcp packets
mDuplicatedRtp, // Number of received duplicated rtp packets
mOldRtp, // Number of late rtp packets
mPacketLoss, // Number of lost packets
mIllegalRtp; // Number of rtp packets with bad payload type
int mLoss[128]; // Every item is number of loss of corresping length
int mAudioTime; // Decoded/found time in milliseconds
uint16_t mSsrc; // Last known SSRC ID in a RTP stream
ice::NetworkAddress mRemotePeer; // Last known remote RTP address
#if defined(USE_AMR_CODEC)
int mBitrateSwitchCounter;
#endif
std::string mCodecName;
float mJitter; // Jitter
ProbeStats<double> mRttDelay; // RTT delay
// Timestamp when first RTP packet has arrived
optional<std::chrono::system_clock::time_point> mFirstRtpTime;
std::map<int, int> mCodecCount; // Stats on used codecs
// It is to calculate network MOS
void calculateBurstr(double* burstr, double* loss) const;
double calculateMos(double maximalMos) const;
Statistics();
~Statistics();
void reset();
Statistics& operator += (const Statistics& src);
Statistics& operator -= (const Statistics& src);
float mNetworkMos = 0.0;
#if defined(USE_PVQA_LIBRARY) && !defined(PVQA_SERVER)
float mPvqaMos = 0.0;
std::string mPvqaReport;
#endif
std::string toShortString() const;
};
} // end of namespace MT
#endif

View File

@@ -0,0 +1,130 @@
/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MT_Stream.h"
#include "../audio/Audio_Interface.h"
#include "../helper/HL_Log.h"
#include <math.h>
#define LOG_SUBSYSTEM "[Media]"
using namespace MT;
Stream::Stream()
:mState(0)
{
}
Stream::~Stream()
{
}
void Stream::setDestination(const RtpPair<InternetAddress>& dest)
{
ICELogInfo(<< "Set RTP destination to " << dest.mRtp.toStdString());
mDestination = dest;
mStat.mRemotePeer = dest.mRtp;
}
void Stream::setState(unsigned state)
{
mState = state;
}
unsigned Stream::state()
{
return mState;
}
void Stream::setSocket(const RtpPair<PDatagramSocket>& socket)
{
mSocket = socket;
}
RtpPair<PDatagramSocket>& Stream::socket()
{
return mSocket;
}
Statistics& Stream::statistics()
{
return mStat;
}
SrtpSession& Stream::srtp()
{
return mSrtpSession;
}
void Stream::configureMediaObserver(MediaObserver *observer, void* userTag)
{
mMediaObserver = observer;
mMediaObserverTag = userTag;
}
StreamList::StreamList()
{
}
StreamList::~StreamList()
{
clear();
}
void StreamList::add(PStream s)
{
Lock l(mMutex);
mStreamVector.push_back(s);
}
void StreamList::remove(PStream s)
{
Lock l(mMutex);
StreamVector::iterator streamIter = std::find(mStreamVector.begin(), mStreamVector.end(), s);
if (streamIter != mStreamVector.end())
mStreamVector.erase(streamIter);
}
void StreamList::clear()
{
Lock l(mMutex);
mStreamVector.clear();
}
bool StreamList::has(PStream s)
{
Lock l(mMutex);
return std::find(mStreamVector.begin(), mStreamVector.end(), s) != mStreamVector.end();
}
int StreamList::size()
{
Lock l(mMutex);
return mStreamVector.size();
}
PStream StreamList::streamAt(int index)
{
return mStreamVector[index];
}
void StreamList::copyTo(StreamList* sl)
{
Lock l(mMutex);
Lock l2(sl->mMutex);
StreamVector::iterator streamIter = mStreamVector.begin();
for(;streamIter != mStreamVector.end(); ++streamIter)
sl->add(*streamIter);
}
Mutex& StreamList::getMutex()
{
return mMutex;
}

View File

@@ -0,0 +1,114 @@
/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __MT_STREAM_H
#define __MT_STREAM_H
#include "ice/ICEAddress.h"
#include "MT_Codec.h"
#include "MT_SrtpHelper.h"
#include "MT_Statistics.h"
#include "../helper/HL_InternetAddress.h"
#include "../helper/HL_NetworkSocket.h"
#include "../helper/HL_Sync.h"
#include "../helper/HL_Rtp.h"
#include "../audio/Audio_WavFile.h"
#include "../audio/Audio_DataWindow.h"
#include <vector>
#include <map>
#include <chrono>
#include "../helper/HL_Optional.hpp"
#if defined(USE_PVQA_LIBRARY)
# include "MT_SevanaMos.h"
#endif
using std::experimental::optional;
namespace MT
{
class Stream
{
public:
enum Type
{
Audio = 1,
Video = 2
};
enum class MediaDirection
{
Incoming,
Outgoing
};
class MediaObserver
{
public:
virtual void onMedia(const void* buffer, int length, MT::Stream::MediaDirection direction,
void* context, void* userTag) = 0;
};
Stream();
virtual ~Stream();
virtual void setDestination(const RtpPair<InternetAddress>& dest);
virtual void setTransmittingCodec(Codec::Factory& factory, int payloadType) = 0;
virtual void dataArrived(PDatagramSocket s, const void* buffer, int length, InternetAddress& source) = 0;
virtual void readFile(const Audio::PWavFileReader& reader, MediaDirection direction) = 0;
virtual void writeFile(const Audio::PWavFileWriter& writer, MediaDirection direction) = 0;
virtual void setupMirror(bool enable) = 0;
virtual void setState(unsigned state);
virtual unsigned state();
virtual void setSocket(const RtpPair<PDatagramSocket>& socket);
virtual RtpPair<PDatagramSocket>& socket();
Statistics& statistics();
SrtpSession& srtp();
void configureMediaObserver(MediaObserver* observer, void* userTag);
protected:
unsigned mState;
RtpPair<InternetAddress> mDestination;
RtpPair<PDatagramSocket> mSocket;
Statistics mStat;
SrtpSession mSrtpSession;
MediaObserver* mMediaObserver = nullptr;
void* mMediaObserverTag = nullptr;
};
typedef std::shared_ptr<Stream> PStream;
class StreamList
{
public:
StreamList();
~StreamList();
void add(PStream s);
void remove(PStream s);
void clear();
bool has(PStream s);
int size();
PStream streamAt(int index);
void copyTo(StreamList* sl);
Mutex& getMutex();
protected:
typedef std::vector<PStream> StreamVector;
StreamVector mStreamVector;
Mutex mMutex;
};
}
#endif

View File

@@ -0,0 +1,70 @@
/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "../config.h"
#include "MT_WebRtc.h"
#include "../helper/HL_Exception.h"
#include <stdlib.h>
static void checkResultCode(unsigned short code)
{
if (code)
throw Exception(ERR_WEBRTC, code);
}
Vad::Vad(int sampleRate)
:mSampleRate(sampleRate), mContext(NULL)
{
checkResultCode(WebRtcVad_Create(&mContext));
}
Vad::~Vad()
{
if (mContext)
WebRtcVad_Free(mContext);
}
bool Vad::isSilence(short* samplePtr, int nrOfSamples)
{
short resultCode = WebRtcVad_Process(mContext, mSampleRate, samplePtr, nrOfSamples);
return !resultCode;
}
// -- Cng ---
Cng::Cng()
:mEncoder(NULL), mDecoder(NULL)
{
checkResultCode(WebRtcCng_CreateEnc(&mEncoder));
checkResultCode(WebRtcCng_CreateDec(&mDecoder));
}
Cng::~Cng()
{
if (mEncoder)
WebRtcCng_FreeEnc(mEncoder);
if (mDecoder)
WebRtcCng_FreeDec(mDecoder);
}
void Cng::updateSid(unsigned char *sidPacket, int sidLength)
{
WebRtcCng_UpdateSid(mDecoder, sidPacket, sidLength);
}
void Cng::generateSid(short* samples, int nrOfSamples, unsigned char* sidPacket, int* sidLength)
{
WebRtc_Word16 produced = 0;
checkResultCode(WebRtcCng_Encode(mEncoder, (WebRtc_Word16*)samples, nrOfSamples, sidPacket, &produced, 1/*TRUE*/));
*sidLength = (int)produced;
}
void Cng::generateNoise(short* buffer, int nrOfSamples)
{
checkResultCode(WebRtcCng_Generate(mDecoder, buffer, nrOfSamples, 1 /*Reset CNG history*/));
}

View File

@@ -0,0 +1,46 @@
/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __MT_WEBRTC_H
#define __MT_WEBRTC_H
#include <string>
#include <vector>
#include "jrtplib/src/rtppacket.h"
#include "webrtc/cng/webrtc_cng.h"
#include "webrtc/vad/webrtc_vad.h"
// Voice activity detector. It is tiny wrapper for webrtc functionality.
class Vad
{
public:
Vad(int sampleRate);
~Vad();
bool isSilence(short* samplePtr, int sampleCount);
protected:
int mSampleRate;
bool mEnabled;
VadInst* mContext;
};
// Comfort noise helper. It is tiny wrapper for webrtc functionality.
class Cng
{
public:
Cng();
~Cng();
void updateSid(unsigned char *sidPacket, int sidLength);
void generateSid(short* samples, int nrOfSamples, unsigned char* sidPacket, int* sidLength);
void generateNoise(short* buffer, int nrOfSamples);
protected:
CNG_enc_inst* mEncoder;
CNG_dec_inst* mDecoder;
};
#endif