1596 lines
42 KiB
C
1596 lines
42 KiB
C
/*
|
|
Copyright (C) 2009 - 2010
|
|
|
|
Artem Makhutov <artem@makhutov.org>
|
|
http://www.makhutov.org
|
|
|
|
Dmitry Vagin <dmitry2004@yandex.ru>
|
|
|
|
bg <bg_one@mail.ru>
|
|
*/
|
|
#include "ast_config.h"
|
|
|
|
#include <asterisk/dsp.h> /* ast_dsp_digitreset() */
|
|
#include <asterisk/pbx.h> /* pbx_builtin_setvar_helper() */
|
|
#include <asterisk/module.h> /* ast_module_ref() ast_module_info = shit */
|
|
#include <asterisk/causes.h> /* AST_CAUSE_INCOMPATIBLE_DESTINATION AST_CAUSE_FACILITY_NOT_IMPLEMENTED AST_CAUSE_REQUESTED_CHAN_UNAVAIL */
|
|
#include <asterisk/musiconhold.h> /* ast_moh_start() ast_moh_stop() */
|
|
#include <asterisk/lock.h> /* AST_MUTEX_DEFINE_STATIC */
|
|
#include <asterisk/timing.h> /* ast_timer_fd() ast_timer_set_rate() ast_timer_ack() */
|
|
|
|
#include "ast_compat.h"
|
|
#if ASTERISK_VERSION_NUM >= 130000 /* 13+ */
|
|
#include <asterisk/stasis_channels.h>
|
|
#include <asterisk/format_cache.h>
|
|
#endif /* ^13+ */
|
|
|
|
#include "channel.h"
|
|
#include "chan_dongle.h"
|
|
#include "at_command.h"
|
|
#include "helpers.h" /* get_at_clir_value() */
|
|
#include "at_queue.h" /* write_all() TODO: move out */
|
|
#include "manager.h" /* manager_event_call_state_change() */
|
|
|
|
|
|
static char silence_frame[FRAME_SIZE];
|
|
|
|
#/* */
|
|
static int parse_dial_string(char * dialstr, const char** number, int * opts)
|
|
{
|
|
char* options;
|
|
char* dest_num;
|
|
int lopts = 0;
|
|
|
|
options = strchr (dialstr, '/');
|
|
if (!options)
|
|
{
|
|
ast_log (LOG_WARNING, "Can't determine destination in chan_dongle\n");
|
|
return AST_CAUSE_INCOMPATIBLE_DESTINATION;
|
|
}
|
|
*options++ = '\0';
|
|
|
|
dest_num = strchr(options, ':');
|
|
if(!dest_num)
|
|
{
|
|
dest_num = options;
|
|
}
|
|
else
|
|
{
|
|
*dest_num++ = '\0';
|
|
|
|
if (!strcasecmp(options, "holdother"))
|
|
lopts = CALL_FLAG_HOLD_OTHER;
|
|
else if (!strcasecmp(options, "conference"))
|
|
lopts = CALL_FLAG_HOLD_OTHER | CALL_FLAG_CONFERENCE;
|
|
else
|
|
{
|
|
ast_log (LOG_WARNING, "Invalid options in chan_dongle\n");
|
|
return AST_CAUSE_INCOMPATIBLE_DESTINATION;
|
|
}
|
|
}
|
|
|
|
if (*dest_num == '\0')
|
|
{
|
|
ast_log (LOG_WARNING, "Empty destination in chan_dongle\n");
|
|
return AST_CAUSE_INCOMPATIBLE_DESTINATION;
|
|
}
|
|
if (!is_valid_phone_number(dest_num))
|
|
{
|
|
ast_log (LOG_WARNING, "Invalid destination '%s' in chan_dongle, only 0123456789*#+ABC allowed\n", dest_num);
|
|
return AST_CAUSE_INCOMPATIBLE_DESTINATION;
|
|
}
|
|
|
|
*number = dest_num;
|
|
*opts = lopts;
|
|
return 0;
|
|
}
|
|
|
|
|
|
#/* */
|
|
EXPORT_DEF int channels_loop(struct pvt * pvt, const struct ast_channel * requestor)
|
|
{
|
|
/* not allow hold requester channel :) */
|
|
/* FIXME: requestor may be just proxy/masquerade for real channel */
|
|
// use ast_bridged_channel(chan) ?
|
|
// use requestor->tech->get_base_channel() ?
|
|
struct cpvt *tmp;
|
|
return (requestor
|
|
&& ast_channel_tech(requestor) == &channel_tech
|
|
&& (tmp = ast_channel_tech_pvt(requestor))
|
|
&& tmp->pvt == pvt)
|
|
? 1
|
|
: 0;
|
|
}
|
|
|
|
#if ASTERISK_VERSION_NUM >= 120000 /* 12+ */
|
|
static struct ast_channel * channel_request(
|
|
attribute_unused const char * type, struct ast_format_cap * cap,
|
|
const struct ast_assigned_ids * assignedids,
|
|
const struct ast_channel * requestor, const char * data, int * cause)
|
|
#elif ASTERISK_VERSION_NUM >= 110000 /* 11+ */
|
|
static struct ast_channel * channel_request(
|
|
attribute_unused const char * type, struct ast_format_cap * cap,
|
|
const struct ast_channel * requestor, const char * data, int * cause)
|
|
#elif ASTERISK_VERSION_NUM >= 100000 /* 10+ */
|
|
static struct ast_channel * channel_request(
|
|
attribute_unused const char * type, struct ast_format_cap * cap,
|
|
const struct ast_channel * requestor, void * data, int * cause)
|
|
#elif ASTERISK_VERSION_NUM >= 10800 /* 1.8+ */
|
|
static struct ast_channel * channel_request(
|
|
attribute_unused const char * type, format_t format,
|
|
const struct ast_channel * requestor, void * data, int * cause)
|
|
#else /* 1.8- */
|
|
static struct ast_channel * channel_request(
|
|
attribute_unused const char * type, int format, void * data, int * cause)
|
|
#endif /* ^1.8- */
|
|
{
|
|
/* TODO: simplify by moving common code to functions */
|
|
/* TODO: add check when request 'holdother' what requestor is not on same device for 1.6 */
|
|
#if ASTERISK_VERSION_NUM >= 10800 && ASTERISK_VERSION_NUM < 100000 /* 1.8+ .. 10- */
|
|
format_t oldformat;
|
|
#elif ASTERISK_VERSION_NUM < 10800 /* 1.8- */
|
|
int oldformat;
|
|
const struct ast_channel * requestor = NULL;
|
|
#endif /* ^1.8- */
|
|
char * dest_dev;
|
|
const char * dest_num;
|
|
struct ast_channel * channel = NULL;
|
|
struct pvt * pvt;
|
|
int opts = CALL_FLAG_NONE;
|
|
int exists;
|
|
|
|
if (!data)
|
|
{
|
|
ast_log (LOG_WARNING, "Channel requested with no data\n");
|
|
*cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
|
|
return NULL;
|
|
}
|
|
|
|
#if ASTERISK_VERSION_NUM >= 130000 /* 13+ */
|
|
if (ast_format_cap_iscompatible_format(cap, ast_format_slin) != AST_FORMAT_CMP_EQUAL)
|
|
{
|
|
struct ast_str *codec_buf = ast_str_alloca(64);
|
|
ast_log(LOG_WARNING, "Asked to get a channel of unsupported format '%s'\n",
|
|
ast_format_cap_get_names(cap, &codec_buf));
|
|
*cause = AST_CAUSE_FACILITY_NOT_IMPLEMENTED;
|
|
return NULL;
|
|
}
|
|
#elif ASTERISK_VERSION_NUM >= 100000 /* 10-13 */
|
|
if (!ast_format_cap_iscompatible(cap, &chan_dongle_format))
|
|
{
|
|
char buf[255];
|
|
ast_log(LOG_WARNING, "Asked to get a channel of unsupported format '%s'\n",
|
|
ast_getformatname_multiple(buf, 255, cap));
|
|
*cause = AST_CAUSE_FACILITY_NOT_IMPLEMENTED;
|
|
return NULL;
|
|
}
|
|
#else /* 10- */
|
|
oldformat = format;
|
|
format &= AST_FORMAT_SLINEAR;
|
|
if (!format)
|
|
{
|
|
#if ASTERISK_VERSION_NUM >= 10800 /* 1.8+ */
|
|
ast_log(LOG_WARNING, "Asked to get a channel of unsupported format '%s'\n",
|
|
ast_getformatname(oldformat));
|
|
#else /* 1.8- */
|
|
ast_log(LOG_WARNING, "Asked to get a channel of unsupported format '%d'\n",
|
|
oldformat);
|
|
#endif /* ^1.8- */
|
|
*cause = AST_CAUSE_FACILITY_NOT_IMPLEMENTED;
|
|
return NULL;
|
|
}
|
|
#endif /* ^10- */
|
|
|
|
dest_dev = ast_strdupa (data);
|
|
|
|
*cause = parse_dial_string(dest_dev, &dest_num, &opts);
|
|
if(*cause)
|
|
return NULL;
|
|
|
|
#if ASTERISK_VERSION_NUM >= 10800
|
|
pvt = find_device_by_resource(dest_dev, opts, requestor, &exists);
|
|
#else /* 1.8- */
|
|
pvt = find_device_by_resource(dest_dev, opts, NULL, &exists);
|
|
#endif /* ^1.8- */
|
|
|
|
if(pvt)
|
|
{
|
|
#if ASTERISK_VERSION_NUM >= 120000 /* 12+ */
|
|
channel = new_channel(pvt, AST_STATE_DOWN, NULL, pvt_get_pseudo_call_idx(pvt),
|
|
CALL_DIR_OUTGOING, CALL_STATE_INIT, NULL, assignedids, requestor);
|
|
#else /* 12- */
|
|
channel = new_channel(pvt, AST_STATE_DOWN, NULL, pvt_get_pseudo_call_idx(pvt),
|
|
CALL_DIR_OUTGOING, CALL_STATE_INIT, NULL, requestor);
|
|
#endif /* ^12- */
|
|
ast_mutex_unlock (&pvt->lock);
|
|
if(!channel)
|
|
{
|
|
ast_log (LOG_WARNING, "Unable to allocate channel structure\n");
|
|
*cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ast_log (LOG_WARNING, "[%s] Request to call on device %s\n", dest_dev, exists ? "which can not make call at this moment" : "not exists");
|
|
*cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
|
|
}
|
|
|
|
return channel;
|
|
}
|
|
|
|
#/* */
|
|
#if ASTERISK_VERSION_NUM >= 110000 /* 11+ */
|
|
static int channel_call(struct ast_channel* channel, const char *dest, attribute_unused int timeout)
|
|
#else /* 11- */
|
|
static int channel_call(struct ast_channel* channel, char* dest, attribute_unused int timeout)
|
|
#endif /* ^11- */
|
|
{
|
|
struct cpvt* cpvt = ast_channel_tech_pvt(channel);
|
|
struct pvt* pvt;
|
|
char* dest_dev;
|
|
const char* dest_num;
|
|
int clir = 0;
|
|
int opts;
|
|
|
|
if(!cpvt || cpvt->channel != channel || !cpvt->pvt)
|
|
{
|
|
ast_log (LOG_WARNING, "call on unreferenced %s\n", ast_channel_name(channel));
|
|
return -1;
|
|
}
|
|
pvt = cpvt->pvt;
|
|
|
|
dest_dev = ast_strdupa (dest);
|
|
|
|
if(parse_dial_string(dest_dev, &dest_num, &opts))
|
|
return -1;
|
|
|
|
if ((ast_channel_state(channel) != AST_STATE_DOWN) && (ast_channel_state(channel) != AST_STATE_RESERVED))
|
|
{
|
|
ast_log (LOG_WARNING, "channel_call called on %s, neither down nor reserved\n", ast_channel_name(channel));
|
|
return -1;
|
|
}
|
|
|
|
ast_mutex_lock (&pvt->lock);
|
|
|
|
// FIXME: check if bridged on same device with CALL_FLAG_HOLD_OTHER
|
|
if (!ready4voice_call(pvt, cpvt, opts))
|
|
{
|
|
ast_mutex_unlock (&pvt->lock);
|
|
ast_log (LOG_ERROR, "[%s] Error device already in use or uninitialized\n", PVT_ID(pvt));
|
|
return -1;
|
|
}
|
|
CPVT_SET_FLAGS(cpvt, opts);
|
|
|
|
ast_debug (1, "[%s] Calling %s on %s\n", PVT_ID(pvt), dest, ast_channel_name(channel));
|
|
|
|
if (CONF_SHARED(pvt, usecallingpres))
|
|
{
|
|
if (CONF_SHARED(pvt, callingpres) < 0)
|
|
{
|
|
#if ASTERISK_VERSION_NUM >= 10800 /* 1.8+ */
|
|
clir = ast_channel_connected(channel)->id.number.presentation;
|
|
#else /* 1.8- */
|
|
clir = channel->cid.cid_pres;
|
|
#endif /* ^1.8- */
|
|
}
|
|
else
|
|
{
|
|
clir = CONF_SHARED(pvt, callingpres);
|
|
}
|
|
|
|
clir = get_at_clir_value (pvt, clir);
|
|
}
|
|
else
|
|
{
|
|
clir = -1;
|
|
}
|
|
|
|
PVT_STAT(pvt, out_calls) ++;
|
|
if (at_enqueue_dial(cpvt, dest_num, clir))
|
|
{
|
|
ast_mutex_unlock (&pvt->lock);
|
|
ast_log (LOG_ERROR, "[%s] Error sending ATD command\n", PVT_ID(pvt));
|
|
return -1;
|
|
}
|
|
|
|
ast_mutex_unlock (&pvt->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#/* ARCH: move to cpvt level */
|
|
static void disactivate_call(struct cpvt* cpvt)
|
|
{
|
|
if(cpvt->channel && CPVT_TEST_FLAG(cpvt, CALL_FLAG_ACTIVATED))
|
|
{
|
|
mixb_detach(&cpvt->pvt->a_write_mixb, &cpvt->mixstream);
|
|
ast_channel_set_fd (cpvt->channel, 1, -1);
|
|
ast_channel_set_fd (cpvt->channel, 0, -1);
|
|
CPVT_RESET_FLAGS(cpvt, CALL_FLAG_ACTIVATED | CALL_FLAG_MASTER);
|
|
|
|
ast_debug (6, "[%s] call idx %d disactivated\n", PVT_ID(cpvt->pvt), cpvt->call_idx);
|
|
}
|
|
}
|
|
|
|
#/* ARCH: move to cpvt level */
|
|
static void activate_call(struct cpvt* cpvt)
|
|
{
|
|
struct cpvt* cpvt2;
|
|
struct pvt* pvt;
|
|
|
|
/* nothing todo, already main */
|
|
if(CPVT_TEST_FLAG(cpvt, CALL_FLAG_MASTER))
|
|
return;
|
|
|
|
/* drop any other from MASTER, any set pipe for actives */
|
|
pvt = cpvt->pvt;
|
|
AST_LIST_TRAVERSE(&pvt->chans, cpvt2, entry)
|
|
{
|
|
if(cpvt2 != cpvt)
|
|
{
|
|
if(CPVT_TEST_FLAG(cpvt, CALL_FLAG_MASTER))
|
|
{
|
|
ast_debug (6, "[%s] call idx %d gave master\n", PVT_ID(pvt), cpvt2->call_idx);
|
|
}
|
|
|
|
CPVT_RESET_FLAGS(cpvt2, CALL_FLAG_MASTER);
|
|
if(cpvt2->channel)
|
|
{
|
|
ast_channel_set_fd (cpvt2->channel, 1, -1);
|
|
if(CPVT_TEST_FLAG(cpvt, CALL_FLAG_ACTIVATED))
|
|
{
|
|
ast_channel_set_fd (cpvt2->channel, 0, cpvt2->rd_pipe[PIPE_READ]);
|
|
ast_debug (6, "[%s] call idx %d still active fd %d\n", PVT_ID(pvt), cpvt2->call_idx, cpvt2->rd_pipe[PIPE_READ]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* setup call local write possition */
|
|
if(!CPVT_TEST_FLAG(cpvt, CALL_FLAG_ACTIVATED))
|
|
{
|
|
// FIXME: reset possition?
|
|
mixb_attach(&pvt->a_write_mixb, &cpvt->mixstream);
|
|
// rb_init (&cpvt->a_write_rb, cpvt->a_write_buf, sizeof (cpvt->a_write_buf));
|
|
// cpvt->write = pvt->a_write_rb.write;
|
|
// cpvt->used = pvt->a_write_rb.used;
|
|
}
|
|
|
|
if (pvt->audio_fd >= 0)
|
|
{
|
|
CPVT_SET_FLAGS(cpvt, CALL_FLAG_ACTIVATED | CALL_FLAG_MASTER);
|
|
if(cpvt->channel)
|
|
{
|
|
ast_channel_set_fd (cpvt->channel, 0, pvt->audio_fd);
|
|
if (pvt->a_timer)
|
|
{
|
|
ast_channel_set_fd (cpvt->channel, 1, ast_timer_fd (pvt->a_timer));
|
|
ast_timer_set_rate (pvt->a_timer, 50);
|
|
/* ast_debug (3, "[%s] Timer set\n", PVT_ID(pvt));
|
|
*/
|
|
}
|
|
}
|
|
if(pvt->dsp)
|
|
ast_dsp_digitreset(pvt->dsp);
|
|
pvt->dtmf_digit = 0;
|
|
ast_debug (6, "[%s] call idx %d was master\n", PVT_ID(pvt), cpvt->call_idx);
|
|
}
|
|
}
|
|
|
|
#/* we has 2 case of call this function, when local side want terminate call and when called for cleanup after remote side alreay terminate call, CEND received and cpvt destroyed */
|
|
static int channel_hangup (struct ast_channel* channel)
|
|
{
|
|
struct cpvt* cpvt = ast_channel_tech_pvt(channel);
|
|
struct pvt* pvt;
|
|
|
|
/* its possible call with channel w/o tech_pvt */
|
|
if(cpvt && cpvt->channel == channel && cpvt->pvt)
|
|
{
|
|
pvt = cpvt->pvt;
|
|
|
|
ast_mutex_lock (&pvt->lock);
|
|
|
|
ast_debug (1, "[%s] Hanging up call idx %d need hangup %d\n", PVT_ID(pvt), cpvt->call_idx, CPVT_TEST_FLAG(cpvt, CALL_FLAG_NEED_HANGUP) ? 1 : 0);
|
|
|
|
if (CPVT_TEST_FLAG(cpvt, CALL_FLAG_NEED_HANGUP))
|
|
{
|
|
if (at_enqueue_hangup(cpvt, cpvt->call_idx))
|
|
ast_log (LOG_ERROR, "[%s] Error adding AT+CHUP command to queue, call not terminated!\n", PVT_ID(pvt));
|
|
else
|
|
CPVT_RESET_FLAGS(cpvt, CALL_FLAG_NEED_HANGUP);
|
|
|
|
}
|
|
|
|
disactivate_call (cpvt);
|
|
|
|
/* drop cpvt->channel reference */
|
|
cpvt->channel = NULL;
|
|
ast_mutex_unlock (&pvt->lock);
|
|
}
|
|
|
|
/* drop channel -> cpvt reference */
|
|
ast_channel_tech_pvt_set(channel, NULL);
|
|
|
|
ast_module_unref (self_module());
|
|
ast_setstate (channel, AST_STATE_DOWN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#/* */
|
|
static int channel_answer (struct ast_channel* channel)
|
|
{
|
|
struct cpvt* cpvt = ast_channel_tech_pvt(channel);
|
|
struct pvt* pvt;
|
|
|
|
if(!cpvt || cpvt->channel != channel || !cpvt->pvt)
|
|
{
|
|
ast_log (LOG_WARNING, "call on unreferenced %s\n", ast_channel_name(channel));
|
|
return 0;
|
|
}
|
|
pvt = cpvt->pvt;
|
|
|
|
ast_mutex_lock (&pvt->lock);
|
|
|
|
if (cpvt->dir == CALL_DIR_INCOMING)
|
|
{
|
|
if (at_enqueue_answer(cpvt))
|
|
{
|
|
ast_log (LOG_ERROR, "[%s] Error sending answer commands\n", PVT_ID(pvt));
|
|
}
|
|
}
|
|
|
|
ast_mutex_unlock (&pvt->lock);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#/* */
|
|
static int channel_digit_begin (struct ast_channel* channel, char digit)
|
|
{
|
|
struct cpvt* cpvt = ast_channel_tech_pvt(channel);
|
|
struct pvt* pvt;
|
|
int rv;
|
|
|
|
if(!cpvt || cpvt->channel != channel || !cpvt->pvt)
|
|
{
|
|
ast_log (LOG_WARNING, "call on unreferenced %s\n", ast_channel_name(channel));
|
|
return -1;
|
|
}
|
|
pvt = cpvt->pvt;
|
|
|
|
ast_mutex_lock (&pvt->lock);
|
|
|
|
rv = at_enqueue_dtmf(cpvt, digit);
|
|
if (rv)
|
|
{
|
|
ast_mutex_unlock (&pvt->lock);
|
|
if(rv == -1974)
|
|
ast_log (LOG_WARNING, "[%s] Sending DTMF %c not supported by dongle. Tell Asterisk to generate inband\n", PVT_ID(pvt), digit);
|
|
else
|
|
ast_log (LOG_ERROR, "[%s] Error adding DTMF %c command to queue\n", PVT_ID(pvt), digit);
|
|
return -1;
|
|
}
|
|
|
|
ast_mutex_unlock (&pvt->lock);
|
|
|
|
ast_debug (3, "[%s] Send DTMF %c\n", PVT_ID(pvt), digit);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#/* */
|
|
static int channel_digit_end (attribute_unused struct ast_channel* channel, attribute_unused char digit, attribute_unused unsigned int duration)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#/* ARCH: move to cpvt level */
|
|
static void iov_write(struct pvt* pvt, int fd, struct iovec * iov, int iovcnt)
|
|
{
|
|
ssize_t written;
|
|
ssize_t done = 0;
|
|
int count = 10;
|
|
|
|
while(iovcnt)
|
|
{
|
|
again:
|
|
written = writev (fd, iov, iovcnt);
|
|
if(written < 0)
|
|
{
|
|
if((errno == EINTR || errno == EAGAIN))
|
|
{
|
|
--count;
|
|
if(count != 0) {
|
|
goto again;
|
|
}
|
|
ast_debug (1, "[%s] Deadlock avoided for write!\n", PVT_ID(pvt));
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
done += written;
|
|
count = 10;
|
|
do
|
|
{
|
|
if((size_t)written >= iov->iov_len)
|
|
{
|
|
written -= iov->iov_len;
|
|
iovcnt--;
|
|
iov++;
|
|
}
|
|
else
|
|
{
|
|
iov->iov_len -= written;
|
|
goto again;
|
|
}
|
|
} while(written > 0);
|
|
}
|
|
}
|
|
PVT_STAT(pvt, a_write_bytes) += done;
|
|
|
|
if (done != FRAME_SIZE)
|
|
{
|
|
ast_debug (1, "[%s] Write error!\n", PVT_ID(pvt));
|
|
}
|
|
}
|
|
|
|
static inline void change_audio_endianness_to_le(
|
|
attribute_unused struct iovec *iov, attribute_unused int iovcnt)
|
|
{
|
|
#if __BYTE_ORDER == __BIG_ENDIAN
|
|
for (; iovcnt-- > 0; ++iov) {
|
|
ast_swapcopy_samples(iov->iov_base, iov->iov_base, iov->iov_len / 2);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#/* */
|
|
static void timing_write(struct pvt* pvt)
|
|
{
|
|
size_t used;
|
|
int iovcnt;
|
|
struct iovec iov[3];
|
|
const char* msg = NULL;
|
|
// char buffer[FRAME_SIZE];
|
|
// struct cpvt* cpvt;
|
|
|
|
// ast_debug (6, "[%s] tm write |\n", PVT_ID(pvt));
|
|
|
|
// memset(buffer, 0, sizeof(buffer));
|
|
|
|
// AST_LIST_TRAVERSE(&pvt->chans, cpvt, entry) {
|
|
|
|
// if(!CPVT_IS_ACTIVE(cpvt))
|
|
// continue;
|
|
|
|
used = mixb_used (&pvt->a_write_mixb);
|
|
// used = rb_used (&cpvt->a_write_rb);
|
|
|
|
if (used >= FRAME_SIZE)
|
|
{
|
|
iovcnt = mixb_read_n_iov (&pvt->a_write_mixb, iov, FRAME_SIZE);
|
|
mixb_read_n_iov (&pvt->a_write_mixb, iov, FRAME_SIZE);
|
|
mixb_read_upd (&pvt->a_write_mixb, FRAME_SIZE);
|
|
change_audio_endianness_to_le(iov, iovcnt);
|
|
}
|
|
else if (used > 0)
|
|
{
|
|
PVT_STAT(pvt, write_tframes) ++;
|
|
msg = "[%s] write truncated frame\n";
|
|
|
|
iovcnt = mixb_read_all_iov (&pvt->a_write_mixb, iov);
|
|
mixb_read_all_iov (&pvt->a_write_mixb, iov);
|
|
mixb_read_upd (&pvt->a_write_mixb, used);
|
|
|
|
iov[iovcnt].iov_base = silence_frame;
|
|
iov[iovcnt].iov_len = FRAME_SIZE - used;
|
|
iovcnt++;
|
|
change_audio_endianness_to_le(iov, iovcnt);
|
|
}
|
|
else
|
|
{
|
|
PVT_STAT(pvt, write_sframes) ++;
|
|
msg = "[%s] write silence\n";
|
|
|
|
iov[0].iov_base = silence_frame;
|
|
iov[0].iov_len = FRAME_SIZE;
|
|
iovcnt = 1;
|
|
// no need to change_audio_endianness_to_le for zeroes
|
|
// continue;
|
|
}
|
|
|
|
// iov_add(buffer, sizeof(buffer), iov);
|
|
if(msg)
|
|
ast_debug (7, msg, PVT_ID(pvt));
|
|
|
|
// }
|
|
|
|
|
|
PVT_STAT(pvt, write_frames) ++;
|
|
iov_write(pvt, pvt->audio_fd, iov, iovcnt);
|
|
// if(write_all(pvt->audio_fd, buffer, sizeof(buffer)) != sizeof(buffer))
|
|
// ast_debug (1, "[%s] Write error!\n", PVT_ID(pvt));
|
|
|
|
}
|
|
|
|
#/* copy voice data from device to each channel in conference */
|
|
static void write_conference(struct pvt * pvt, const char * buffer, size_t length)
|
|
{
|
|
struct cpvt* cpvt;
|
|
size_t wr;
|
|
|
|
AST_LIST_TRAVERSE(&pvt->chans, cpvt, entry) {
|
|
if(CPVT_IS_ACTIVE(cpvt) && !CPVT_IS_MASTER(cpvt) && CPVT_TEST_FLAG(cpvt, CALL_FLAG_MULTIPARTY) && cpvt->rd_pipe[PIPE_WRITE] >= 0)
|
|
{
|
|
wr = write_all(cpvt->rd_pipe[PIPE_WRITE], buffer, length);
|
|
// ast_debug (6, "[%s] write2 | call idx %d pipe fd %d wrote %d bytes\n", PVT_ID(pvt), cpvt->call_idx, cpvt->rd_pipe[PIPE_WRITE], wr);
|
|
if(wr != length)
|
|
{
|
|
ast_debug (1, "[%s] Pipe write error %d\n", PVT_ID(pvt), errno);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#if ASTERISK_VERSION_NUM >= 100000 /* 10+ */
|
|
#define subclass_integer subclass.integer
|
|
#elif ASTERISK_VERSION_NUM >= 10800 /* 1.8+ */
|
|
#define subclass_codec subclass.codec
|
|
#define subclass_integer subclass.integer
|
|
#else /* 1.8- */
|
|
#define subclass_codec subclass
|
|
#define subclass_integer subclass
|
|
#endif /* ^1.8- */
|
|
|
|
#/* */
|
|
static struct ast_frame* channel_read (struct ast_channel* channel)
|
|
{
|
|
struct cpvt* cpvt = ast_channel_tech_pvt(channel);
|
|
struct pvt* pvt;
|
|
struct ast_frame* f = &ast_null_frame;
|
|
ssize_t res;
|
|
|
|
if(!cpvt || cpvt->channel != channel || !cpvt->pvt)
|
|
{
|
|
return f;
|
|
}
|
|
pvt = cpvt->pvt;
|
|
|
|
while (ast_mutex_trylock (&pvt->lock))
|
|
{
|
|
CHANNEL_DEADLOCK_AVOIDANCE (channel);
|
|
}
|
|
|
|
ast_debug (7, "[%s] read call idx %d state %d audio_fd %d\n", PVT_ID(pvt), cpvt->call_idx, cpvt->state, pvt->audio_fd);
|
|
|
|
/* FIXME: move down for enable timing_write() to device ? */
|
|
if (!CPVT_IS_SOUND_SOURCE(cpvt) || pvt->audio_fd < 0)
|
|
{
|
|
goto e_return;
|
|
}
|
|
|
|
if (pvt->a_timer && ast_channel_fdno(channel) == 1)
|
|
{
|
|
ast_timer_ack (pvt->a_timer, 1);
|
|
timing_write (pvt);
|
|
ast_debug (7, "[%s] *** timing ***\n", PVT_ID(pvt));
|
|
}
|
|
|
|
else
|
|
{
|
|
memset (&cpvt->a_read_frame, 0, sizeof (cpvt->a_read_frame));
|
|
|
|
cpvt->a_read_frame.frametype = AST_FRAME_VOICE;
|
|
#if ASTERISK_VERSION_NUM >= 130000 /* 13+ */
|
|
cpvt->a_read_frame.subclass.format = ast_format_slin;
|
|
#elif ASTERISK_VERSION_NUM >= 100000 /* 10-13 */
|
|
ast_format_copy(&cpvt->a_read_frame.subclass.format, &chan_dongle_format);
|
|
#else /* 10- */
|
|
cpvt->a_read_frame.subclass_codec = AST_FORMAT_SLINEAR;
|
|
#endif /* ^10- */
|
|
cpvt->a_read_frame.data.ptr = cpvt->a_read_buf + AST_FRIENDLY_OFFSET;
|
|
cpvt->a_read_frame.offset = AST_FRIENDLY_OFFSET;
|
|
cpvt->a_read_frame.src = AST_MODULE;
|
|
|
|
res = read (CPVT_IS_MASTER(cpvt) ? pvt->audio_fd : cpvt->rd_pipe[PIPE_READ], cpvt->a_read_frame.data.ptr, FRAME_SIZE);
|
|
if (res <= 0)
|
|
{
|
|
if (errno != EAGAIN && errno != EINTR)
|
|
{
|
|
ast_debug (1, "[%s] Read error %d, going to wait for new connection\n", PVT_ID(pvt), errno);
|
|
}
|
|
|
|
goto e_return;
|
|
}
|
|
|
|
/* ast_debug (7, "[%s] call idx %d read %u\n", PVT_ID(pvt), cpvt->call_idx, (unsigned)res);
|
|
ast_debug (6, "[%s] read | call idx %d fd %d read %d bytes\n", PVT_ID(pvt), cpvt->call_idx, pvt->audio_fd, res);
|
|
*/
|
|
|
|
if(CPVT_IS_MASTER(cpvt))
|
|
{
|
|
if(CPVT_TEST_FLAG(cpvt, CALL_FLAG_MULTIPARTY))
|
|
write_conference(pvt, cpvt->a_read_frame.data.ptr, res);
|
|
|
|
PVT_STAT(pvt, a_read_bytes) += res;
|
|
PVT_STAT(pvt, read_frames) ++;
|
|
if(res < FRAME_SIZE)
|
|
PVT_STAT(pvt, read_sframes) ++;
|
|
}
|
|
|
|
cpvt->a_read_frame.samples = res / 2;
|
|
cpvt->a_read_frame.datalen = res;
|
|
ast_frame_byteswap_le (&cpvt->a_read_frame);
|
|
/*
|
|
cpvt->a_read_frame.ts;
|
|
cpvt->a_read_frame.len;
|
|
cpvt->a_read_frame.seqno;
|
|
*/
|
|
f = &cpvt->a_read_frame;
|
|
if (pvt->dsp)
|
|
{
|
|
f = ast_dsp_process (channel, pvt->dsp, f);
|
|
if ((f->frametype == AST_FRAME_DTMF_END) || (f->frametype == AST_FRAME_DTMF_BEGIN))
|
|
{
|
|
if ((f->subclass_integer == 'm') || (f->subclass_integer == 'u'))
|
|
{
|
|
f->frametype = AST_FRAME_NULL;
|
|
f->subclass_integer = 0;
|
|
goto e_return;
|
|
}
|
|
if(f->frametype == AST_FRAME_DTMF_BEGIN)
|
|
{
|
|
pvt->dtmf_begin_time = ast_tvnow();
|
|
}
|
|
else if (f->frametype == AST_FRAME_DTMF_END)
|
|
{
|
|
if(!ast_tvzero(pvt->dtmf_begin_time) && ast_tvdiff_ms(ast_tvnow(), pvt->dtmf_begin_time) < CONF_SHARED(pvt, mindtmfgap))
|
|
{
|
|
ast_debug(1, "[%s] DTMF char %c ignored min gap %d > %ld\n", PVT_ID(pvt), f->subclass_integer, CONF_SHARED(pvt, mindtmfgap), (long)ast_tvdiff_ms(ast_tvnow(), pvt->dtmf_begin_time));
|
|
f->frametype = AST_FRAME_NULL;
|
|
f->subclass_integer = 0;
|
|
}
|
|
else if(f->len < CONF_SHARED(pvt, mindtmfduration))
|
|
{
|
|
ast_debug(1, "[%s] DTMF char %c ignored min duration %d > %ld\n", PVT_ID(pvt), f->subclass_integer, CONF_SHARED(pvt, mindtmfduration), f->len);
|
|
f->frametype = AST_FRAME_NULL;
|
|
f->subclass_integer = 0;
|
|
}
|
|
else if(f->subclass_integer == pvt->dtmf_digit
|
|
&&
|
|
!ast_tvzero(pvt->dtmf_end_time)
|
|
&&
|
|
ast_tvdiff_ms(ast_tvnow(), pvt->dtmf_end_time) < CONF_SHARED(pvt, mindtmfinterval))
|
|
{
|
|
ast_debug(1, "[%s] DTMF char %c ignored min interval %d > %ld\n", PVT_ID(pvt), f->subclass_integer, CONF_SHARED(pvt, mindtmfinterval), (long)ast_tvdiff_ms(ast_tvnow(), pvt->dtmf_end_time));
|
|
f->frametype = AST_FRAME_NULL;
|
|
f->subclass_integer = 0;
|
|
}
|
|
else
|
|
{
|
|
ast_debug(1, "[%s] Got DTMF char %c\n",PVT_ID(pvt), f->subclass_integer);
|
|
pvt->dtmf_digit = f->subclass_integer;
|
|
pvt->dtmf_end_time = ast_tvnow();
|
|
}
|
|
|
|
}
|
|
goto e_return;
|
|
}
|
|
}
|
|
|
|
if (CONF_SHARED(pvt, rxgain) && f->frametype == AST_FRAME_VOICE)
|
|
{
|
|
if (ast_frame_adjust_volume (f, CONF_SHARED(pvt, rxgain)) == -1)
|
|
{
|
|
ast_debug (1, "[%s] Volume could not be adjusted!\n", PVT_ID(pvt));
|
|
}
|
|
}
|
|
}
|
|
|
|
e_return:
|
|
ast_mutex_unlock (&pvt->lock);
|
|
|
|
return f;
|
|
}
|
|
|
|
#/* */
|
|
static int channel_write (struct ast_channel* channel, struct ast_frame* f)
|
|
{
|
|
struct cpvt* cpvt = ast_channel_tech_pvt(channel);
|
|
struct pvt* pvt;
|
|
size_t count;
|
|
int gains[2];
|
|
|
|
#if ASTERISK_VERSION_NUM >= 130000 /* 13+ */
|
|
if (f->frametype != AST_FRAME_VOICE
|
|
|| ast_format_cmp(f->subclass.format, ast_format_slin) != AST_FORMAT_CMP_EQUAL)
|
|
#elif ASTERISK_VERSION_NUM >= 100000 /* 10-13 */
|
|
if (f->frametype != AST_FRAME_VOICE
|
|
|| f->subclass.format.id != AST_FORMAT_SLINEAR)
|
|
#else /* 10- */
|
|
if (f->frametype != AST_FRAME_VOICE
|
|
|| f->subclass_codec != AST_FORMAT_SLINEAR)
|
|
#endif /* ^10- */
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if(!cpvt || cpvt->channel != channel || !cpvt->pvt)
|
|
{
|
|
ast_log (LOG_WARNING, "call on unreferenced %s\n", ast_channel_name(channel));
|
|
return 0;
|
|
}
|
|
|
|
/* TODO: write silence better ? */
|
|
/* TODO: check end of bridge loop condition */
|
|
/* never write to same device from other channel its possible for call hold or conference */
|
|
if(CPVT_TEST_FLAG(cpvt, CALL_FLAG_BRIDGE_LOOP))
|
|
return 0;
|
|
|
|
pvt = cpvt->pvt;
|
|
|
|
ast_debug (7, "[%s] write call idx %d state %d\n", PVT_ID(pvt), cpvt->call_idx, cpvt->state);
|
|
|
|
while (ast_mutex_trylock (&pvt->lock))
|
|
{
|
|
CHANNEL_DEADLOCK_AVOIDANCE (channel);
|
|
}
|
|
|
|
if(!CPVT_IS_ACTIVE(cpvt))
|
|
goto e_return;
|
|
|
|
if(CPVT_TEST_FLAG(cpvt, CALL_FLAG_MULTIPARTY) && !CPVT_TEST_FLAG(cpvt, CALL_FLAG_BRIDGE_CHECK))
|
|
{
|
|
#if ASTERISK_VERSION_NUM >= 120000 /* 12+ */
|
|
RAII_VAR(struct ast_channel *, bridged, ast_channel_bridge_peer(channel), ast_channel_cleanup);
|
|
#else /* 12- */
|
|
struct ast_channel *bridged = ast_bridged_channel(channel);
|
|
#endif /* ^12- */
|
|
struct cpvt *tmp_cpvt;
|
|
|
|
CPVT_SET_FLAGS(cpvt, CALL_FLAG_BRIDGE_CHECK);
|
|
|
|
if (bridged && ast_channel_tech(bridged) == &channel_tech && (tmp_cpvt = ast_channel_tech_pvt(bridged)) && tmp_cpvt->pvt == pvt)
|
|
{
|
|
CPVT_SET_FLAGS(cpvt, CALL_FLAG_BRIDGE_LOOP);
|
|
CPVT_SET_FLAGS((struct cpvt*)ast_channel_tech_pvt(bridged), CALL_FLAG_BRIDGE_LOOP);
|
|
ast_log(LOG_WARNING, "[%s] Bridged channels %s and %s working on same device, discard writes to avoid voice loop\n", PVT_ID(pvt), ast_channel_name(channel), ast_channel_name(bridged));
|
|
goto e_return;
|
|
}
|
|
}
|
|
|
|
if (pvt->audio_fd < 0)
|
|
{
|
|
ast_debug (1, "[%s] audio_fd not ready\n", PVT_ID(pvt));
|
|
}
|
|
else
|
|
{
|
|
if(f->datalen)
|
|
{
|
|
/** try to minimize of ast_frame_adjust_volume() calls:
|
|
* one hand we must obey txgain but with other divide gain to
|
|
* number of mixed channels. In some cases one call of ast_frame_adjust_volume() enough
|
|
*/
|
|
|
|
gains[1] = mixb_streams(&pvt->a_write_mixb);
|
|
if(gains[1] < 1 || pvt->a_timer == NULL)
|
|
gains[1] = 1;
|
|
|
|
gains[0] = CONF_SHARED(pvt, txgain);
|
|
if(gains[0] <= -2)
|
|
{
|
|
gains[0] *= gains[1];
|
|
gains[1] = 0;
|
|
}
|
|
else if(gains[0] <= 1)
|
|
{
|
|
gains[0] = - gains[1];
|
|
gains[1] = 0;
|
|
}
|
|
else if(gains[0] % gains[1] == 0)
|
|
{
|
|
gains[0] /= gains[1];
|
|
gains[1] = 0;
|
|
}
|
|
|
|
for(count = 0; count < ITEMS_OF(gains); ++count)
|
|
{
|
|
if(gains[count] > 1 || gains[count] < -1)
|
|
if(ast_frame_adjust_volume (f, gains[count]) == -1)
|
|
{
|
|
ast_debug (1, "[%s] Volume could not be adjusted!\n", PVT_ID(pvt));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (pvt->a_timer)
|
|
{
|
|
count = mixb_free (&pvt->a_write_mixb, &cpvt->mixstream);
|
|
|
|
if (count < (size_t) f->datalen)
|
|
{
|
|
mixb_read_upd (&pvt->a_write_mixb, f->datalen - count);
|
|
|
|
PVT_STAT(pvt, write_rb_overflow_bytes) += f->datalen - count;
|
|
PVT_STAT(pvt, write_rb_overflow) ++;
|
|
}
|
|
|
|
mixb_write (&pvt->a_write_mixb, &cpvt->mixstream, f->data.ptr, f->datalen);
|
|
|
|
/*
|
|
ast_debug (6, "[%s] write | call idx %d, %d bytes lwrite %d lused %d write %d used %d\n", PVT_ID(pvt), cpvt->call_idx, f->datalen, cpvt->write, cpvt->used, pvt->a_write_rb.write, pvt->a_write_rb.used);
|
|
rb_tetris(&pvt->a_write_rb, f->data.ptr, f->datalen, &cpvt->write, &cpvt->used);
|
|
ast_debug (6, "[%s] write | lwrite %d lused %d write %d used %d\n", PVT_ID(pvt), cpvt->write, cpvt->used, pvt->a_write_rb.write, pvt->a_write_rb.used);
|
|
*/
|
|
}
|
|
|
|
else
|
|
{
|
|
if(mixb_streams(&pvt->a_write_mixb) != 1)
|
|
{
|
|
ast_log (LOG_ERROR, "[%s] write conference without timer\n", PVT_ID(pvt));
|
|
goto e_return;
|
|
}
|
|
|
|
{
|
|
int iovcnt;
|
|
struct iovec iov[2];
|
|
|
|
ast_frame_byteswap_le (f);
|
|
iov[0].iov_base = f->data.ptr;
|
|
iov[0].iov_len = FRAME_SIZE;
|
|
|
|
if (f->datalen < FRAME_SIZE)
|
|
{
|
|
iov[0].iov_len = f->datalen;
|
|
iov[1].iov_base = silence_frame;
|
|
iov[1].iov_len = FRAME_SIZE - f->datalen;
|
|
iovcnt = 2;
|
|
PVT_STAT(pvt, write_tframes) ++;
|
|
}
|
|
else
|
|
{
|
|
iovcnt = 1;
|
|
}
|
|
|
|
iov_write(pvt, pvt->audio_fd, iov, iovcnt);
|
|
PVT_STAT(pvt, write_frames) ++;
|
|
}
|
|
}
|
|
|
|
/* if (f->datalen != 320)
|
|
*/
|
|
{
|
|
ast_debug (7, "[%s] Write frame: samples = %d, data lenght = %d byte\n", PVT_ID(pvt), f->samples, f->datalen);
|
|
}
|
|
}
|
|
|
|
e_return:
|
|
ast_mutex_unlock (&pvt->lock);
|
|
|
|
return 0;
|
|
}
|
|
#undef subclass_integer
|
|
#undef subclass_codec
|
|
|
|
#/* */
|
|
static int channel_fixup (struct ast_channel* oldchannel, struct ast_channel* newchannel)
|
|
{
|
|
struct cpvt * cpvt = ast_channel_tech_pvt(newchannel);
|
|
struct pvt* pvt;
|
|
|
|
if (!cpvt || !cpvt->pvt)
|
|
{
|
|
ast_log (LOG_WARNING, "call on unreferenced %s\n", ast_channel_name(newchannel));
|
|
return -1;
|
|
}
|
|
pvt = cpvt->pvt;
|
|
|
|
ast_mutex_lock (&pvt->lock);
|
|
if (cpvt->channel == oldchannel)
|
|
{
|
|
cpvt->channel = newchannel;
|
|
}
|
|
ast_mutex_unlock (&pvt->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#/* FIXME: must modify in conjuction with state on call not whole device? */
|
|
#if ASTERISK_VERSION_NUM >= 110000 /* 11+ */
|
|
static int channel_devicestate (const char *data)
|
|
#else /* 11- */
|
|
static int channel_devicestate (void* data)
|
|
#endif /* ^11- */
|
|
{
|
|
char* device;
|
|
struct pvt* pvt;
|
|
int res = AST_DEVICE_INVALID;
|
|
|
|
device = ast_strdupa (data ? data : "");
|
|
|
|
ast_debug (1, "Checking device state for device %s\n", device);
|
|
|
|
pvt = find_device_ext(device);
|
|
if (pvt)
|
|
{
|
|
if (pvt->connected)
|
|
{
|
|
if (is_dial_possible(pvt, CALL_FLAG_NONE))
|
|
{
|
|
res = AST_DEVICE_NOT_INUSE;
|
|
}
|
|
else
|
|
{
|
|
res = AST_DEVICE_INUSE;
|
|
}
|
|
}
|
|
ast_mutex_unlock (&pvt->lock);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
#/* */
|
|
static int channel_indicate (struct ast_channel* channel, int condition, const void* data, attribute_unused size_t datalen)
|
|
{
|
|
int res = 0;
|
|
|
|
ast_debug (1, "[%s] Requested indication %d\n", ast_channel_name(channel), condition);
|
|
|
|
switch (condition)
|
|
{
|
|
case AST_CONTROL_BUSY:
|
|
case AST_CONTROL_CONGESTION:
|
|
case AST_CONTROL_RINGING:
|
|
case -1:
|
|
res = -1;
|
|
break;
|
|
/* appears in r295843 */
|
|
#ifdef HAVE_AST_CONTROL_SRCCHANGE
|
|
case AST_CONTROL_SRCCHANGE:
|
|
#endif
|
|
case AST_CONTROL_PROGRESS:
|
|
case AST_CONTROL_PROCEEDING:
|
|
case AST_CONTROL_VIDUPDATE:
|
|
case AST_CONTROL_SRCUPDATE:
|
|
#if ASTERISK_VERSION_NUM >= 110000 /* 11+ */
|
|
case AST_CONTROL_PVT_CAUSE_CODE:
|
|
#endif /* ^11+ */
|
|
break;
|
|
|
|
case AST_CONTROL_HOLD:
|
|
ast_moh_start (channel, data, NULL);
|
|
break;
|
|
|
|
case AST_CONTROL_UNHOLD:
|
|
ast_moh_stop (channel);
|
|
break;
|
|
|
|
default:
|
|
ast_log (LOG_WARNING, "[%s] Don't know how to indicate condition %d\n", ast_channel_name(channel), condition);
|
|
res = -1;
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/* ARCH: move to cpvt */
|
|
/* FIXME: protection for cpvt->channel if exists */
|
|
#/* NOTE: called from device level with locked pvt */
|
|
EXPORT_DEF void change_channel_state(struct cpvt * cpvt, unsigned newstate, int cause)
|
|
{
|
|
struct ast_channel * channel;
|
|
struct pvt* pvt;
|
|
call_state_t oldstate = cpvt->state;
|
|
short call_idx;
|
|
|
|
if(newstate != oldstate)
|
|
{
|
|
pvt = cpvt->pvt;
|
|
channel = cpvt->channel;
|
|
call_idx = cpvt->call_idx;
|
|
|
|
cpvt->state = newstate;
|
|
PVT_STATE(pvt, chan_count[oldstate])--;
|
|
PVT_STATE(pvt, chan_count[newstate])++;
|
|
|
|
ast_debug (1, "[%s] call idx %d mpty %d, change state from '%s' to '%s' has%s channel\n", PVT_ID(pvt), call_idx, CPVT_TEST_FLAG(cpvt, CALL_FLAG_MULTIPARTY) ? 1 : 0, call_state2str(oldstate), call_state2str(newstate), channel ? "" : "'t");
|
|
|
|
/* update bits of devstate cache */
|
|
switch(newstate)
|
|
{
|
|
case CALL_STATE_ACTIVE:
|
|
case CALL_STATE_RELEASED:
|
|
/* no split to incoming/outgoing because these states not intersect */
|
|
switch(oldstate)
|
|
{
|
|
case CALL_STATE_INIT:
|
|
case CALL_STATE_DIALING:
|
|
case CALL_STATE_ALERTING:
|
|
pvt->dialing = 0;
|
|
break;
|
|
case CALL_STATE_INCOMING:
|
|
pvt->ring = 0;
|
|
break;
|
|
case CALL_STATE_WAITING:
|
|
pvt->cwaiting = 0;
|
|
break;
|
|
default:;
|
|
}
|
|
break;
|
|
default:;
|
|
}
|
|
|
|
/* check channel is dead */
|
|
if(!channel)
|
|
{
|
|
/* channel already dead */
|
|
if(newstate == CALL_STATE_RELEASED)
|
|
cpvt_free(cpvt);
|
|
}
|
|
else
|
|
{
|
|
/* for live channel */
|
|
switch(newstate)
|
|
{
|
|
case CALL_STATE_DIALING:
|
|
/* from ^ORIG:idx,y */
|
|
activate_call(cpvt);
|
|
queue_control_channel (cpvt, AST_CONTROL_PROGRESS);
|
|
ast_setstate (channel, AST_STATE_DIALING);
|
|
break;
|
|
|
|
case CALL_STATE_ALERTING:
|
|
activate_call(cpvt);
|
|
queue_control_channel (cpvt, AST_CONTROL_RINGING);
|
|
ast_setstate (channel, AST_STATE_RINGING);
|
|
break;
|
|
|
|
case CALL_STATE_ACTIVE:
|
|
activate_call(cpvt);
|
|
if (oldstate == CALL_STATE_ONHOLD)
|
|
{
|
|
ast_debug (1, "[%s] Unhold call idx %d\n", PVT_ID(pvt), call_idx);
|
|
queue_control_channel (cpvt, AST_CONTROL_UNHOLD);
|
|
}
|
|
else if (cpvt->dir == CALL_DIR_OUTGOING)
|
|
{
|
|
ast_debug (1, "[%s] Remote end answered on call idx %d\n", PVT_ID(pvt), call_idx);
|
|
queue_control_channel (cpvt, AST_CONTROL_ANSWER);
|
|
}
|
|
else /* if (cpvt->answered) */
|
|
{
|
|
ast_debug (1, "[%s] Call idx %d answer\n", PVT_ID(pvt), call_idx);
|
|
ast_setstate (channel, AST_STATE_UP);
|
|
}
|
|
break;
|
|
|
|
case CALL_STATE_ONHOLD:
|
|
disactivate_call(cpvt);
|
|
ast_debug (1, "[%s] Hold call idx %d\n", PVT_ID(pvt), call_idx);
|
|
queue_control_channel (cpvt, AST_CONTROL_HOLD);
|
|
break;
|
|
|
|
case CALL_STATE_RELEASED:
|
|
disactivate_call(cpvt);
|
|
/* from +CEND, restart or disconnect */
|
|
|
|
|
|
/* drop channel -> cpvt reference */
|
|
ast_channel_tech_pvt_set(channel, NULL);
|
|
cpvt_free(cpvt);
|
|
if (queue_hangup (channel, cause))
|
|
{
|
|
ast_log (LOG_ERROR, "[%s] Error queueing hangup...\n", PVT_ID(pvt));
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
manager_event_call_state_change(PVT_ID(pvt), call_idx, call_state2str(newstate));
|
|
}
|
|
}
|
|
|
|
#/* */
|
|
static void set_channel_vars(struct pvt* pvt, struct ast_channel* channel)
|
|
{
|
|
unsigned idx;
|
|
channel_var_t dev_vars[] =
|
|
{
|
|
{ "DONGLENAME", PVT_ID(pvt) },
|
|
{ "DONGLEPROVIDER", pvt->provider_name },
|
|
{ "DONGLEIMEI", pvt->imei },
|
|
{ "DONGLEIMSI", pvt->imsi },
|
|
{ "DONGLENUMBER", pvt->subscriber_number },
|
|
};
|
|
|
|
#if ASTERISK_VERSION_NUM >= 110000 /* 11+ */
|
|
ast_channel_language_set(channel, CONF_SHARED(pvt, language));
|
|
#else /* 11- */
|
|
//TODO uncomment and fix
|
|
//ast_string_field_set (channel, language, CONF_SHARED(pvt, language);
|
|
#endif /* ^11- */
|
|
|
|
for (idx = 0; idx < ITEMS_OF(dev_vars); ++idx) {
|
|
ast_debug(1, "[%s] Setting chanvar %s = %s\n",
|
|
PVT_ID(pvt),
|
|
(dev_vars[idx].name ? dev_vars[idx].name : "(null)"),
|
|
(dev_vars[idx].value ? dev_vars[idx].value : "(null)"));
|
|
pbx_builtin_setvar_helper(channel, dev_vars[idx].name, dev_vars[idx].value);
|
|
}
|
|
}
|
|
|
|
/* NOTE: called from device and current levels with locked pvt */
|
|
#if ASTERISK_VERSION_NUM >= 120000 /* 12+ */
|
|
EXPORT_DEF struct ast_channel* new_channel(
|
|
struct pvt* pvt, int ast_state, const char* cid_num, int call_idx,
|
|
unsigned dir, call_state_t state, const char * dnid,
|
|
const struct ast_assigned_ids *assignedids,
|
|
attribute_unused const struct ast_channel * requestor)
|
|
#else /* 13- */
|
|
EXPORT_DEF struct ast_channel* new_channel(
|
|
struct pvt* pvt, int ast_state, const char* cid_num, int call_idx,
|
|
unsigned dir, call_state_t state, const char * dnid,
|
|
attribute_unused const struct ast_channel * requestor)
|
|
#endif /* ^13- */
|
|
{
|
|
struct ast_channel* channel;
|
|
struct cpvt * cpvt;
|
|
|
|
cpvt = cpvt_alloc(pvt, call_idx, dir, state);
|
|
if (cpvt)
|
|
{
|
|
#if ASTERISK_VERSION_NUM >= 120000 /* 12+ */
|
|
channel = ast_channel_alloc(
|
|
1, ast_state, cid_num, PVT_ID(pvt), NULL, dnid,
|
|
CONF_SHARED(pvt, context), assignedids, requestor, 0,
|
|
"%s/%s-%02u%08lx", channel_tech.type, PVT_ID(pvt),
|
|
call_idx, pvt->channel_instance);
|
|
#elif ASTERISK_VERSION_NUM >= 10800 /* 1.8+ */
|
|
channel = ast_channel_alloc(
|
|
1, ast_state, cid_num, PVT_ID(pvt), NULL, dnid,
|
|
CONF_SHARED(pvt, context),
|
|
requestor ? ast_channel_linkedid(requestor) : NULL, 0,
|
|
"%s/%s-%02u%08lx", channel_tech.type, PVT_ID(pvt),
|
|
call_idx, pvt->channel_instance);
|
|
#else /* 1.8- */
|
|
channel = ast_channel_alloc(
|
|
1, ast_state, cid_num, PVT_ID(pvt), NULL, dnid,
|
|
CONF_SHARED(pvt, context), 0,
|
|
"%s/%s-%02u%08lx", channel_tech.type, PVT_ID(pvt),
|
|
call_idx, pvt->channel_instance);
|
|
#endif /* ^1.8- */
|
|
if (channel)
|
|
{
|
|
cpvt->channel = channel;
|
|
pvt->channel_instance++;
|
|
|
|
ast_channel_tech_pvt_set(channel, cpvt);
|
|
ast_channel_tech_set(channel, &channel_tech);
|
|
|
|
#if ASTERISK_VERSION_NUM >= 130000 /* 13+ */
|
|
ast_channel_nativeformats_set(channel, channel_tech.capabilities);
|
|
ast_channel_set_rawreadformat(channel, ast_format_slin);
|
|
ast_channel_set_rawwriteformat(channel, ast_format_slin);
|
|
ast_channel_set_writeformat(channel, ast_format_slin);
|
|
ast_channel_set_readformat(channel, ast_format_slin);
|
|
#elif ASTERISK_VERSION_NUM >= 110000 /* 11+ */
|
|
ast_format_cap_add(ast_channel_nativeformats(channel), &chan_dongle_format);
|
|
ast_format_copy(ast_channel_rawreadformat(channel), &chan_dongle_format);
|
|
ast_format_copy(ast_channel_rawwriteformat(channel), &chan_dongle_format);
|
|
ast_format_copy(ast_channel_writeformat(channel), &chan_dongle_format);
|
|
ast_format_copy(ast_channel_readformat(channel), &chan_dongle_format);
|
|
#elif ASTERISK_VERSION_NUM >= 100000 /* 10+ */
|
|
ast_format_cap_add(channel->nativeformats, &chan_dongle_format);
|
|
ast_format_copy(&channel->rawreadformat, &chan_dongle_format);
|
|
ast_format_copy(&channel->rawwriteformat, &chan_dongle_format);
|
|
ast_format_copy(&channel->writeformat, &chan_dongle_format);
|
|
ast_format_copy(&channel->readformat, &chan_dongle_format);
|
|
#else /* 10- */
|
|
channel->nativeformats = AST_FORMAT_SLINEAR;
|
|
channel->rawreadformat = AST_FORMAT_SLINEAR;
|
|
channel->rawwriteformat = AST_FORMAT_SLINEAR;
|
|
channel->readformat = AST_FORMAT_SLINEAR;
|
|
channel->writeformat = AST_FORMAT_SLINEAR;
|
|
#endif /* ^10- */
|
|
|
|
if (ast_state == AST_STATE_RING)
|
|
{
|
|
ast_channel_rings_set(channel, 1);
|
|
}
|
|
|
|
set_channel_vars(pvt, channel);
|
|
|
|
if(dnid != NULL && dnid[0] != 0)
|
|
pbx_builtin_setvar_helper(channel, "CALLERID(dnid)", dnid);
|
|
/*
|
|
#if ASTERISK_VERSION_NUM >= 10800
|
|
channel->dialed.number.str = ast_strdup(dnid);
|
|
#else
|
|
channel->cid.cid_dnid = ast_strdup(dnid);
|
|
#endif
|
|
*/
|
|
ast_jb_configure (channel, &CONF_GLOBAL(jbconf));
|
|
|
|
ast_module_ref (self_module());
|
|
|
|
#if ASTERISK_VERSION_NUM >= 120000 /* 12+ */
|
|
/* commit e2630fcd516b8f794bf342d9fd267b0c905e79ce
|
|
* Date: Wed Dec 18 19:28:05 2013 +0000a
|
|
* ast_channel_alloc() returns allocated channels locked. */
|
|
ast_channel_unlock(channel);
|
|
#endif /* ^12+ */
|
|
|
|
return channel;
|
|
}
|
|
cpvt_free(cpvt);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* NOTE: bg: hmm ast_queue_control() say no need channel lock, trylock got deadlock up to 30 seconds here */
|
|
/* NOTE: called from device and current levels with pvt locked */
|
|
EXPORT_DEF int queue_control_channel (struct cpvt * cpvt, enum ast_control_frame_type control)
|
|
{
|
|
/*
|
|
for (;;)
|
|
{
|
|
*/
|
|
if (cpvt->channel)
|
|
{
|
|
/*
|
|
if (ast_channel_trylock (cpvt->channel))
|
|
{
|
|
DEADLOCK_AVOIDANCE (&cpvt->pvt->lock);
|
|
}
|
|
else
|
|
{
|
|
*/
|
|
ast_queue_control (cpvt->channel, control);
|
|
/*
|
|
ast_channel_unlock (cpvt->channel);
|
|
break;
|
|
}
|
|
*/
|
|
}
|
|
/*
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
/* NOTE: bg: hmm ast_queue_hangup() say no need channel lock before call, trylock got deadlock up to 30 seconds here */
|
|
/* NOTE: bg: called from device level and change_channel_state() with pvt locked */
|
|
EXPORT_DEF int queue_hangup(struct ast_channel* channel, int hangupcause)
|
|
{
|
|
int rv = 0;
|
|
if(channel)
|
|
{
|
|
if (hangupcause != 0) {
|
|
ast_channel_hangupcause_set(channel, hangupcause);
|
|
}
|
|
|
|
rv = ast_queue_hangup (channel);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
#/* NOTE: bg: called from device level with pvt locked */
|
|
EXPORT_DEF void start_local_channel (struct pvt* pvt, const char* exten, const char* number, channel_var_t* vars)
|
|
{
|
|
struct ast_channel* channel;
|
|
int cause = 0;
|
|
char channel_name[1024];
|
|
|
|
snprintf (channel_name, sizeof (channel_name), "%s@%s", exten, CONF_SHARED(pvt, context));
|
|
|
|
#if ASTERISK_VERSION_NUM >= 120000 /* 12+ */
|
|
channel = ast_request("Local", channel_tech.capabilities, NULL, NULL, channel_name, &cause);
|
|
#elif ASTERISK_VERSION_NUM >= 100000 /* 10-12 */
|
|
channel = ast_request("Local", chan_dongle_format_cap, NULL, channel_name, &cause);
|
|
#elif ASTERISK_VERSION_NUM >= 10800 /* 1.8+ */
|
|
channel = ast_request("Local", AST_FORMAT_AUDIO_MASK, NULL, channel_name, &cause);
|
|
#else /* 1.8- */
|
|
channel = ast_request("Local", AST_FORMAT_AUDIO_MASK, channel_name, &cause);
|
|
#endif /* ^1.8- */
|
|
if (channel)
|
|
{
|
|
set_channel_vars(pvt, channel);
|
|
ast_set_callerid (channel, number, PVT_ID(pvt), number);
|
|
|
|
for(; vars->name; ++vars)
|
|
pbx_builtin_setvar_helper (channel, vars->name, vars->value);
|
|
|
|
cause = ast_pbx_start (channel);
|
|
if (cause)
|
|
{
|
|
ast_hangup (channel);
|
|
ast_log (LOG_ERROR, "[%s] Unable to start pbx on channel Local/%s\n", PVT_ID(pvt), channel_name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ast_log (LOG_ERROR, "[%s] Unable to request channel Local/%s\n", PVT_ID(pvt), channel_name);
|
|
}
|
|
}
|
|
|
|
#/* */
|
|
static int channel_func_read(struct ast_channel* channel, attribute_unused const char* function, char* data, char* buf, size_t len)
|
|
{
|
|
struct cpvt* cpvt = ast_channel_tech_pvt(channel);
|
|
struct pvt* pvt;
|
|
int ret = 0;
|
|
|
|
if(!cpvt || !cpvt->pvt)
|
|
{
|
|
ast_log (LOG_WARNING, "call on unreferenced %s\n", ast_channel_name(channel));
|
|
return -1;
|
|
}
|
|
pvt = cpvt->pvt;
|
|
|
|
if (!strcasecmp(data, "callstate"))
|
|
{
|
|
while (ast_mutex_trylock (&pvt->lock))
|
|
{
|
|
CHANNEL_DEADLOCK_AVOIDANCE (channel);
|
|
}
|
|
call_state_t state = cpvt->state;
|
|
ast_mutex_unlock(&pvt->lock);
|
|
|
|
ast_copy_string(buf, call_state2str(state), len);
|
|
}
|
|
/*
|
|
else if (!strcasecmp(data, "calls"))
|
|
{
|
|
char buffer[20];
|
|
while (ast_mutex_trylock (&pvt->lock))
|
|
{
|
|
CHANNEL_DEADLOCK_AVOIDANCE (channel);
|
|
}
|
|
unsigned calls = pvt->chansno;
|
|
ast_mutex_unlock(&pvt->lock);
|
|
|
|
snprintf(buffer, sizeof(buffer), "%u", calls);
|
|
ast_copy_string(buf, buffer, len);
|
|
}
|
|
*/
|
|
else if (!strcasecmp(data, "dtmf"))
|
|
{
|
|
const char* dtmf;
|
|
while (ast_mutex_trylock (&pvt->lock))
|
|
{
|
|
CHANNEL_DEADLOCK_AVOIDANCE (channel);
|
|
}
|
|
dtmf = dc_dtmf_setting2str(pvt->real_dtmf);
|
|
ast_mutex_unlock(&pvt->lock);
|
|
|
|
ast_copy_string(buf, dtmf, len);
|
|
}
|
|
else
|
|
ret = -1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
#/* */
|
|
static int channel_func_write(struct ast_channel* channel, const char* function, char* data, const char* value)
|
|
{
|
|
struct cpvt* cpvt = ast_channel_tech_pvt(channel);
|
|
struct pvt* pvt = cpvt->pvt;
|
|
call_state_t newstate, oldstate;
|
|
int ret = 0;
|
|
|
|
if(!cpvt || !cpvt->pvt)
|
|
{
|
|
ast_log (LOG_WARNING, "call on unreferenced %s\n", ast_channel_name(channel));
|
|
return -1;
|
|
}
|
|
|
|
if (!strcasecmp(data, "callstate"))
|
|
{
|
|
if (!strcasecmp(value, "active"))
|
|
{
|
|
newstate = CALL_STATE_ACTIVE;
|
|
}
|
|
else
|
|
{
|
|
ast_log(LOG_WARNING, "Invalid value for %s(callstate).\n",
|
|
function);
|
|
return -1;
|
|
}
|
|
|
|
while (ast_mutex_trylock (&cpvt->pvt->lock))
|
|
{
|
|
CHANNEL_DEADLOCK_AVOIDANCE (channel);
|
|
}
|
|
oldstate = cpvt->state;
|
|
|
|
if (oldstate == newstate)
|
|
;
|
|
else if (oldstate == CALL_STATE_ONHOLD)
|
|
{
|
|
if (at_enqueue_activate(cpvt))
|
|
{
|
|
/* TODO: handle error */
|
|
ast_log(LOG_ERROR,
|
|
"Error state to active for call idx %d in %s(callstate).\n",
|
|
cpvt->call_idx, function);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ast_log(LOG_WARNING,
|
|
"allow change state to 'active' only from 'held' in %s(callstate).\n",
|
|
function);
|
|
ret = -1;
|
|
}
|
|
ast_mutex_unlock(&cpvt->pvt->lock);
|
|
}
|
|
else if (!strcasecmp(data, "dtmf"))
|
|
{
|
|
int val = dc_dtmf_str2setting(value);
|
|
|
|
if(val >= 0)
|
|
{
|
|
while (ast_mutex_trylock (&cpvt->pvt->lock))
|
|
{
|
|
CHANNEL_DEADLOCK_AVOIDANCE (channel);
|
|
}
|
|
|
|
if((dc_dtmf_setting_t)val != pvt->real_dtmf)
|
|
{
|
|
pvt_dsp_setup(pvt, PVT_ID(pvt), val);
|
|
}
|
|
|
|
ast_mutex_unlock(&cpvt->pvt->lock);
|
|
}
|
|
else
|
|
{
|
|
ast_log(LOG_WARNING, "Invalid value for %s(dtmf).\n", function);
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
ret = -1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
EXPORT_DEF struct ast_channel_tech channel_tech =
|
|
{
|
|
.type = "Dongle",
|
|
.description = MODULE_DESCRIPTION,
|
|
#if ASTERISK_VERSION_NUM < 100000 /* 10- */
|
|
.capabilities = AST_FORMAT_SLINEAR,
|
|
#endif /* ^10- */
|
|
.requester = channel_request,
|
|
.call = channel_call,
|
|
.hangup = channel_hangup,
|
|
.answer = channel_answer,
|
|
.send_digit_begin = channel_digit_begin,
|
|
.send_digit_end = channel_digit_end,
|
|
.read = channel_read,
|
|
.write = channel_write,
|
|
.exception = channel_read,
|
|
.fixup = channel_fixup,
|
|
.devicestate = channel_devicestate,
|
|
.indicate = channel_indicate,
|
|
.func_channel_read = channel_func_read,
|
|
.func_channel_write = channel_func_write
|
|
};
|