asterisk-chan-dongle/channel.c

1596 lines
42 KiB
C
Raw Permalink Normal View History

2023-05-25 08:30:27 +00:00
/*
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
};