- initial import
This commit is contained in:
958
src/engine/media/MT_AmrCodec.cpp
Normal file
958
src/engine/media/MT_AmrCodec.cpp
Normal 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
|
||||
162
src/engine/media/MT_AmrCodec.h
Normal file
162
src/engine/media/MT_AmrCodec.h
Normal 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
|
||||
|
||||
1433
src/engine/media/MT_AudioCodec.cpp
Normal file
1433
src/engine/media/MT_AudioCodec.cpp
Normal file
File diff suppressed because it is too large
Load Diff
421
src/engine/media/MT_AudioCodec.h
Normal file
421
src/engine/media/MT_AudioCodec.h
Normal 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
|
||||
723
src/engine/media/MT_AudioReceiver.cpp
Normal file
723
src/engine/media/MT_AudioReceiver.cpp
Normal 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)
|
||||
{
|
||||
}
|
||||
199
src/engine/media/MT_AudioReceiver.h
Normal file
199
src/engine/media/MT_AudioReceiver.h
Normal 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
|
||||
426
src/engine/media/MT_AudioStream.cpp
Normal file
426
src/engine/media/MT_AudioStream.cpp
Normal 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, ¶ms, jrtplib::RTPTransmitter::ExternalProto);
|
||||
mRtpDtmfSession.Create(sessionParams, ¶ms, 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;
|
||||
}
|
||||
110
src/engine/media/MT_AudioStream.h
Normal file
110
src/engine/media/MT_AudioStream.h
Normal 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
132
src/engine/media/MT_Box.cpp
Normal 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
47
src/engine/media/MT_Box.h
Normal 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
|
||||
181
src/engine/media/MT_CngHelper.cpp
Normal file
181
src/engine/media/MT_CngHelper.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
29
src/engine/media/MT_CngHelper.h
Normal file
29
src/engine/media/MT_CngHelper.h
Normal 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
|
||||
41
src/engine/media/MT_Codec.cpp
Normal file
41
src/engine/media/MT_Codec.cpp
Normal 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
|
||||
64
src/engine/media/MT_Codec.h
Normal file
64
src/engine/media/MT_Codec.h
Normal 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
|
||||
160
src/engine/media/MT_CodecList.cpp
Normal file
160
src/engine/media/MT_CodecList.cpp
Normal 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);
|
||||
}
|
||||
95
src/engine/media/MT_CodecList.h
Normal file
95
src/engine/media/MT_CodecList.h
Normal 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
|
||||
889
src/engine/media/MT_Dtmf.cpp
Normal file
889
src/engine/media/MT_Dtmf.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
97
src/engine/media/MT_Dtmf.h
Normal file
97
src/engine/media/MT_Dtmf.h
Normal 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
|
||||
121
src/engine/media/MT_NativeRtpSender.cpp
Normal file
121
src/engine/media/MT_NativeRtpSender.cpp
Normal 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;
|
||||
}
|
||||
|
||||
58
src/engine/media/MT_NativeRtpSender.h
Normal file
58
src/engine/media/MT_NativeRtpSender.h
Normal 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
|
||||
919
src/engine/media/MT_SevanaMos.cpp
Normal file
919
src/engine/media/MT_SevanaMos.cpp
Normal 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
|
||||
|
||||
208
src/engine/media/MT_SevanaMos.h
Normal file
208
src/engine/media/MT_SevanaMos.h
Normal 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
|
||||
42
src/engine/media/MT_SingleAudioStream.cpp
Normal file
42
src/engine/media/MT_SingleAudioStream.cpp
Normal 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");
|
||||
}
|
||||
|
||||
31
src/engine/media/MT_SingleAudioStream.h
Normal file
31
src/engine/media/MT_SingleAudioStream.h
Normal 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
|
||||
231
src/engine/media/MT_SrtpHelper.cpp
Normal file
231
src/engine/media/MT_SrtpHelper.cpp
Normal 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;
|
||||
}
|
||||
73
src/engine/media/MT_SrtpHelper.h
Normal file
73
src/engine/media/MT_SrtpHelper.h
Normal 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
|
||||
276
src/engine/media/MT_Statistics.cpp
Normal file
276
src/engine/media/MT_Statistics.cpp
Normal 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();
|
||||
}
|
||||
150
src/engine/media/MT_Statistics.h
Normal file
150
src/engine/media/MT_Statistics.h
Normal 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
|
||||
130
src/engine/media/MT_Stream.cpp
Normal file
130
src/engine/media/MT_Stream.cpp
Normal 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;
|
||||
}
|
||||
114
src/engine/media/MT_Stream.h
Normal file
114
src/engine/media/MT_Stream.h
Normal 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
|
||||
70
src/engine/media/MT_WebRtc.cpp
Normal file
70
src/engine/media/MT_WebRtc.cpp
Normal 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*/));
|
||||
}
|
||||
|
||||
|
||||
46
src/engine/media/MT_WebRtc.h
Normal file
46
src/engine/media/MT_WebRtc.h
Normal 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
|
||||
Reference in New Issue
Block a user