/* Copyright (C) 2009 - 2010 Artem Makhutov http://www.makhutov.org Dmitry Vagin bg */ #include "ast_config.h" #include /* ast_dsp_digitreset() */ #include /* pbx_builtin_setvar_helper() */ #include /* ast_module_ref() ast_module_info = shit */ #include /* AST_CAUSE_INCOMPATIBLE_DESTINATION AST_CAUSE_FACILITY_NOT_IMPLEMENTED AST_CAUSE_REQUESTED_CHAN_UNAVAIL */ #include /* ast_moh_start() ast_moh_stop() */ #include /* AST_MUTEX_DEFINE_STATIC */ #include /* ast_timer_fd() ast_timer_set_rate() ast_timer_ack() */ #include "ast_compat.h" #if ASTERISK_VERSION_NUM >= 130000 /* 13+ */ #include #include #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 };