Commit a1f6c4ac authored by Alexandre Bourget's avatar Alexandre Bourget
Browse files

IAX: Dynamic codecs + Fixed samplerate conversion + Ability to receive calls

Added documentation.
parent 77751a2f
......@@ -20,13 +20,93 @@
#include "iaxcall.h"
#include "global.h" // for _debug
IAXCall::IAXCall(const CallID& id, Call::CallType type) : Call(id, type), _session(0)
IAXCall::IAXCall(const CallID& id, Call::CallType type) : Call(id, type), _session(NULL)
{
}
IAXCall::~IAXCall()
{
_session = 0; // just to be sure to don't have unknown pointer, do not delete it!
_session = NULL; // just to be sure to don't have unknown pointer, do not delete it!
}
void
IAXCall::setFormat(int format)
{
_format = format;
switch(format) {
case AST_FORMAT_ULAW:
setAudioCodec(_codecMap.getCodec(PAYLOAD_CODEC_ULAW)); break;
case AST_FORMAT_GSM:
setAudioCodec(_codecMap.getCodec(PAYLOAD_CODEC_GSM)); break;
case AST_FORMAT_ALAW:
setAudioCodec(_codecMap.getCodec(PAYLOAD_CODEC_ALAW)); break;
case AST_FORMAT_ILBC:
setAudioCodec(_codecMap.getCodec(PAYLOAD_CODEC_ILBC)); break;
case AST_FORMAT_SPEEX:
setAudioCodec(_codecMap.getCodec(PAYLOAD_CODEC_SPEEX)); break;
default:
setAudioCodec(NULL);
break;
}
}
int
IAXCall::getSupportedFormat()
{
CodecMap map = getCodecMap().getMap();
int format = 0;
CodecMap::iterator iter = map.begin();
while(iter != map.end()) {
switch(iter->first) {
case PAYLOAD_CODEC_ULAW:
format |= AST_FORMAT_ULAW; break;
case PAYLOAD_CODEC_GSM:
format |= AST_FORMAT_GSM; break;
case PAYLOAD_CODEC_ALAW:
format |= AST_FORMAT_ALAW; break;
case PAYLOAD_CODEC_ILBC:
format |= AST_FORMAT_ILBC; break;
case PAYLOAD_CODEC_SPEEX:
format |= AST_FORMAT_SPEEX; break;
default:
break;
}
iter++;
}
return format;
}
int
IAXCall::getFirstMatchingFormat(int needles)
{
CodecMap map = getCodecMap().getMap();
int format = 0;
CodecMap::iterator iter = map.begin();
while(iter != map.end()) {
switch(iter->first) {
case PAYLOAD_CODEC_ULAW:
format = AST_FORMAT_ULAW; break;
case PAYLOAD_CODEC_GSM:
format = AST_FORMAT_GSM; break;
case PAYLOAD_CODEC_ALAW:
format = AST_FORMAT_ALAW; break;
case PAYLOAD_CODEC_ILBC:
format = AST_FORMAT_ILBC; break;
case PAYLOAD_CODEC_SPEEX:
format = AST_FORMAT_SPEEX; break;
default:
break;
}
// Return the first that matches
if (format & needles)
return format;
iter++;
}
return 0;
}
......@@ -21,6 +21,7 @@
#include "call.h"
#include <iax/iax-client.h>
#include <iax/frame.h>
/**
* IAXCall are IAX implementation of a normal Call
......@@ -33,7 +34,7 @@ public:
~IAXCall();
/** Get the session pointer or 0 */
/** Get the session pointer or NULL */
struct iax_session* getSession() { return _session; }
/** Set the session pointer
......@@ -41,12 +42,49 @@ public:
*/
void setSession(struct iax_session* session) { _session = session; }
void setFormat(int format) { _format = format; }
/**
* Set format (one single bit
*
* This function sets the _audioCodec variable with the correct
* codec.
*/
void setFormat(int format);
/**
* Get format
*/
int getFormat() { return _format; }
/**
* Get the bitwise list of supported formats
*/
int getSupportedFormat();
/**
* Return a format (int) with the first matching codec selected.
*
* This considers the order of the appearance in the CodecMap,
* thus, the order of preference.
*
* NOTE: Everything returned is bound to the content of the local
* CodecMap, so it won't return format values that aren't valid
* in this call context.
*
* @param needles The format(s) (bitwise) you are looking for to match
* @return The matching format, thus 0 if none matches
*/
int getFirstMatchingFormat(int needles);
private:
// each call is associate to a session
/** Each call is associated with an iax_session */
struct iax_session* _session;
/**
* Format currently in use in the conversation,
* sent in each outgoing voice packet.
*/
int _format;
};
......
......@@ -27,6 +27,7 @@
#include <samplerate.h>
#include <iax/iax-client.h>
#include <math.h>
#define IAX_BLOCKING 1
......@@ -39,33 +40,14 @@
// from IAXC : iaxclient.h
/* payload formats : WARNING: must match libiax values!!! */
/* Data formats for capabilities and frames alike */
#define IAX__FORMAT_G723_1 (1 << 0) /* G.723.1 compression */
#define IAX__FORMAT_GSM (1 << 1) /* GSM compression */
#define IAX__FORMAT_ULAW (1 << 2) /* Raw mu-law data (G.711) */
#define IAX__FORMAT_ALAW (1 << 3) /* Raw A-law data (G.711) */
#define IAX__FORMAT_G726 (1 << 4) /* ADPCM, 32kbps */
#define IAX__FORMAT_ADPCM (1 << 5) /* ADPCM IMA */
#define IAX__FORMAT_SLINEAR (1 << 6) /* Raw 16-bit Signed Linear (8000 Hz) PCM */
#define IAX__FORMAT_LPC10 (1 << 7) /* LPC10, 180 samples/frame */
#define IAX__FORMAT_G729A (1 << 8) /* G.729a Audio */
#define IAX__FORMAT_SPEEX (1 << 9) /* Speex Audio */
#define IAX__FORMAT_ILBC (1 << 10) /* iLBC Audio */
#define IAX__FORMAT_MAX_AUDIO (1 << 15) /* Maximum audio format */
#define IAX__FORMAT_JPEG (1 << 16) /* JPEG Images */
#define IAX__FORMAT_PNG (1 << 17) /* PNG Images */
#define IAX__FORMAT_H261 (1 << 18) /* H.261 Video */
#define IAX__FORMAT_H263 (1 << 19) /* H.263 Video */
#define IAX__FORMAT_H263_PLUS (1 << 20) /* H.263+ Video */
#define IAX__FORMAT_MPEG4 (1 << 21) /* MPEG4 Video */
#define IAX__FORMAT_H264 (1 << 23) /* H264 Video */
#define IAX__FORMAT_THEORA (1 << 24) /* Theora Video */
#define IAX__20S_8KHZ_MAX 320 // 320 samples
#define IAX__20S_48KHZ_MAX 1920 // 320*6 samples, 6 = 48000/8000
#define CHK_VALID_CALL if (call == NULL) { _debug("IAX: Call doesn't exists\n"); \
return false; }
IAXVoIPLink::IAXVoIPLink(const AccountID& accountID)
: VoIPLink(accountID)
{
......@@ -75,7 +57,6 @@ IAXVoIPLink::IAXVoIPLink(const AccountID& accountID)
// to get random number for RANDOM_PORT
srand (time(NULL));
audiocodec = NULL;
audiolayer = NULL;
_receiveDataDecoded = new int16[IAX__20S_48KHZ_MAX];
......@@ -86,6 +67,11 @@ IAXVoIPLink::IAXVoIPLink(const AccountID& accountID)
_floatBuffer8000 = new float32[IAX__20S_8KHZ_MAX];
_floatBuffer48000 = new float32[IAX__20S_48KHZ_MAX];
_intBuffer8000 = new int16[IAX__20S_8KHZ_MAX];
// libsamplerate-related
_src_state_mic = src_new(SRC_SINC_BEST_QUALITY, 1, &_src_err);
_src_state_spkr = src_new(SRC_SINC_BEST_QUALITY, 1, &_src_err);
}
......@@ -95,7 +81,6 @@ IAXVoIPLink::~IAXVoIPLink()
_regSession = NULL; // shall not delete it
terminate();
audiocodec = NULL;
audiolayer = NULL;
delete [] _intBuffer8000; _intBuffer8000 = NULL;
delete [] _floatBuffer48000; _floatBuffer48000 = NULL;
......@@ -104,6 +89,10 @@ IAXVoIPLink::~IAXVoIPLink()
delete [] _sendDataEncoded; _sendDataEncoded = NULL;
delete [] _receiveDataDecoded; _receiveDataDecoded = NULL;
// libsamplerate-related
_src_state_mic = src_delete(_src_state_mic);
_src_state_spkr = src_delete(_src_state_spkr);
}
bool
......@@ -133,8 +122,6 @@ IAXVoIPLink::init()
_evThread->start();
// audio stuff, not dynamic yet
audiocodec = Manager::instance().getCodecDescriptorMap().getCodec((CodecType)0);
audiolayer = Manager::instance().getAudioDriver();
break;
}
......@@ -167,8 +154,8 @@ IAXVoIPLink::terminateIAXCall()
_mutexIAX.enterMutex();
iax_hangup(call->getSession(),"Dumped Call");
_mutexIAX.leaveMutex();
call->setSession(0);
delete call; call = 0;
call->setSession(NULL);
delete call; call = NULL;
}
iter++;
}
......@@ -183,45 +170,45 @@ IAXVoIPLink::getEvent()
iax_event* event = NULL;
IAXCall* call = NULL;
while ( (event = iax_get_event(IAX_BLOCKING)) != NULL ) {
//_debug ("Receive IAX Event: %d\n", event->etype);
while ( (event = iax_get_event(IAX_NONBLOCKING)) != NULL ) {
// If we received an 'ACK', libiax2 tells apps to ignore them.
if (event->etype == IAX_EVENT_NULL) {
continue;
}
_debug ("Receive IAX Event: %d (0x%x)\n", event->etype, event->etype);
call = iaxFindCallBySession(event->session);
if (call != 0) {
if (call) {
_debug(" - We've got an associated call, handle call event\n");
iaxHandleCallEvent(event, call);
} else if (event->session != 0 && event->session == _regSession) {
} else if (event->session && event->session == _regSession) {
_debug(" - We've got an associated REGISTRATION session, handle registration process\n");
// in iaxclient, there is many session handling, here, only one
iaxHandleRegReply(event);
} else {
switch(event->etype) {
case IAX_EVENT_REGACK:
case IAX_EVENT_REGREJ:
_debug("Unknown IAX Registration Event\n");
break;
case IAX_EVENT_REGREQ:
_debug("Registration by a peer, don't allow it\n");
break;
case IAX_EVENT_CONNECT: // new call
// New incoming call!
break;
case IAX_EVENT_TIMEOUT: // timeout for an unknown session
} else {
break;
_debug (" - We've got some other event, deal with them alone.\n");
iaxHandlePrecallEvent(event);
default:
_debug("Unknown event type: %d\n", event->etype);
}
}
iax_event_free(event);
}
// Woah, we should do that in another thread, which will always send out stuff..
// send sound here
if(_currentCall != 0 && audiolayer != 0) {
if(_currentCall && audiolayer) {
int samples = audiolayer->canGetMic();
if (samples != 0) {
int datalen = audiolayer->getMic(_sendDataEncoded, samples);
_debug("iax_send_voice(%p, %d, ,%d, %d)\n", _currentCall->getSession(), _currentCall->getFormat(), datalen, samples);
//int datalen = audiolayer->getMic(_sendDataEncoded, samples);
//_debug("iax_send_voice(%p, %d, ,%d, %d)\n", _currentCall->getSession(), _currentCall->getFormat(), datalen, samples);
//if ( iax_send_voice(_currentCall->getSession(), _currentCall->getFormat(), (char*)_sendDataEncoded, datalen, samples) == -1) {
// // error sending voice
//}
......@@ -236,6 +223,22 @@ IAXVoIPLink::getEvent()
_evThread->sleep(5);
}
IAXCall*
IAXVoIPLink::getIAXCall(const CallID& id)
{
Call* call = getCall(id);
if (call) {
return dynamic_cast<IAXCall*>(call);
}
return 0;
}
bool
IAXVoIPLink::setRegister()
{
......@@ -278,10 +281,13 @@ IAXVoIPLink::setRegister()
return result;
}
bool
IAXVoIPLink::setUnregister()
{
if (_regSession == NULL) {
if (_regSession) {
// lock here
_mutexIAX.enterMutex();
iax_destroy(_regSession);
......@@ -297,60 +303,70 @@ Call*
IAXVoIPLink::newOutgoingCall(const CallID& id, const std::string& toUrl)
{
IAXCall* call = new IAXCall(id, Call::Outgoing);
if (call) {
call->setPeerNumber(toUrl);
// we have to add the codec before using it in SIPOutgoingInvite...
//call->setCodecMap(Manager::instance().getCodecDescriptorMap());
if ( iaxOutgoingInvite(call) ) {
call->setConnectionState(Call::Progressing);
call->setState(Call::Active);
addCall(call);
} else {
delete call; call = 0;
delete call; call = NULL;
}
}
return call;
}
bool
IAXVoIPLink::answer(const CallID& id)
IAXVoIPLink::answer(const CallID& id)
{
IAXCall* call = getIAXCall(id);
if (call==0) { _debug("Call doesn't exists\n"); return false; }
CHK_VALID_CALL;
_mutexIAX.enterMutex();
iax_answer(call->getSession());
_mutexIAX.leaveMutex();
call->setState(Call::Active);
call->setConnectionState(Call::Connected);
return true;
}
bool
IAXVoIPLink::hangup(const CallID& id)
{
IAXCall* call = getIAXCall(id);
if (call==0) { _debug("Call doesn't exists\n"); return false; }
_mutexIAX.enterMutex();
iax_hangup(call->getSession(),"Dumped Call");
_mutexIAX.leaveMutex();
call->setSession(0);
if (Manager::instance().isCurrentCall(id)) {
// stop audio
}
removeCall(id);
return true;
IAXCall* call = getIAXCall(id);
CHK_VALID_CALL;
_mutexIAX.enterMutex();
iax_hangup(call->getSession(), "Dumped Call");
_mutexIAX.leaveMutex();
call->setSession(NULL);
if (Manager::instance().isCurrentCall(id)) {
// stop audio
}
removeCall(id);
return true;
}
bool
IAXVoIPLink::onhold(const CallID& id)
{
IAXCall* call = getIAXCall(id);
if (call==0) { _debug("Call doesn't exists\n"); return false; }
CHK_VALID_CALL;
//if (call->getState() == Call::Hold) { _debug("Call is already on hold\n"); return false; }
_mutexIAX.enterMutex();
iax_quelch(call->getSession());
_mutexIAX.leaveMutex();
call->setState(Call::Hold);
return true;
}
......@@ -359,7 +375,9 @@ bool
IAXVoIPLink::offhold(const CallID& id)
{
IAXCall* call = getIAXCall(id);
if (call==0) { _debug("Call doesn't exists\n"); return false; }
CHK_VALID_CALL;
//if (call->getState() == Call::Active) { _debug("Call is already active\n"); return false; }
_mutexIAX.enterMutex();
iax_unquelch(call->getSession());
......@@ -372,7 +390,8 @@ bool
IAXVoIPLink::transfer(const CallID& id, const std::string& to)
{
IAXCall* call = getIAXCall(id);
if (call==0) { _debug("Call doesn't exists\n"); return false; }
CHK_VALID_CALL;
char callto[to.length()+1];
strcpy(callto, to.c_str());
......@@ -389,7 +408,9 @@ bool
IAXVoIPLink::refuse(const CallID& id)
{
IAXCall* call = getIAXCall(id);
if (call==0) { _debug("Call doesn't exists\n"); return false; }
CHK_VALID_CALL;
_mutexIAX.enterMutex();
iax_reject(call->getSession(), "Call rejected manually.");
_mutexIAX.leaveMutex();
......@@ -400,23 +421,25 @@ bool
IAXVoIPLink::carryingDTMFdigits(const CallID& id, char code)
{
IAXCall* call = getIAXCall(id);
if (call==0) { _debug("Call doesn't exists\n"); return false; }
CHK_VALID_CALL;
_mutexIAX.enterMutex();
iax_send_dtmf(call->getSession(), code);
_mutexIAX.leaveMutex();
}
bool
IAXVoIPLink::iaxOutgoingInvite(IAXCall* call)
{
struct iax_session *newsession;
// lock here
_mutexIAX.enterMutex();
ost::MutexLock m(_mutexIAX);
newsession = iax_session_new();
if (!newsession) {
_debug("IAX Error: Can't make new session for a new call\n");
// unlock here
_mutexIAX.leaveMutex();
return false;
}
call->setSession(newsession);
......@@ -433,14 +456,13 @@ IAXVoIPLink::iaxOutgoingInvite(IAXCall* call)
char* lang = NULL;
int wait = 0;
int audio_format_preferred = IAX__FORMAT_ULAW;
int audio_format_capability = IAX__FORMAT_ULAW; // | IAX__FORMAT_ALAW | IAX__FORMAT_GSM | IAX__FORMAT_SPEEX;
/** @todo Make preference dynamic, and configurable */
int audio_format_preferred = call->getFirstMatchingFormat(call->getSupportedFormat());
int audio_format_capability = call->getSupportedFormat();
_debug("IAX New call: %s\n", num);
iax_call(newsession, user, user, num, lang, wait, audio_format_preferred, audio_format_capability);
// unlock here
_mutexIAX.leaveMutex();
return true;
}
......@@ -451,7 +473,7 @@ IAXVoIPLink::iaxFindCallBySession(struct iax_session* session)
// access to callMap shoud use that
// the code below is like findSIPCallWithCid()
ost::MutexLock m(_callMapMutex);
IAXCall* call = 0;
IAXCall* call = NULL;
CallMap::iterator iter = _callMap.begin();
while(iter != _callMap.end()) {
call = dynamic_cast<IAXCall*>(iter->second);
......@@ -460,7 +482,7 @@ IAXVoIPLink::iaxFindCallBySession(struct iax_session* session)
}
iter++;
}
return 0; // not found
return NULL; // not found
}
void
......@@ -471,162 +493,286 @@ IAXVoIPLink::iaxHandleCallEvent(iax_event* event, IAXCall* call)
//
CallID id = call->getCallId();
int16* output = 0; // for audio output
switch(event->etype) {
case IAX_EVENT_HANGUP:
Manager::instance().peerHungupCall(id);
if (Manager::instance().isCurrentCall(id)) {
_currentCall = 0;
audiolayer->stopStream();
// stop audio
}
removeCall(id);
case IAX_EVENT_HANGUP:
Manager::instance().peerHungupCall(id);
if (Manager::instance().isCurrentCall(id)) {
_currentCall = 0;
audiolayer->stopStream();
// stop audio
}
removeCall(id);
break;
case IAX_EVENT_REJECT:
Manager::instance().peerHungupCall(id);
if (Manager::instance().isCurrentCall(id)) {
// stop audio
_currentCall = 0;
audiolayer->stopStream();
}
removeCall(id);
break;
case IAX_EVENT_REJECT:
Manager::instance().peerHungupCall(id);
if (Manager::instance().isCurrentCall(id)) {
// stop audio
_currentCall = 0;
audiolayer->stopStream();
}
removeCall(id);
break;
case IAX_EVENT_ACCEPT:
// accept
//
case IAX_EVENT_ACCEPT:
// accept
//
if (event->ies.format) {
call->setFormat(event->ies.format);
break;
}
case IAX_EVENT_ANSWER:
if (call->getConnectionState() != Call::Connected){
call->setConnectionState(Call::Connected);
call->setState(Call::Active);
call->setFormat(event->ies.format);
Manager::instance().peerAnsweredCall(id);
_currentCall = call;
audiolayer->startStream();
// start audio here?
} else {
// deja connecté
// ?
}
break;
case IAX_EVENT_BUSY:
case IAX_EVENT_ANSWER:
if (call->getConnectionState() != Call::Connected){
call->setConnectionState(Call::Connected);
call->setState(Call::Busy);