diff --git a/conf/freeswitch.xml b/conf/freeswitch.xml index 2e13d1e3e2..32b474cb64 100644 --- a/conf/freeswitch.xml +++ b/conf/freeswitch.xml @@ -8,6 +8,12 @@ + + + + + + @@ -429,6 +435,9 @@ + + + diff --git a/libs/libteletone/src/libteletone_generate.c b/libs/libteletone/src/libteletone_generate.c index 6c1237a069..b3421da358 100644 --- a/libs/libteletone/src/libteletone_generate.c +++ b/libs/libteletone/src/libteletone_generate.c @@ -84,11 +84,14 @@ int teletone_init_session(teletone_generation_session_t *ts, int buflen, tone_ha ts->user_data = user_data; ts->volume = 1500; ts->decay_step = 0; - if ((ts->buffer = calloc(buflen, sizeof(teletone_audio_t))) == 0) { - return -1; + if (buflen) { + if ((ts->buffer = calloc(buflen, sizeof(teletone_audio_t))) == 0) { + return -1; + } + ts->datalen = buflen; + } else { + ts->dynamic = 1024; } - ts->datalen = buflen; - /* Add Standard DTMF Tones */ teletone_set_tone(ts, '1', 697.0, 1209.0, 0.0); teletone_set_tone(ts, '2', 697.0, 1336.0, 0.0); @@ -120,8 +123,21 @@ int teletone_destroy_session(teletone_generation_session_t *ts) return 0; } -/** Generate a specified number of samples containing the three specified - * frequencies (in hertz) and dump to the file descriptor audio_fd. */ +static int ensure_buffer(teletone_generation_session_t *ts, int need) +{ + need += ts->samples; + need *= sizeof(teletone_audio_t); + need *= ts->channels; + + if (need > ts->datalen) { + ts->datalen = need + ts->dynamic; + if (!(ts->buffer = realloc(ts->buffer, ts->datalen))) { + return -1; + } + } + + return 0; +} int teletone_mux_tones(teletone_generation_session_t *ts, teletone_tone_map_t *map) { @@ -159,6 +175,11 @@ int teletone_mux_tones(teletone_generation_session_t *ts, teletone_tone_map_t *m duration *= ts->channels; } + if (ts->dynamic) { + if (ensure_buffer(ts, duration)) { + return -1; + } + } for (ts->samples = 0; ts->samples < ts->datalen && ts->samples < duration; ts->samples++) { if (ts->decay_step && !(ts->samples % ts->decay_step) && ts->volume > 0 && ts->samples > decay) { ts->volume += ts->decay_direction; @@ -179,6 +200,11 @@ int teletone_mux_tones(teletone_generation_session_t *ts, teletone_tone_map_t *m } } + if (ts->dynamic) { + if (ensure_buffer(ts, wait)) { + return -1; + } + } for (c = 0; c < ts->channels; c++) { for (i = 0; i < wait && ts->samples < ts->datalen; i++) { ts->buffer[ts->samples++] = 0; @@ -211,19 +237,37 @@ int teletone_mux_tones(teletone_generation_session_t *ts, teletone_tone_map_t *m return ts->samples; } +/* don't ask */ +static char *my_strdup (const char *s) +{ + size_t len = strlen (s) + 1; + void *new = malloc (len); + + if (new == NULL) { + return NULL; + } + + return (char *) memcpy (new, s, len); +} int teletone_run(teletone_generation_session_t *ts, char *cmd) { - char *data, *cur, *end; + char *data = NULL, *cur = NULL, *end = NULL; int var = 0, LOOPING = 0; + + if (!cmd) { + return -1; + } do { - data = strdup(cmd); + if (!(data = my_strdup(cmd))) { + return -1; + } + cur = data; - + while (*cur) { var = 0; - if (*cur == ' ' || *cur == '\r' || *cur == '\n') { cur++; continue; diff --git a/libs/libteletone/src/libteletone_generate.h b/libs/libteletone/src/libteletone_generate.h index e47ca5387e..817cda0c63 100644 --- a/libs/libteletone/src/libteletone_generate.h +++ b/libs/libteletone/src/libteletone_generate.h @@ -56,7 +56,7 @@ extern "C" { This module is responsible for tone generation specifics */ -typedef short teletone_audio_t; +typedef int16_t teletone_audio_t; struct teletone_generation_session; typedef int (*tone_handler)(struct teletone_generation_session *ts, teletone_tone_map_t *map); @@ -101,6 +101,7 @@ struct teletone_generation_session { /*! In-Use size of the buffer */ int samples; /*! Callback function called during generation */ + int dynamic; tone_handler handler; }; diff --git a/src/include/switch_apr.h b/src/include/switch_apr.h index 7dde426e38..71b40476c0 100644 --- a/src/include/switch_apr.h +++ b/src/include/switch_apr.h @@ -183,6 +183,7 @@ typedef apr_file_t switch_file_t; */ DoxyDefine(apr_status_t switch_file_open(switch_file_t **newf, const char *fname, apr_int32_t flag, switch_fileperms_t perm, switch_pool_t *pool);) #define switch_file_open apr_file_open +#define switch_file_seek apr_file_seek /** * Close the specified file. diff --git a/src/include/switch_core.h b/src/include/switch_core.h index 8e40776b51..56de1c2787 100644 --- a/src/include/switch_core.h +++ b/src/include/switch_core.h @@ -417,6 +417,13 @@ SWITCH_DECLARE(char *) switch_core_session_get_uuid(switch_core_session_t *sessi */ SWITCH_DECLARE(switch_core_session_t *) switch_core_session_locate(char *uuid_str); +/*! + \brief Retrieve a global variable from the core + \param varname the name of the variable + \return the value of the desired variable +*/ +SWITCH_DECLARE(char *) switch_core_get_variable(char *varname); + /*! \brief Hangup All Sessions \param cause the hangup cause to apply to the hungup channels diff --git a/src/mod/dialplans/mod_dialplan_directory/mod_dialplan_directory.c b/src/mod/dialplans/mod_dialplan_directory/mod_dialplan_directory.c index 1a4d516e02..ba2cc98b5e 100644 --- a/src/mod/dialplans/mod_dialplan_directory/mod_dialplan_directory.c +++ b/src/mod/dialplans/mod_dialplan_directory/mod_dialplan_directory.c @@ -151,7 +151,7 @@ static switch_caller_extension_t *directory_dialplan_hunt(switch_core_session_t if (extension) { switch_channel_set_state(channel, CS_EXECUTE); } else { - switch_channel_hangup(channel, SWITCH_CAUSE_MESSAGE_TYPE_NONEXIST); + switch_channel_hangup(channel, SWITCH_CAUSE_NO_ROUTE_DESTINATION); } return extension; diff --git a/src/mod/dialplans/mod_dialplan_xml/mod_dialplan_xml.c b/src/mod/dialplans/mod_dialplan_xml/mod_dialplan_xml.c index 1a83259825..c247aa62f4 100644 --- a/src/mod/dialplans/mod_dialplan_xml/mod_dialplan_xml.c +++ b/src/mod/dialplans/mod_dialplan_xml/mod_dialplan_xml.c @@ -296,7 +296,7 @@ static switch_caller_extension_t *dialplan_hunt(switch_core_session_t *session) if (!(xcontext = switch_xml_find_child(cfg, "context", "name", context))) { if (!(xcontext = switch_xml_find_child(cfg, "context", "name", "global"))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "context %s not found\n", context); - switch_channel_hangup(channel, SWITCH_CAUSE_MESSAGE_TYPE_NONEXIST); + switch_channel_hangup(channel, SWITCH_CAUSE_NO_ROUTE_DESTINATION); switch_xml_free(xml); return NULL; } @@ -325,7 +325,7 @@ static switch_caller_extension_t *dialplan_hunt(switch_core_session_t *session) if (extension) { switch_channel_set_state(channel, CS_EXECUTE); } else { - switch_channel_hangup(channel, SWITCH_CAUSE_MESSAGE_TYPE_NONEXIST); + switch_channel_hangup(channel, SWITCH_CAUSE_NO_ROUTE_DESTINATION); } return extension; diff --git a/src/mod/formats/mod_native_file/mod_native_file.c b/src/mod/formats/mod_native_file/mod_native_file.c index 7a6a600e76..06b38b80f2 100644 --- a/src/mod/formats/mod_native_file/mod_native_file.c +++ b/src/mod/formats/mod_native_file/mod_native_file.c @@ -75,7 +75,7 @@ static switch_status_t native_file_file_open(switch_file_handle_t *handle, char handle->channels = 1; handle->format = 0; handle->sections = 0; - handle->seekable = 0; + handle->seekable = 1; handle->speed = 0; handle->private_info = context; switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Opening File [%s] %dhz\n", path, handle->samplerate); @@ -98,8 +98,10 @@ static switch_status_t native_file_file_close(switch_file_handle_t *handle) static switch_status_t native_file_file_seek(switch_file_handle_t *handle, unsigned int *cur_sample, int64_t samples, int whence) { - //native_file_context *context = handle->private_info; - + native_file_context *context = handle->private_info; + + switch_file_seek(context->fd, whence, &samples); + return SWITCH_STATUS_FALSE; } diff --git a/src/mod/languages/mod_spidermonkey_teletone/mod_spidermonkey_teletone.c b/src/mod/languages/mod_spidermonkey_teletone/mod_spidermonkey_teletone.c index 19769cd9bf..a14a3629ea 100644 --- a/src/mod/languages/mod_spidermonkey_teletone/mod_spidermonkey_teletone.c +++ b/src/mod/languages/mod_spidermonkey_teletone/mod_spidermonkey_teletone.c @@ -75,7 +75,6 @@ static int teletone_handler(teletone_generation_session_t *ts, teletone_tone_map /*********************************************************************************/ static JSBool teletone_construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { - int32 memory = 65535; JSObject *session_obj; struct teletone_obj *tto = NULL; struct js_session *jss = NULL; @@ -101,12 +100,6 @@ static JSBool teletone_construct(JSContext *cx, JSObject *obj, uintN argc, jsval timer_name = JS_GetStringBytes(JS_ValueToString(cx, argv[1])); } - if (argc > 2) { - if (!JS_ValueToInt32(cx, argv[2], &memory)) { - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot Convert to INT\n"); - return JS_FALSE; - } - } switch_core_new_memory_pool(&pool); if (!(tto = switch_core_alloc(pool, sizeof(*tto)))) { @@ -149,7 +142,7 @@ static JSBool teletone_construct(JSContext *cx, JSObject *obj, uintN argc, jsval tto->obj = obj; tto->cx = cx; tto->session = jss->session; - teletone_init_session(&tto->ts, memory, teletone_handler, tto); + teletone_init_session(&tto->ts, 0, teletone_handler, tto); JS_SetPrivate(cx, obj, tto); return JS_TRUE; diff --git a/src/switch_channel.c b/src/switch_channel.c index 06c60d9533..821b9b3473 100644 --- a/src/switch_channel.c +++ b/src/switch_channel.c @@ -344,7 +344,7 @@ SWITCH_DECLARE(void) switch_channel_presence(switch_channel_t *channel, char *rp SWITCH_DECLARE(char *) switch_channel_get_variable(switch_channel_t *channel, char *varname) { - char *v; + char *v = NULL; assert(channel != NULL); if (!(v=switch_core_hash_find(channel->variables, varname))) { @@ -352,6 +352,7 @@ SWITCH_DECLARE(char *) switch_channel_get_variable(switch_channel_t *channel, ch if (!strcmp(varname, "base_dir")) { return SWITCH_GLOBAL_dirs.base_dir; } + v = switch_core_get_variable(varname); } } @@ -1113,7 +1114,7 @@ SWITCH_DECLARE(char *) switch_channel_expand_variables(switch_channel_t *channel { char *p, *c; char *data, *indup; - size_t sp = 0, len = 0, olen = 0, vtype = 0, br = 0, vnamepos, vvalpos, cpos, ppos, block = 128; + size_t sp = 0, len = 0, olen = 0, vtype = 0, br = 0, cpos, block = 128; char *sub_val = NULL, *func_val = NULL; if (!strchr(in, '$') && !strchr(in, '&')) { @@ -1195,36 +1196,35 @@ SWITCH_DECLARE(char *) switch_channel_expand_variables(switch_channel_t *channel return in; } } - nlen = strlen(sub_val); + nlen = sub_val ? strlen(sub_val) : 0; + if (len + nlen >= olen) { - olen += block; + olen = (olen + len + nlen + block); cpos = c - data; - ppos = p - data; - vnamepos = vname - data; - vvalpos = vval - data; data = realloc(data, olen); - c = data + cpos; - p = data + ppos; - vname = data + vnamepos; - vname = data + vvalpos; + memset(c, 0, olen - cpos); } - - len += nlen; - strcat(c, sub_val); - c += nlen; - - if (func_val) { - free(func_val); - func_val = NULL; + if (nlen) { + len += nlen; + strcat(c, sub_val); + c += nlen; } + + switch_safe_free(func_val); } if (sp) { *c++ = ' '; sp = 0; + len++; } - *c++ = *p; - len++; + + if (*p == '$' || *p == '&') { + p--; + } else { + *c++ = *p; + len++; + } } } free(indup); diff --git a/src/switch_core.c b/src/switch_core.c index a06293ce04..926d936988 100644 --- a/src/switch_core.c +++ b/src/switch_core.c @@ -120,6 +120,7 @@ struct switch_core_runtime { uint32_t session_id; apr_pool_t *memory_pool; switch_hash_t *session_table; + switch_hash_t *global_vars; switch_mutex_t *session_table_mutex; #ifdef CRASH_PROT switch_hash_t *stack_table; @@ -558,6 +559,11 @@ SWITCH_DECLARE(void) switch_core_session_rwunlock(switch_core_session_t *session } +SWITCH_DECLARE(char *) switch_core_get_variable(char *varname) +{ + return (char *) switch_core_hash_find(runtime.global_vars, varname); +} + SWITCH_DECLARE(switch_core_session_t *) switch_core_session_locate(char *uuid_str) { switch_core_session_t *session; @@ -3849,6 +3855,8 @@ SWITCH_DECLARE(switch_status_t) switch_core_init(char *console, const char **err return SWITCH_STATUS_MEMERR; } + switch_core_hash_init(&runtime.global_vars, runtime.memory_pool); + if (switch_xml_init(runtime.memory_pool, err) != SWITCH_STATUS_SUCCESS) { apr_terminate(); return SWITCH_STATUS_MEMERR; @@ -3868,6 +3876,18 @@ SWITCH_DECLARE(switch_status_t) switch_core_init(char *console, const char **err } } } + + if ((settings = switch_xml_child(cfg, "variables"))) { + for (param = switch_xml_child(settings, "variable"); param; param = param->next) { + char *var = (char *) switch_xml_attr_soft(param, "name"); + char *val = (char *) switch_xml_attr_soft(param, "value"); + char *varr = NULL, *vall = NULL; + + varr = switch_core_strdup(runtime.memory_pool, var); + vall = switch_core_strdup(runtime.memory_pool, val); + switch_core_hash_insert(runtime.global_vars, varr, vall); + } + } switch_xml_free(xml); } diff --git a/src/switch_ivr.c b/src/switch_ivr.c index 28e5183a23..45d7e61755 100644 --- a/src/switch_ivr.c +++ b/src/switch_ivr.c @@ -32,6 +32,7 @@ */ #include #include +#include static const switch_state_handler_table_t audio_bridge_peer_state_handlers; @@ -2198,6 +2199,31 @@ static uint8_t check_channel_status(switch_channel_t **peer_channels, } +struct ringback { + switch_buffer_t *audio_buffer; + switch_buffer_t *loop_buffer; + teletone_generation_session_t ts; + switch_file_handle_t fhb; + switch_file_handle_t *fh; + uint8_t asis; +}; + +typedef struct ringback ringback_t; + +static int teletone_handler(teletone_generation_session_t *ts, teletone_tone_map_t *map) +{ + ringback_t *tto = ts->user_data; + int wrote; + + if (!tto) { + return -1; + } + wrote = teletone_mux_tones(ts, map); + switch_buffer_write(tto->audio_buffer, ts->buffer, wrote * 2); + + return 0; +} + #define MAX_PEERS 256 SWITCH_DECLARE(switch_status_t) switch_ivr_originate(switch_core_session_t *session, switch_core_session_t **bleg, @@ -2220,6 +2246,7 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_originate(switch_core_session_t *sess switch_caller_profile_t *caller_profiles[MAX_PEERS] = {0}, *caller_caller_profile; char *chan_type = NULL, *chan_data; switch_channel_t *peer_channel = NULL, *peer_channels[MAX_PEERS] = {0}; + ringback_t ringback = {0}; time_t start; switch_frame_t *read_frame = NULL; switch_memory_pool_t *pool = NULL; @@ -2231,6 +2258,9 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_originate(switch_core_session_t *sess char *file = NULL, *key = NULL, *odata, *var; switch_call_cause_t reason = SWITCH_CAUSE_UNALLOCATED; uint8_t to = 0; + char *ringback_data = NULL; + switch_codec_t *read_codec = NULL; + write_frame.data = fdata; *bleg = NULL; @@ -2261,6 +2291,7 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_originate(switch_core_session_t *sess caller_channel = switch_core_session_get_channel(session); assert(caller_channel != NULL); + ringback_data = switch_channel_get_variable(caller_channel, "ringback"); switch_channel_set_variable(caller_channel, "originate_disposition", "failure"); if ((var = switch_channel_get_variable(caller_channel, "group_confirm_key"))) { @@ -2449,12 +2480,15 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_originate(switch_core_session_t *sess } endfor1: - - if (session && !switch_channel_test_flag(caller_channel, CF_NOMEDIA)) { - switch_codec_t *read_codec = NULL; + if (ringback_data && !switch_channel_test_flag(caller_channel, CF_ANSWERED) && !switch_channel_test_flag(caller_channel, CF_EARLY_MEDIA)) { + switch_channel_pre_answer(caller_channel); + } + + if (session && !switch_channel_test_flag(caller_channel, CF_NOMEDIA)) { read_codec = switch_core_session_get_read_codec(session); assert(read_codec != NULL); + if (!(pass = (uint8_t)switch_test_flag(read_codec, SWITCH_CODEC_FLAG_PASSTHROUGH))) { if (switch_core_codec_init(&write_codec, "L16", @@ -2465,6 +2499,8 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_originate(switch_core_session_t *sess SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, NULL, pool) == SWITCH_STATUS_SUCCESS) { + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Raw Codec Activation Success L16@%uhz 1 channel %dms\n", read_codec->implementation->samples_per_second, read_codec->implementation->microseconds_per_frame / 1000); @@ -2472,9 +2508,60 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_originate(switch_core_session_t *sess write_frame.datalen = read_codec->implementation->bytes_per_frame; write_frame.samples = write_frame.datalen / 2; memset(write_frame.data, 255, write_frame.datalen); + + if (ringback_data) { + switch_buffer_create_dynamic(&ringback.audio_buffer, 512, 1024, 0); + switch_buffer_create_dynamic(&ringback.loop_buffer, 512, 1024, 0); + char *tmp_data = NULL; + + if (*ringback_data == '/') { + char *ext; + + if ((ext = strrchr(ringback_data, '.'))) { + switch_core_session_set_read_codec(session, &write_codec); + ext++; + } else { + ringback.asis++; + write_frame.codec = read_codec; + ext = read_codec->implementation->iananame; + tmp_data = switch_mprintf("%s.%s", ringback_data, ext); + ringback_data = tmp_data; + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Play Ringback File [%s]\n", ringback_data); + + ringback.fhb.channels = read_codec->implementation->number_of_channels; + ringback.fhb.samplerate = read_codec->implementation->samples_per_second; + if (switch_core_file_open(&ringback.fhb, + ringback_data, + SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT, + switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Playing File\n"); + switch_safe_free(tmp_data); + goto notready; + } + ringback.fh = &ringback.fhb; + + + } else { + teletone_init_session(&ringback.ts, 0, teletone_handler, &ringback); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Play Ringback Tone [%s]\n", ringback_data); + //ringback.ts.debug = 1; + //ringback.ts.debug_stream = switch_core_get_console(); + if (teletone_run(&ringback.ts, ringback_data)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Playing Tone\n"); + teletone_destroy_session(&ringback.ts); + switch_buffer_destroy(&ringback.audio_buffer); + switch_buffer_destroy(&ringback.loop_buffer); + ringback_data = NULL; + } + } + switch_safe_free(tmp_data); + } } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Codec Error!"); switch_channel_hangup(caller_channel, SWITCH_CAUSE_NORMAL_TEMPORARY_FAILURE); + read_codec = NULL; } } } @@ -2505,10 +2592,56 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_originate(switch_core_session_t *sess if (!SWITCH_READ_ACCEPTABLE(status)) { break; } - if (read_frame && !pass) { + if (read_frame && !pass && !switch_test_flag(read_frame, SFF_CNG) && read_frame->datalen > 1) { + if (ringback.fh) { + uint8_t abuf[1024]; + switch_size_t mlen, olen; + unsigned int pos = 0; + + if (ringback.asis) { + mlen = read_frame->datalen; + } else { + mlen = read_frame->datalen / 2; + } + + olen = mlen; + switch_core_file_read(ringback.fh, abuf, &olen); + + if (olen == 0) { + olen = mlen; + ringback.fh->speed = 0; + switch_core_file_seek(ringback.fh, &pos, 0, SEEK_SET); + switch_core_file_read(ringback.fh, abuf, &olen); + if (olen == 0) { + break; + } + } + write_frame.data = abuf; + write_frame.datalen = (uint32_t) ringback.asis ? olen : olen * 2; + if (switch_core_session_write_frame(session, &write_frame, 1000, 0) != SWITCH_STATUS_SUCCESS) { + break; + } + } else if (ringback.audio_buffer) { + if ((write_frame.datalen = (uint32_t)switch_buffer_read(ringback.audio_buffer, + write_frame.data, + write_frame.codec->implementation->bytes_per_frame)) <= 0) { + switch_buffer_t *tmp; + tmp = ringback.audio_buffer; + ringback.audio_buffer = ringback.loop_buffer; + ringback.loop_buffer = tmp; + if ((write_frame.datalen = (uint32_t)switch_buffer_read(ringback.audio_buffer, + write_frame.data, + write_frame.codec->implementation->bytes_per_frame)) <= 0) { + break; + } + } + } if (switch_core_session_write_frame(session, &write_frame, 1000, 0) != SWITCH_STATUS_SUCCESS) { break; } + if (ringback.loop_buffer) { + switch_buffer_write(ringback.loop_buffer, write_frame.data, write_frame.datalen); + } } } else { @@ -2617,6 +2750,19 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_originate(switch_core_session_t *sess switch_core_codec_destroy(&write_codec); } + if (ringback.fh) { + switch_core_file_close(ringback.fh); + ringback.fh = NULL; + if (read_codec && !ringback.asis) { + switch_core_session_set_read_codec(session, read_codec); + switch_core_session_reset(session); + } + } else if (ringback.audio_buffer) { + teletone_destroy_session(&ringback.ts); + switch_buffer_destroy(&ringback.audio_buffer); + switch_buffer_destroy(&ringback.loop_buffer); + } + for (i = 0; i < and_argc; i++) { if (!peer_channels[i]) { continue;