diff --git a/src/mod/applications/mod_spandsp/mod_spandsp.c b/src/mod/applications/mod_spandsp/mod_spandsp.c index 8cfe9d1e24..f72708e9a0 100644 --- a/src/mod/applications/mod_spandsp/mod_spandsp.c +++ b/src/mod/applications/mod_spandsp/mod_spandsp.c @@ -34,6 +34,8 @@ * */ + +#define MY_EVENT_TDD_SEND_MESSAGE "TDD::SEND_MESSAGE" #include "mod_spandsp.h" #include #include "mod_spandsp_modem.h" @@ -51,7 +53,6 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_spandsp_init); SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_spandsp_shutdown); SWITCH_MODULE_DEFINITION(mod_spandsp, mod_spandsp_init, mod_spandsp_shutdown, NULL); -static switch_event_node_t *NODE = NULL; SWITCH_STANDARD_APP(spanfax_tx_function) { @@ -74,6 +75,47 @@ SWITCH_STANDARD_APP(stop_dtmf_session_function) } +SWITCH_STANDARD_APP(tdd_encode_function) +{ + char *text = (char *) data; + + if (!zstr(text)) { + spandsp_tdd_encode_session(session, text); + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Missing text data\n"); + } +} + +SWITCH_STANDARD_APP(tdd_send_function) +{ + char *text = (char *) data; + + if (!zstr(text)) { + spandsp_tdd_send_session(session, text); + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Missing text data\n"); + } +} + +SWITCH_STANDARD_APP(stop_tdd_encode_function) +{ + spandsp_stop_tdd_encode_session(session); +} + + + + +SWITCH_STANDARD_APP(tdd_decode_function) +{ + spandsp_tdd_decode_session(session); +} + +SWITCH_STANDARD_APP(stop_tdd_decode_function) +{ + spandsp_stop_tdd_decode_session(session); +} + + SWITCH_STANDARD_APP(spandsp_fax_detect_session_function) { int argc = 0; @@ -117,6 +159,36 @@ SWITCH_STANDARD_APP(spandsp_stop_fax_detect_session_function) spandsp_fax_stop_detect_session(session); } +static void tdd_event_handler(switch_event_t *event) +{ + const char *uuid = switch_event_get_header(event, "tdd-uuid"); + const char *message = switch_event_get_body(event); + switch_core_session_t *session; + + if (zstr(message)) { + message = switch_event_get_header(event, "tdd-message"); + } + + if (zstr(message)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "No message for tdd handler\n"); + return; + } + + if (zstr(uuid)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "No uuid for tdd handler\n"); + return; + } + + if ((session = switch_core_session_locate(uuid))) { + + spandsp_tdd_encode_session(session, message); + + switch_core_session_rwunlock(session); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "No session for supplied uuid.\n"); + } +} + static void event_handler(switch_event_t *event) { load_configuration(1); @@ -543,6 +615,16 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_spandsp_init) SWITCH_ADD_APP(app_interface, "spandsp_stop_dtmf", "stop inband dtmf", "Stop detecting inband dtmf.", stop_dtmf_session_function, "", SAF_NONE); SWITCH_ADD_APP(app_interface, "spandsp_start_dtmf", "Detect dtmf", "Detect inband dtmf on the session", dtmf_session_function, "", SAF_MEDIA_TAP); + + SWITCH_ADD_APP(app_interface, "spandsp_stop_send_tdd", "stop sending tdd", "", stop_tdd_encode_function, "", SAF_NONE); + SWITCH_ADD_APP(app_interface, "spandsp_send_tdd", "Send TDD data", "Send TDD data", tdd_encode_function, "", SAF_MEDIA_TAP); + + SWITCH_ADD_APP(app_interface, "spandsp_stop_detect_tdd", "stop sending tdd", "", stop_tdd_decode_function, "", SAF_NONE); + SWITCH_ADD_APP(app_interface, "spandsp_detect_tdd", "Detect TDD data", "Detect TDD data", tdd_decode_function, "", SAF_MEDIA_TAP); + + + SWITCH_ADD_APP(app_interface, "spandsp_send_tdd", "Send TDD data", "Send TDD data", tdd_send_function, "", SAF_NONE); + SWITCH_ADD_APP(app_interface, "spandsp_start_fax_detect", "start fax detect", "start fax detect", spandsp_fax_detect_session_function, "[ ][ ][ ]", SAF_NONE); @@ -562,13 +644,18 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_spandsp_init) SWITCH_ADD_API(api_interface, "start_tone_detect", "Start background tone detection with cadence", start_tone_detect_api, "[name]"); SWITCH_ADD_API(api_interface, "stop_tone_detect", "Stop background tone detection with cadence", stop_tone_detect_api, ""); } - - if ((switch_event_bind_removable(modname, SWITCH_EVENT_RELOADXML, NULL, event_handler, NULL, &NODE) != SWITCH_STATUS_SUCCESS)) { + + if ((switch_event_bind(modname, SWITCH_EVENT_RELOADXML, NULL, event_handler, NULL) != SWITCH_STATUS_SUCCESS)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't bind our reloadxml handler!\n"); /* Not such severe to prevent loading */ } + if (switch_event_bind(modname, SWITCH_EVENT_CUSTOM, MY_EVENT_TDD_SEND_MESSAGE, tdd_event_handler, NULL) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't bind!\n"); + } + + modem_global_init(module_interface, pool); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "mod_spandsp loaded, using spandsp library version [%s]\n", SPANDSP_RELEASE_DATETIME_STRING); @@ -578,7 +665,9 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_spandsp_init) SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_spandsp_shutdown) { - switch_event_unbind(&NODE); + switch_event_unbind_callback(event_handler); + switch_event_unbind_callback(tdd_event_handler); + mod_spandsp_fax_shutdown(); mod_spandsp_dsp_shutdown(); diff --git a/src/mod/applications/mod_spandsp/mod_spandsp.h b/src/mod/applications/mod_spandsp/mod_spandsp.h index d4f388dd36..7e1eb49fa8 100644 --- a/src/mod/applications/mod_spandsp/mod_spandsp.h +++ b/src/mod/applications/mod_spandsp/mod_spandsp.h @@ -140,3 +140,11 @@ switch_status_t spandsp_fax_stop_detect_session(switch_core_session_t *session); void spanfax_log_message(void *user_data, int level, const char *msg); switch_status_t load_configuration(switch_bool_t reload); void mod_spandsp_indicate_data(switch_core_session_t *session, switch_bool_t self, switch_bool_t on); + +switch_status_t spandsp_stop_tdd_encode_session(switch_core_session_t *session); +switch_status_t spandsp_tdd_encode_session(switch_core_session_t *session, const char *text); + + +switch_status_t spandsp_stop_tdd_decode_session(switch_core_session_t *session); +switch_status_t spandsp_tdd_decode_session(switch_core_session_t *session); +switch_status_t spandsp_tdd_send_session(switch_core_session_t *session, const char *text); diff --git a/src/mod/applications/mod_spandsp/mod_spandsp_dsp.c b/src/mod/applications/mod_spandsp/mod_spandsp_dsp.c index cc4d4964e2..57ba4547f9 100644 --- a/src/mod/applications/mod_spandsp/mod_spandsp_dsp.c +++ b/src/mod/applications/mod_spandsp/mod_spandsp_dsp.c @@ -35,6 +35,285 @@ #include "mod_spandsp.h" +#define TDD_LEAD 10 + +typedef struct { + switch_core_session_t *session; + v18_state_t *tdd_state; + int head_lead; + int tail_lead; +} switch_tdd_t; + +static switch_bool_t tdd_encode_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type) +{ + switch_tdd_t *pvt = (switch_tdd_t *) user_data; + switch_frame_t *frame = NULL; + switch_bool_t r = SWITCH_TRUE; + + switch (type) { + case SWITCH_ABC_TYPE_INIT: { + break; + } + case SWITCH_ABC_TYPE_CLOSE: + if (pvt->tdd_state) { + v18_free(pvt->tdd_state); + } + break; + case SWITCH_ABC_TYPE_WRITE_REPLACE: + if ((frame = switch_core_media_bug_get_write_replace_frame(bug))) { + int len; + + if (pvt->tail_lead) { + if (!--pvt->tail_lead) { + r = SWITCH_FALSE; + } + memset(frame->data, 0, frame->datalen); + + } else if (pvt->head_lead) { + pvt->head_lead--; + memset(frame->data, 0, frame->datalen); + } else { + len = v18_tx(pvt->tdd_state, frame->data, frame->samples); + + if (!len) { + pvt->tail_lead = TDD_LEAD; + } + } + + switch_core_media_bug_set_write_replace_frame(bug, frame); + } + break; + case SWITCH_ABC_TYPE_WRITE: + default: + break; + } + + return r; +} + +switch_status_t spandsp_stop_tdd_encode_session(switch_core_session_t *session) +{ + switch_media_bug_t *bug; + switch_channel_t *channel = switch_core_session_get_channel(session); + + if ((bug = switch_channel_get_private(channel, "tdd_encode"))) { + switch_channel_set_private(channel, "tdd_encode", NULL); + switch_core_media_bug_remove(session, &bug); + return SWITCH_STATUS_SUCCESS; + } + return SWITCH_STATUS_FALSE; +} + +static void put_text_msg(void *user_data, const uint8_t *msg, int len) +{ + switch_tdd_t *pvt = (switch_tdd_t *) user_data; + switch_event_t *event, *clone; + switch_channel_t *channel = switch_core_session_get_channel(pvt->session); + switch_core_session_t *other_session; + + + switch_channel_add_variable_var_check(channel, "tdd_messages", (char *)msg, SWITCH_FALSE, SWITCH_STACK_PUSH); + + if (switch_event_create(&event, SWITCH_EVENT_MESSAGE) == SWITCH_STATUS_SUCCESS) { + + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "login", "mod_spandsp"); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", "tdd"); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "subject", "TDD MESSAGE"); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Unique-ID", switch_core_session_get_uuid(pvt->session)); + switch_event_add_body(event, (char *)msg); + + if (switch_core_session_get_partner(pvt->session, &other_session) == SWITCH_STATUS_SUCCESS) { + + if (switch_event_dup(&clone, event) == SWITCH_STATUS_SUCCESS) { + switch_core_session_receive_event(other_session, &clone); + } + + if (switch_event_dup(&clone, event) == SWITCH_STATUS_SUCCESS) { + switch_core_session_queue_event(other_session, &clone); + } + + switch_core_session_rwunlock(other_session); + } + + switch_event_fire(&event); + + + } + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pvt->session), SWITCH_LOG_DEBUG, "%s got TDD Message [%s]\n", switch_channel_get_name(channel), (char *)msg); + +} + +switch_status_t spandsp_tdd_send_session(switch_core_session_t *session, const char *text) +{ + v18_state_t *tdd_state; + switch_frame_t *read_frame, write_frame = { 0 }; + uint8_t write_buf[SWITCH_RECOMMENDED_BUFFER_SIZE]; + switch_codec_implementation_t read_impl = { 0 }; + switch_codec_t write_codec = { 0 }; + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_status_t status; + + switch_core_session_get_read_impl(session, &read_impl); + + if (switch_core_codec_init(&write_codec, + "L16", + NULL, + read_impl.actual_samples_per_second, + read_impl.microseconds_per_packet / 1000, + read_impl.number_of_channels, + SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, NULL, + switch_core_session_get_pool(session)) == SWITCH_STATUS_SUCCESS) { + write_frame.data = write_buf; + write_frame.buflen = sizeof(write_buf); + write_frame.datalen = read_impl.decoded_bytes_per_packet; + write_frame.samples = write_frame.datalen / 2; + write_frame.codec = &write_codec; + switch_core_session_set_read_codec(session, &write_codec); + } else { + return SWITCH_STATUS_FALSE; + } + + tdd_state = v18_init(NULL, TRUE, V18_MODE_5BIT_45, put_text_msg, NULL); + + + v18_put(tdd_state, text, -1); + + while(switch_channel_ready(channel)) { + status = switch_core_session_read_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0); + + if (!SWITCH_READ_ACCEPTABLE(status)) { + break; + } + + + if (!v18_tx(tdd_state, (void *)write_buf, write_frame.samples)) { + break; + } + + if (switch_core_session_write_frame(session, &write_frame, SWITCH_IO_FLAG_NONE, 0) != SWITCH_STATUS_SUCCESS) { + break; + } + + } + + switch_core_codec_destroy(&write_codec); + switch_core_session_set_read_codec(session, NULL); + + v18_free(tdd_state); + + return SWITCH_STATUS_SUCCESS; +} + + +switch_status_t spandsp_tdd_encode_session(switch_core_session_t *session, const char *text) +{ + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_media_bug_t *bug; + switch_status_t status; + switch_tdd_t *pvt; + //switch_codec_implementation_t read_impl = { 0 }; + + //switch_core_session_get_read_impl(session, &read_impl); + + if (!(pvt = switch_core_session_alloc(session, sizeof(*pvt)))) { + return SWITCH_STATUS_MEMERR; + } + + pvt->session = session; + pvt->tdd_state = v18_init(NULL, TRUE, V18_MODE_5BIT_45, put_text_msg, NULL); + pvt->head_lead = TDD_LEAD; + + v18_put(pvt->tdd_state, text, -1); + + if ((status = switch_core_media_bug_add(session, "spandsp_tdd_encode", NULL, + tdd_encode_callback, pvt, 0, SMBF_WRITE_REPLACE | SMBF_NO_PAUSE, &bug)) != SWITCH_STATUS_SUCCESS) { + v18_free(pvt->tdd_state); + return status; + } + + switch_channel_set_private(channel, "tdd_encode", bug); + + return SWITCH_STATUS_SUCCESS; +} + + + +///XXX +static switch_bool_t tdd_decode_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type) +{ + switch_tdd_t *pvt = (switch_tdd_t *) user_data; + switch_frame_t *frame = NULL; + switch_bool_t r = SWITCH_TRUE; + + switch (type) { + case SWITCH_ABC_TYPE_INIT: { + break; + } + case SWITCH_ABC_TYPE_CLOSE: + if (pvt->tdd_state) { + v18_free(pvt->tdd_state); + } + break; + case SWITCH_ABC_TYPE_READ_REPLACE: + if ((frame = switch_core_media_bug_get_read_replace_frame(bug))) { + + v18_rx(pvt->tdd_state, frame->data, frame->samples); + + switch_core_media_bug_set_read_replace_frame(bug, frame); + } + break; + case SWITCH_ABC_TYPE_WRITE: + default: + break; + } + + return r; +} + +switch_status_t spandsp_stop_tdd_decode_session(switch_core_session_t *session) +{ + switch_media_bug_t *bug; + switch_channel_t *channel = switch_core_session_get_channel(session); + + if ((bug = switch_channel_get_private(channel, "tdd_decode"))) { + switch_channel_set_private(channel, "tdd_decode", NULL); + switch_core_media_bug_remove(session, &bug); + return SWITCH_STATUS_SUCCESS; + } + return SWITCH_STATUS_FALSE; +} + +switch_status_t spandsp_tdd_decode_session(switch_core_session_t *session) +{ + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_media_bug_t *bug; + switch_status_t status; + switch_tdd_t *pvt; + //switch_codec_implementation_t read_impl = { 0 }; + + //switch_core_session_get_read_impl(session, &read_impl); + + if (!(pvt = switch_core_session_alloc(session, sizeof(*pvt)))) { + return SWITCH_STATUS_MEMERR; + } + + pvt->session = session; + pvt->tdd_state = v18_init(NULL, FALSE, V18_MODE_5BIT_45, put_text_msg, pvt); + + if ((status = switch_core_media_bug_add(session, "spandsp_tdd_decode", NULL, + tdd_decode_callback, pvt, 0, SMBF_READ_REPLACE | SMBF_NO_PAUSE, &bug)) != SWITCH_STATUS_SUCCESS) { + v18_free(pvt->tdd_state); + return status; + } + + switch_channel_set_private(channel, "tdd_decode", bug); + + return SWITCH_STATUS_SUCCESS; +} + +///XXX + typedef struct { switch_core_session_t *session; dtmf_rx_state_t *dtmf_detect;