diff options
author | Andreas Eversberg <jolly@eversberg.eu> | 2017-10-28 07:11:40 +0200 |
---|---|---|
committer | Andreas Eversberg <jolly@eversberg.eu> | 2017-11-05 16:58:31 +0100 |
commit | ee3fbeb03b503bedcc1f61791e678eb9998c4d3e (patch) | |
tree | d83e927305dbb86fd08c39bf90ef8239121c957d /src/common | |
parent | 7cbebaeb75211bbeb4d3f3b7bfe59ea6bce12aa1 (diff) |
Split call control from built-in call console by using MNCC layer
Diffstat (limited to 'src/common')
-rw-r--r-- | src/common/Makefile.am | 1 | ||||
-rw-r--r-- | src/common/call.c | 942 | ||||
-rw-r--r-- | src/common/call.h | 40 | ||||
-rw-r--r-- | src/common/cause.h | 3 | ||||
-rw-r--r-- | src/common/debug.c | 4 | ||||
-rw-r--r-- | src/common/main_mobile.c | 41 | ||||
-rw-r--r-- | src/common/mncc_console.c | 473 | ||||
-rw-r--r-- | src/common/mncc_console.h | 10 | ||||
-rw-r--r-- | src/common/mncc_sock.c | 31 | ||||
-rw-r--r-- | src/common/mncc_sock.h | 7 |
10 files changed, 812 insertions, 740 deletions
diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 0ae7f9c..3b3375e 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -27,6 +27,7 @@ libmobile_a_SOURCES = \ cause.c \ call.c \ testton.c \ + mncc_console.c \ mncc_sock.c \ hagelbarger.c \ display_status.c \ diff --git a/src/common/call.c b/src/common/call.c index 1bfc233..53c0886 100644 --- a/src/common/call.c +++ b/src/common/call.c @@ -1,4 +1,4 @@ -/* built-in call control +/* interface between mobile network/phone implementation and MNCC * * (C) 2016 by Andreas Eversberg <jolly@eversberg.eu> * All Rights Reserved @@ -30,8 +30,7 @@ #include "cause.h" #include "call.h" #include "timer.h" -#include "mncc_sock.h" -#include "testton.h" +#include "mncc.h" #define DISC_TIMEOUT 30 @@ -52,53 +51,35 @@ static double level_of(double *samples, int count) } #endif +static int send_patterns; /* send patterns towards fixed network */ +static int release_on_disconnect; /* release towards mobile phone, if MNCC call disconnects, don't send disconnect tone */ + /* stream patterns/announcements */ -int16_t *test_spl = NULL; int16_t *ringback_spl = NULL; -int16_t *hangup_spl = NULL; -int16_t *busy_spl = NULL; -int16_t *noanswer_spl = NULL; -int16_t *outoforder_spl = NULL; -int16_t *invalidnumber_spl = NULL; -int16_t *congestion_spl = NULL; -int16_t *recall_spl = NULL; -int test_size = 0; int ringback_size = 0; -int hangup_size = 0; -int busy_size = 0; -int noanswer_size = 0; -int outoforder_size = 0; -int invalidnumber_size = 0; -int congestion_size = 0; -int recall_size = 0; -int test_max = 0; int ringback_max = 0; +int16_t *hangup_spl = NULL; +int hangup_size = 0; int hangup_max = 0; +int16_t *busy_spl = NULL; +int busy_size = 0; int busy_max = 0; +int16_t *noanswer_spl = NULL; +int noanswer_size = 0; int noanswer_max = 0; +int16_t *outoforder_spl = NULL; +int outoforder_size = 0; int outoforder_max = 0; +int16_t *invalidnumber_spl = NULL; +int invalidnumber_size = 0; int invalidnumber_max = 0; +int16_t *congestion_spl = NULL; +int congestion_size = 0; int congestion_max = 0; +int16_t *recall_spl = NULL; +int recall_size = 0; int recall_max = 0; -enum call_state { - CALL_IDLE = 0, - CALL_SETUP_MO, - CALL_SETUP_MT, - CALL_ALERTING, - CALL_CONNECT, - CALL_DISCONNECTED, -}; - -static const char *call_state_name[] = { - "IDLE", - "SETUP_MO", - "SETUP_MT", - "ALERTING", - "CONNECT", - "DISCONNECTED", -}; - enum audio_pattern { PATTERN_NONE = 0, PATTERN_TEST, @@ -112,18 +93,13 @@ enum audio_pattern { PATTERN_RECALL, }; -void get_pattern(const int16_t **spl, int *size, int *max, enum audio_pattern pattern) +static void get_pattern(const int16_t **spl, int *size, int *max, enum audio_pattern pattern) { *spl = NULL; *size = 0; *max = 0; switch (pattern) { - case PATTERN_TEST: - *spl = test_spl; - *size = test_size; - *max = test_max; - break; case PATTERN_RINGBACK: no_recall: *spl = ringback_spl; @@ -184,82 +160,6 @@ no_invalidnumber: } } -static int new_callref = 0; /* toward mobile */ - -/* built in call instance */ -typedef struct call { - int callref; - enum call_state state; - int disc_cause; /* cause that has been sent by transceiver instance for release */ - char station_id[16]; - char dialing[16]; - char audiodev[64]; /* headphone interface, if used */ - int samplerate; /* sample rate of headphone interface */ - void *sound; /* headphone interface */ - int latspl; /* sample latency at headphone interface */ - samplerate_t srstate; /* patterns/announcement upsampling */ - jitter_t dejitter; /* headphone audio dejittering */ - int audio_pos; /* position when playing patterns */ - int test_audio_pos; /* position for test tone toward mobile */ - int dial_digits; /* number of digits to be dialed */ - int loopback; /* loopback test for echo */ - int echo_test; /* send echo back to mobile phone */ - int use_mncc_sock; /* use MNCC socket instead of built-in call control */ - int send_patterns; /* send patterns towards fixed network */ - int release_on_disconnect; /* release towards mobile phone, if MNCC call disconnects, don't send disconnect tone */ - -} call_t; - -static call_t call; - -static void call_new_state(enum call_state state) -{ - PDEBUG(DCALL, DEBUG_DEBUG, "Call state '%s' -> '%s'\n", call_state_name[call.state], call_state_name[state]); - call.state = state; - call.audio_pos = 0; - call.test_audio_pos = 0; -} - -static void get_call_patterns(int16_t *samples, int length, enum audio_pattern pattern) -{ - const int16_t *spl; - int size, max, pos; - - get_pattern(&spl, &size, &max, pattern); - - /* stream sample */ - pos = call.audio_pos; - while(length--) { - if (pos >= size) - *samples++ = 0; - else - *samples++ = spl[pos] >> 1; - if (++pos == max) - pos = 0; - } - call.audio_pos = pos; -} - -static void get_test_patterns(int16_t *samples, int length) -{ - const int16_t *spl; - int size, max, pos; - - get_pattern(&spl, &size, &max, PATTERN_TEST); - - /* stream sample */ - pos = call.test_audio_pos; - while(length--) { - if (pos >= size) - *samples++ = 0; - else - *samples++ = spl[pos] >> 2; - if (++pos == max) - pos = 0; - } - call.test_audio_pos = pos; -} - static enum audio_pattern cause2pattern(int cause) { int pattern; @@ -290,11 +190,21 @@ static enum audio_pattern cause2pattern(int cause) return pattern; } +enum process_state { + PROCESS_IDLE = 0, /* IDLE */ + PROCESS_SETUP_RO, /* call from radio to MNCC */ + PROCESS_SETUP_RT, /* call from MNCC to radio */ + PROCESS_ALERTING_RO, /* call from radio to MNCC */ + PROCESS_ALERTING_RT, /* call from MNCC to radio */ + PROCESS_CONNECT, + PROCESS_DISCONNECT, +}; + /* MNCC call instance */ typedef struct process { struct process *next; int callref; - enum call_state state; + enum process_state state; int audio_disconnected; /* if not associated with transceiver anymore */ enum audio_pattern pattern; int audio_pos; @@ -306,7 +216,7 @@ static process_t *process_head = NULL; static void process_timeout(struct timer *timer); -static void create_process(int callref, int state) +static process_t *create_process(int callref, enum process_state state) { process_t *process; @@ -321,6 +231,8 @@ static void create_process(int callref, int state) process->callref = callref; process->state = state; + + return process; } static void destroy_process(int callref) @@ -341,108 +253,55 @@ static void destroy_process(int callref) PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref); } -static int is_process(int callref) -{ - process_t *process = process_head; - - while (process) { - if (process->callref == callref) - return 1; - process = process->next; - } - return 0; -} - -static enum call_state is_process_state(int callref) +static process_t *get_process(int callref) { process_t *process = process_head; while (process) { if (process->callref == callref) - return process->state; + return process; process = process->next; } - return CALL_IDLE; + return NULL; } -static void set_state_process(int callref, enum call_state state) +static void new_state_process(int callref, enum process_state state) { - process_t *process = process_head; + process_t *process = get_process(callref); - while (process) { - if (process->callref == callref) { - process->state = state; - return; - } - process = process->next; + if (!process) { + PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref); + return; } - PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref); + process->state = state; } static void set_pattern_process(int callref, enum audio_pattern pattern) { - process_t *process = process_head; + process_t *process = get_process(callref); - while (process) { - if (process->callref == callref) { - process->pattern = pattern; - process->audio_pos = 0; - return; - } - process = process->next; + if (!process) { + PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref); + return; } - PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref); + process->pattern = pattern; + process->audio_pos = 0; } /* disconnect audio, now send audio directly from pattern/announcement, not from transceiver */ static void disconnect_process(int callref, int cause) { - process_t *process = process_head; - - while (process) { - if (process->callref == callref) { - process->pattern = cause2pattern(cause); - process->audio_disconnected = 1; - process->audio_pos = 0; - process->cause = cause; - timer_start(&process->timer, DISC_TIMEOUT); - return; - } - process = process->next; - } - PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref); -} + process_t *process = get_process(callref); -/* check if audio is disconnected */ -static int is_process_disconnected(int callref) -{ - process_t *process = process_head; - - while (process) { - if (process->callref == callref) { - return process->audio_disconnected; - } - process = process->next; - } - PDEBUG(DCALL, DEBUG_DEBUG, "Process with callref 0x%x not found, this is ok!\n", callref); - - return 0; -} - -/* check if pattern is set, so we send patterns and announcements */ -static int is_process_pattern(int callref) -{ - process_t *process = process_head; - - while (process) { - if (process->callref == callref) { - return (process->pattern != PATTERN_NONE); - } - process = process->next; + if (!process) { + PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref); + return; } - PDEBUG(DCALL, DEBUG_DEBUG, "Process with callref 0x%x not found, this is ok!\n", callref); - - return 0; + process->pattern = cause2pattern(cause); + process->audio_disconnected = 1; + process->audio_pos = 0; + process->cause = cause; + timer_start(&process->timer, DISC_TIMEOUT); } static void get_process_patterns(process_t *process, int16_t *samples, int length) @@ -478,301 +337,56 @@ static void process_timeout(struct timer *timer) mncc->msg_type = MNCC_REL_IND; mncc->callref = process->callref; mncc->fields |= MNCC_F_CAUSE; - mncc->cause.location = 1; /* private local */ + mncc->cause.location = LOCATION_PRIVATE_LOCAL; mncc->cause.value = process->cause; destroy_process(process->callref); - PDEBUG(DMNCC, DEBUG_INFO, "Releasing MNCC call towards Network (after timeout)\n"); - mncc_write(buf, sizeof(struct gsm_mncc)); - } -} - -int call_init(const char *station_id, const char *audiodev, int samplerate, int latency, int dial_digits, int loopback, int use_mncc_sock, int send_patterns, int release_on_disconnect, int echo_test) -{ - int rc = 0; - - init_testton(); - - memset(&call, 0, sizeof(call)); - strncpy(call.station_id, station_id, sizeof(call.station_id) - 1); - call.latspl = latency * samplerate / 1000; - call.dial_digits = dial_digits; - call.loopback = loopback; - call.echo_test = echo_test; - call.use_mncc_sock = use_mncc_sock; - call.send_patterns = send_patterns; - call.release_on_disconnect = release_on_disconnect; - call.samplerate = samplerate; - strncpy(call.audiodev, audiodev, sizeof(call.audiodev) - 1); - - if (use_mncc_sock && audiodev[0]) { - PDEBUG(DSENDER, DEBUG_ERROR, "You selected MNCC interface, but it cannot be used with call device (headset).\n"); - rc = -EINVAL; - goto error; - } - if (use_mncc_sock && echo_test) { - PDEBUG(DSENDER, DEBUG_ERROR, "You selected MNCC interface, but it cannot be used with echo test.\n"); - rc = -EINVAL; - goto error; - } - if (audiodev[0] && echo_test) { - PDEBUG(DSENDER, DEBUG_ERROR, "You selected call device (headset), but it cannot be used with echo test.\n"); - rc = -EINVAL; - goto error; - } - - if (use_mncc_sock) - return 0; - - if (!audiodev[0]) - return 0; - - rc = init_samplerate(&call.srstate, 8000.0, (double)samplerate, 3300.0); - if (rc < 0) { - PDEBUG(DSENDER, DEBUG_ERROR, "Failed to init sample rate conversion!\n"); - goto error; - } - - rc = jitter_create(&call.dejitter, samplerate / 5); - if (rc < 0) { - PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create and init dejitter buffer!\n"); - goto error; + PDEBUG(DCALL, DEBUG_INFO, "Releasing MNCC call towards fixed network (after timeout)\n"); + mncc_up(buf, sizeof(struct gsm_mncc)); } - - return 0; - -error: - call_cleanup(); - return rc; } -int call_open_audio(int latspl) +int call_init(int _send_patterns, int _release_on_disconnect) { - if (!call.audiodev[0]) - return 0; - - /* open sound device for call control */ - /* use factor 1.4 of speech level for complete range of sound card */ - call.sound = sound_open(call.audiodev, NULL, NULL, 1, 0.0, call.samplerate, latspl, 1.4, 4000.0); - if (!call.sound) { - PDEBUG(DSENDER, DEBUG_ERROR, "No sound device!\n"); - return -EIO; - } + send_patterns = _send_patterns; + release_on_disconnect = _release_on_disconnect; return 0; } -int call_start_audio(void) -{ - if (!call.audiodev[0]) - return 0; - - return sound_start(call.sound); -} - -void call_cleanup(void) -{ - if (call.use_mncc_sock) - return; - - /* close sound devoice */ - if (call.sound) - sound_close(call.sound); - - jitter_destroy(&call.dejitter); - - if (process_head) { - PDEBUG(DMNCC, DEBUG_ERROR, "Not all MNCC instances have been released!\n"); - } -} - -static char console_text[256]; -static char console_clear[256]; -static int console_len = 0; - -static void process_ui(int c) -{ - char text[256]; - int len; - - switch (call.state) { - case CALL_IDLE: - if (c > 0) { - if (c >= '0' && c <= '9' && (int)strlen(call.station_id) < call.dial_digits) { - call.station_id[strlen(call.station_id) + 1] = '\0'; - call.station_id[strlen(call.station_id)] = c; - } - if ((c == 8 || c == 127) && strlen(call.station_id)) - call.station_id[strlen(call.station_id) - 1] = '\0'; -dial_after_hangup: - if (c == 'd' && (int)strlen(call.station_id) == call.dial_digits) { - int rc; - int callref = ++new_callref; - - PDEBUG(DCALL, DEBUG_INFO, "Outgoing call to %s\n", call.station_id); - call.dialing[0] = '\0'; - call_new_state(CALL_SETUP_MT); - call.callref = callref; - rc = call_out_setup(callref, "", TYPE_NOTAVAIL, call.station_id); - if (rc < 0) { - PDEBUG(DCALL, DEBUG_NOTICE, "Call rejected, cause %d\n", -rc); - call_new_state(CALL_DISCONNECTED); - call.callref = 0; - call.disc_cause = -rc; - } - } - } - if (call.dial_digits != (int)strlen(call.station_id)) - sprintf(text, "on-hook: %s%s (enter digits 0..9)\r", call.station_id, "..............." + 15 - call.dial_digits + strlen(call.station_id)); - else - sprintf(text, "on-hook: %s (press d=dial)\r", call.station_id); - break; - case CALL_SETUP_MO: - case CALL_SETUP_MT: - case CALL_ALERTING: - case CALL_CONNECT: - case CALL_DISCONNECTED: - if (c > 0) { - if (c == 'h' || (c == 'd' && call.state == CALL_DISCONNECTED)) { - PDEBUG(DCALL, DEBUG_INFO, "Call hangup\n"); - call_new_state(CALL_IDLE); - if (call.callref) { - call_out_release(call.callref, CAUSE_NORMAL); - call.callref = 0; - } - if (c == 'd') - goto dial_after_hangup; - } - } - if (call.state == CALL_SETUP_MT) - sprintf(text, "call setup: %s (press h=hangup)\r", call.station_id); - if (call.state == CALL_ALERTING) - sprintf(text, "call ringing: %s (press h=hangup)\r", call.station_id); - if (call.state == CALL_CONNECT) { - if (call.dialing[0]) - sprintf(text, "call active: %s->%s (press h=hangup)\r", call.station_id, call.dialing); - else - sprintf(text, "call active: %s (press h=hangup)\r", call.station_id); - } - if (call.state == CALL_DISCONNECTED) - sprintf(text, "call disconnected: %s (press h=hangup d=redial)\r", cause_name(call.disc_cause)); - break; - } - /* skip if nothing has changed */ - len = strlen(text); - if (console_len == len && !memcmp(console_text, text, len)) - return; - clear_console_text(); - console_len = len; - memcpy(console_text, text, len); - memset(console_clear, ' ', len - 1); - console_clear[len - 1] = '\r'; - print_console_text(); - fflush(stdout); -} - -void clear_console_text(void) -{ - if (!console_len) - return; - - fwrite(console_clear, console_len, 1, stdout); - // note: fflused by user of this function - console_len = 0; -} - -void print_console_text(void) +/* Setup is received from transceiver. */ +static int _indicate_setup(int callref, const char *callerid, const char *dialing) { - if (!console_len) - return; + uint8_t buf[sizeof(struct gsm_mncc)]; + struct gsm_mncc *mncc = (struct gsm_mncc *)buf; + int rc; - printf("\033[1;37m"); - fwrite(console_text, console_len, 1, stdout); - printf("\033[0;39m"); + memset(buf, 0, sizeof(buf)); + mncc->msg_type = MNCC_SETUP_IND; + mncc->callref = callref; + mncc->fields |= MNCC_F_CALLING; + if (callerid) { + strncpy(mncc->calling.number, callerid, sizeof(mncc->calling.number) - 1); + mncc->calling.type = 4; /* caller ID is of type 'subscriber' */ + } // otherwise unknown and no number + mncc->fields |= MNCC_F_CALLED; + strncpy(mncc->called.number, dialing, sizeof(mncc->called.number) - 1); + mncc->called.type = 0; /* dialing is of type 'unknown' */ + mncc->lchan_type = GSM_LCHAN_TCH_F; + mncc->fields |= MNCC_F_BEARER_CAP; + mncc->bearer_cap.speech_ver[0] = BCAP_ANALOG_8000HZ; + mncc->bearer_cap.speech_ver[1] = -1; + + PDEBUG(DCALL, DEBUG_INFO, "Indicate MNCC setup towards fixed network\n"); + rc = mncc_up(buf, sizeof(struct gsm_mncc)); + if (rc < 0) + destroy_process(callref); + return rc; } - -/* get keys from keyboad to control call via console - * returns 1 on exit (ctrl+c) */ -void process_call(int c) +int call_up_setup(int callref, const char *callerid, const char *dialing) { - if (call.use_mncc_sock) { - mncc_handle(); - return; - } - - if (!call.loopback) - process_ui(c); - - if (!call.sound) - return; - - /* handle audio, if sound device is used */ - sample_t samples[call.latspl + 10], *samples_list[1]; - uint8_t *power_list[1]; - double rf_level_db[1]; - int count; int rc; - count = sound_get_tosend(call.sound, call.latspl); - if (count < 0) { - PDEBUG(DSENDER, DEBUG_ERROR, "Failed to get samples in buffer (rc = %d)!\n", count); - if (count == -EPIPE) - PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover.\n"); - return; - } - if (count > 0) { - int16_t spl[count + 10]; /* more than enough, count will be reduced by scaling with factor */ - switch(call.state) { - case CALL_ALERTING: - /* the count will be an approximation that will be upsampled */ - count = (int)((double)count / call.srstate.factor + 0.5); - get_call_patterns(spl, count, PATTERN_RINGBACK); - int16_to_samples(samples, spl, count); - count = samplerate_upsample(&call.srstate, samples, count, samples); - /* prevent click after hangup */ - jitter_clear(&call.dejitter); - break; - case CALL_DISCONNECTED: - /* the count will be an approximation that will be upsampled */ - count = (int)((double)count / call.srstate.factor + 0.5); - get_call_patterns(spl, count, cause2pattern(call.disc_cause)); - int16_to_samples(samples, spl, count); - count = samplerate_upsample(&call.srstate, samples, count, samples); - /* prevent click after hangup */ - jitter_clear(&call.dejitter); - break; - default: - jitter_load(&call.dejitter, samples, count); - } - samples_list[0] = samples; - power_list[0] = NULL; - rc = sound_write(call.sound, samples_list, power_list, count, NULL, NULL, 1); - if (rc < 0) { - PDEBUG(DSENDER, DEBUG_ERROR, "Failed to write TX data to sound device (rc = %d)\n", rc); - if (rc == -EPIPE) - PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover.\n"); - return; - } - } - samples_list[0] = samples; - count = sound_read(call.sound, samples_list, call.latspl, 1, rf_level_db); - if (count < 0) { - PDEBUG(DSENDER, DEBUG_ERROR, "Failed to read from sound device (rc = %d)!\n", count); - if (count == -EPIPE) - PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover.\n"); - return; - } - if (count) { - if (call.loopback == 3) - jitter_save(&call.dejitter, samples, count); - count = samplerate_downsample(&call.srstate, samples, count); - call_rx_audio(call.callref, samples, count); - } -} - -/* Setup is received from transceiver. */ -int call_in_setup(int callref, const char *callerid, const char *dialing) -{ if (!callref) { PDEBUG(DCALL, DEBUG_DEBUG, "Ignoring setup, because callref not set. (not for us)\n"); return -CAUSE_INVALCALLREF; @@ -787,63 +401,31 @@ int call_in_setup(int callref, const char *callerid, const char *dialing) if (!strcmp(dialing, "010")) PDEBUG(DCALL, DEBUG_INFO, " -> Call to Operator '%s'\n", dialing); - if (call.use_mncc_sock) { - uint8_t buf[sizeof(struct gsm_mncc)]; - struct gsm_mncc *mncc = (struct gsm_mncc *)buf; - int rc; - memset(buf, 0, sizeof(buf)); - mncc->msg_type = MNCC_SETUP_IND; - mncc->callref = callref; - mncc->fields |= MNCC_F_CALLING; - if (callerid) { - strncpy(mncc->calling.number, callerid, sizeof(mncc->calling.number) - 1); - mncc->calling.type = 4; /* caller ID is of type 'subscriber' */ - } // otherwise unknown and no number - mncc->fields |= MNCC_F_CALLED; - strncpy(mncc->called.number, dialing, sizeof(mncc->called.number) - 1); - mncc->called.type = 0; /* dialing is of type 'unknown' */ - mncc->lchan_type = GSM_LCHAN_TCH_F; - mncc->fields |= MNCC_F_BEARER_CAP; - mncc->bearer_cap.speech_ver[0] = BCAP_ANALOG_8000HZ; - mncc->bearer_cap.speech_ver[1] = -1; + create_process(callref, PROCESS_SETUP_RO); - PDEBUG(DMNCC, DEBUG_INFO, "Sending MNCC call towards Network\n"); + rc = _indicate_setup(callref, callerid, dialing); - create_process(callref, CALL_SETUP_MO); + return rc; +} - rc = mncc_write(buf, sizeof(struct gsm_mncc)); - if (rc < 0) { - PDEBUG(DCALL, DEBUG_NOTICE, "We have no MNCC connection, rejecting.\n"); - destroy_process(callref); - return -CAUSE_TEMPFAIL; - } - return 0; - } +/* Transceiver indicates alerting. */ +static void _indicate_alerting(int callref) +{ + uint8_t buf[sizeof(struct gsm_mncc)]; + struct gsm_mncc *mncc = (struct gsm_mncc *)buf; + int rc; - /* setup is also allowed on disconnected call */ - if (call.state == CALL_DISCONNECTED) - call_new_state(CALL_IDLE); - if (call.state != CALL_IDLE) { - PDEBUG(DCALL, DEBUG_NOTICE, "We are busy, rejecting.\n"); - return -CAUSE_BUSY; - } - call.callref = callref; - if (callerid) { - strncpy(call.station_id, callerid, call.dial_digits); - call.station_id[call.dial_digits] = '\0'; - } - strncpy(call.dialing, dialing, sizeof(call.dialing) - 1); - call.dialing[sizeof(call.dialing) - 1] = '\0'; - call_new_state(CALL_CONNECT); - PDEBUG(DCALL, DEBUG_INFO, "Call automatically answered\n"); - call_out_answer(callref); + memset(buf, 0, sizeof(buf)); + mncc->msg_type = MNCC_ALERT_IND; + mncc->callref = callref; - return 0; + PDEBUG(DCALL, DEBUG_INFO, "Indicate MNCC alerting towards fixed network\n"); + rc = mncc_up(buf, sizeof(struct gsm_mncc)); + if (rc < 0) + destroy_process(callref); } - -/* Transceiver indicates alerting. */ -void call_in_alerting(int callref) +void call_up_alerting(int callref) { if (!callref) { PDEBUG(DCALL, DEBUG_DEBUG, "Ignoring alerting, because callref not set. (not for us)\n"); @@ -852,27 +434,10 @@ void call_in_alerting(int callref) PDEBUG(DCALL, DEBUG_INFO, "Call is alerting\n"); - if (call.use_mncc_sock) { - uint8_t buf[sizeof(struct gsm_mncc)]; - struct gsm_mncc *mncc = (struct gsm_mncc *)buf; - - if (!call.send_patterns) { - memset(buf, 0, sizeof(buf)); - mncc->msg_type = MNCC_ALERT_IND; - mncc->callref = callref; - PDEBUG(DMNCC, DEBUG_INFO, "Indicate MNCC alerting towards Network\n"); - mncc_write(buf, sizeof(struct gsm_mncc)); - } else - set_pattern_process(callref, PATTERN_RINGBACK); - return; - } - - if (call.callref != callref) { - PDEBUG(DCALL, DEBUG_ERROR, "invalid call ref.\n"); - call_out_release(callref, CAUSE_INVALCALLREF); - return; - } - call_new_state(CALL_ALERTING); + if (!send_patterns) + _indicate_alerting(callref); + set_pattern_process(callref, PATTERN_RINGBACK); + new_state_process(callref, PROCESS_ALERTING_RT); } /* Transceiver indicates answer. */ @@ -880,6 +445,7 @@ static void _indicate_answer(int callref, const char *connect_id) { uint8_t buf[sizeof(struct gsm_mncc)]; struct gsm_mncc *mncc = (struct gsm_mncc *)buf; + int rc; memset(buf, 0, sizeof(buf)); mncc->msg_type = MNCC_SETUP_CNF; @@ -892,11 +458,12 @@ static void _indicate_answer(int callref, const char *connect_id) mncc->connected.present = 0; mncc->connected.screen = 3; - - PDEBUG(DMNCC, DEBUG_INFO, "Indicate MNCC answer towards Network\n"); - mncc_write(buf, sizeof(struct gsm_mncc)); + PDEBUG(DCALL, DEBUG_INFO, "Indicate MNCC answer towards fixed network\n"); + rc = mncc_up(buf, sizeof(struct gsm_mncc)); + if (rc < 0) + destroy_process(callref); } -void call_in_answer(int callref, const char *connect_id) +void call_up_answer(int callref, const char *connect_id) { if (!callref) { PDEBUG(DCALL, DEBUG_DEBUG, "Ignoring answer, because callref not set. (not for us)\n"); @@ -905,26 +472,35 @@ void call_in_answer(int callref, const char *connect_id) PDEBUG(DCALL, DEBUG_INFO, "Call has been answered by '%s'\n", connect_id); - if (call.use_mncc_sock) { + if (!send_patterns) _indicate_answer(callref, connect_id); - set_pattern_process(callref, PATTERN_NONE); - set_state_process(callref, CALL_CONNECT); - return; - } - - if (call.callref != callref) { - PDEBUG(DCALL, DEBUG_ERROR, "invalid call ref.\n"); - call_out_release(callref, CAUSE_INVALCALLREF); - return; - } - call_new_state(CALL_CONNECT); - strncpy(call.station_id, connect_id, call.dial_digits); - call.station_id[call.dial_digits] = '\0'; + set_pattern_process(callref, PATTERN_NONE); + new_state_process(callref, PROCESS_CONNECT); } /* Transceiver indicates release. */ -void call_in_release(int callref, int cause) +static void _indicate_disconnect_release(int callref, int cause, int disc) +{ + uint8_t buf[sizeof(struct gsm_mncc)]; + struct gsm_mncc *mncc = (struct gsm_mncc *)buf; + int rc; + + memset(buf, 0, sizeof(buf)); + mncc->msg_type = (disc) ? MNCC_DISC_IND : MNCC_REL_IND; + mncc->callref = callref; + mncc->fields |= MNCC_F_CAUSE; + mncc->cause.location = LOCATION_PRIVATE_LOCAL; + mncc->cause.value = cause; + + PDEBUG(DCALL, DEBUG_INFO, "Indicate MNCC %s towards fixed network\n", (disc) ? "disconnect" : "release"); + rc = mncc_up(buf, sizeof(struct gsm_mncc)); + if (rc < 0) + destroy_process(callref); +} +void call_up_release(int callref, int cause) { + process_t *process; + if (!callref) { PDEBUG(DCALL, DEBUG_DEBUG, "Ignoring release, because callref not set. (not for us)\n"); return; @@ -932,99 +508,69 @@ void call_in_release(int callref, int cause) PDEBUG(DCALL, DEBUG_INFO, "Call has been released with cause=%d\n", cause); - if (call.use_mncc_sock) { - uint8_t buf[sizeof(struct gsm_mncc)]; - struct gsm_mncc *mncc = (struct gsm_mncc *)buf; - - memset(buf, 0, sizeof(buf)); - mncc->msg_type = MNCC_REL_IND; - mncc->callref = callref; - mncc->fields |= MNCC_F_CAUSE; - mncc->cause.location = 1; /* private local */ - mncc->cause.value = cause; - - if (is_process(callref)) { - if (!call.send_patterns - || is_process_state(callref) == CALL_DISCONNECTED - || is_process_state(callref) == CALL_SETUP_MO) { - PDEBUG(DMNCC, DEBUG_INFO, "Releasing MNCC call towards Network\n"); - destroy_process(callref); - mncc_write(buf, sizeof(struct gsm_mncc)); - } else { - disconnect_process(callref, cause); - } + process = get_process(callref); + if (process) { + /* just keep MNCC connection it tones shall be sent. + * no tones while setting up / alerting the call. */ + if (send_patterns && !process->state == PROCESS_SETUP_RO && !process->state == PROCESS_ALERTING_RO) + disconnect_process(callref, cause); + else + if (process->state == PROCESS_DISCONNECT + || process->state == PROCESS_SETUP_RO + || process->state == PROCESS_ALERTING_RO) { + destroy_process(callref); + _indicate_disconnect_release(callref, cause, 0); } else { - PDEBUG(DMNCC, DEBUG_INFO, "Releasing MNCC call towards Network\n"); - mncc_write(buf, sizeof(struct gsm_mncc)); + disconnect_process(callref, cause); + _indicate_disconnect_release(callref, cause, 1); } - return; - } - - if (call.callref != callref) { - PDEBUG(DCALL, DEBUG_ERROR, "invalid call ref.\n"); - /* don't send release, because caller already released */ - return; + } else { + /* we don't know about the process, just send release to upper layer anyway */ + _indicate_disconnect_release(callref, cause, 0); } - call_new_state(CALL_DISCONNECTED); - call.callref = 0; - call.disc_cause = cause; } /* turn recall tone on or off */ void call_tone_recall(int callref, int on) { - /* can do that only with MNCC socket */ - if (call.use_mncc_sock) - set_pattern_process(callref, (on) ? PATTERN_RECALL : PATTERN_NONE); + set_pattern_process(callref, (on) ? PATTERN_RECALL : PATTERN_NONE); } /* forward audio to MNCC or call instance */ -void call_tx_audio(int callref, sample_t *samples, int count) +void call_up_audio(int callref, sample_t *samples, int count) { + if (count != 160) { + fprintf(stderr, "Samples must be 160, please fix!\n"); + abort(); + } + /* is MNCC us used, forward audio */ + uint8_t buf[sizeof(struct gsm_data_frame) + 160 * sizeof(int16_t)]; + struct gsm_data_frame *data = (struct gsm_data_frame *)buf; + process_t *process; + if (!callref) return; - /* is MNCC us used, forward audio */ - if (call.use_mncc_sock) { - uint8_t buf[sizeof(struct gsm_data_frame) + count * sizeof(int16_t)]; - struct gsm_data_frame *data = (struct gsm_data_frame *)buf; - - /* if we are disconnected, ignore audio */ - if (is_process_pattern(callref)) - return; + /* if we are disconnected, ignore audio */ + process = get_process(callref); + if (!process || process->pattern != PATTERN_NONE) + return; - /* forward audio */ - data->msg_type = ANALOG_8000HZ; - data->callref = callref; + /* forward audio */ + data->msg_type = ANALOG_8000HZ; + data->callref = callref; #ifdef DEBUG_LEVEL - double lev = level_of(samples, count); - printf(" mobil-level: %s%.4f\n", debug_db(lev), (20 * log10(lev))); + double lev = level_of(samples, count); + printf(" mobil-level: %s%.4f\n", debug_db(lev), (20 * log10(lev))); #endif - samples_to_int16((int16_t *)data->data, samples, count); - - mncc_write(buf, sizeof(buf)); - } else - /* else, save audio from transceiver to jitter buffer */ - if (call.sound) { - sample_t up[(int)((double)count * call.srstate.factor + 0.5) + 10]; - count = samplerate_upsample(&call.srstate, samples, count, up); - jitter_save(&call.dejitter, up, count); - } else - /* else, if echo test is used, send echo back to mobile */ - if (call.echo_test) { - call_rx_audio(callref, samples, count); - } else - /* else, if no sound is used, send test tone to mobile */ - if (call.state == CALL_CONNECT) { - int16_t spl[count]; - get_test_patterns(spl, count); - int16_to_samples(samples, spl, count); - call_rx_audio(callref, samples, count); - } + samples_to_int16((int16_t *)data->data, samples, count); + + mncc_up(buf, sizeof(buf)); + /* don't destroy process here in case of an error */ } /* clock that is used to transmit patterns */ -void call_mncc_clock(void) +void call_clock(void) { process_t *process = process_head; uint8_t buf[sizeof(struct gsm_data_frame) + 160 * sizeof(int16_t)]; @@ -1043,14 +589,15 @@ void call_mncc_clock(void) printf(" mobil-level: %s%.4f\n", debug_db(lev), (20 * log10(lev))); samples_to_int16((int16_t *)data->data, samples, 160); #endif - mncc_write(buf, sizeof(buf)); + mncc_up(buf, sizeof(buf)); + /* don't destroy process here in case of an error */ } process = process->next; } } -/* mncc messages received from network */ -void call_mncc_recv(uint8_t *buf, int length) +/* mncc messages received from fixed network */ +void mncc_down(uint8_t *buf, int length) { struct gsm_mncc *mncc = (struct gsm_mncc *)buf; char number[sizeof(mncc->called.number)]; @@ -1058,41 +605,53 @@ void call_mncc_recv(uint8_t *buf, int length) enum number_type caller_type; int callref; int rc; + process_t *process; + + callref = mncc->callref; + process = get_process(callref); + if (!process) { + if (mncc->msg_type == MNCC_SETUP_REQ) + process = create_process(callref, PROCESS_SETUP_RT); + else { + if (mncc->msg_type != MNCC_REL_REQ) + PDEBUG(DCALL, DEBUG_ERROR, "No process!\n"); + return; + } + } if (mncc->msg_type == ANALOG_8000HZ) { struct gsm_data_frame *data = (struct gsm_data_frame *)buf; - int count = (length - sizeof(struct gsm_data_frame)) / 2; - sample_t samples[count]; + sample_t samples[160]; /* if we are disconnected, ignore audio */ - if (is_process_pattern(data->callref)) + if (process->pattern != PATTERN_NONE) return; - int16_to_samples(samples, (int16_t *)data->data, count); + int16_to_samples(samples, (int16_t *)data->data, 160); #ifdef DEBUG_LEVEL - double lev = level_of(samples, count); + double lev = level_of(samples, 160); printf("festnetz-level: %s %.4f\n", debug_db(lev), (20 * log10(lev))); #endif - call_rx_audio(data->callref, samples, count); + call_down_audio(callref, samples, 160); return; } - callref = mncc->callref; - - if (is_process_disconnected(callref)) { + if (process->audio_disconnected) { switch(mncc->msg_type) { case MNCC_DISC_REQ: - PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC disconnect from Network with cause %d\n", mncc->cause.value); + PDEBUG(DCALL, DEBUG_INFO, "Received MNCC disconnect from fixed network with cause %d\n", mncc->cause.value); PDEBUG(DCALL, DEBUG_INFO, "Call disconnected, releasing!\n"); destroy_process(callref); - PDEBUG(DMNCC, DEBUG_INFO, "Releasing MNCC call towards Network\n"); + PDEBUG(DCALL, DEBUG_INFO, "Indicate MNCC release towards fixed network\n"); mncc->msg_type = MNCC_REL_IND; - mncc_write(buf, sizeof(struct gsm_mncc)); + rc = mncc_up(buf, sizeof(struct gsm_mncc)); + if (rc < 0) + destroy_process(callref); break; case MNCC_REL_REQ: - PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC release from Network with cause %d\n", mncc->cause.value); + PDEBUG(DCALL, DEBUG_INFO, "Received MNCC release from fixed network with cause %d\n", mncc->cause.value); PDEBUG(DCALL, DEBUG_INFO, "Call released\n"); @@ -1127,14 +686,14 @@ void call_mncc_recv(uint8_t *buf, int length) if (mncc->calling.present == 1) caller_type = TYPE_ANONYMOUS; - PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC call from Network to '%s'\n", caller_id); + PDEBUG(DCALL, DEBUG_INFO, "Received MNCC call from fixed network '%s' to mobile '%s'\n", caller_id, number); if (mncc->callref >= 0x4000000) { - fprintf(stderr, "Invalid callref from network, please fix!\n"); + fprintf(stderr, "Invalid callref from fixed network, please fix!\n"); abort(); } - PDEBUG(DMNCC, DEBUG_INFO, "Confirming MNCC call to Network\n"); + PDEBUG(DCALL, DEBUG_INFO, "Indicate MNCC call confirm towards fixed network\n"); memset(buf, 0, length); mncc->msg_type = MNCC_CALL_CONF_IND; mncc->callref = callref; @@ -1143,77 +702,82 @@ void call_mncc_recv(uint8_t *buf, int length) mncc->bearer_cap.speech_ver[0] = BCAP_ANALOG_8000HZ; mncc->bearer_cap.speech_ver[1] = -1; - mncc_write(buf, sizeof(struct gsm_mncc)); + mncc_up(buf, sizeof(struct gsm_mncc)); PDEBUG(DCALL, DEBUG_INFO, "Outgoing call from '%s' to '%s'\n", caller_id, number); - create_process(callref, CALL_SETUP_MT); - - rc = call_out_setup(callref, caller_id, caller_type, number); + rc = call_down_setup(callref, caller_id, caller_type, number); if (rc < 0) { PDEBUG(DCALL, DEBUG_NOTICE, "Call rejected, cause %d\n", -rc); - if (call.send_patterns) { + if (send_patterns) { PDEBUG(DCALL, DEBUG_DEBUG, "Early connecting after setup\n"); _indicate_answer(callref, number); - disconnect_process(callref, -rc); - break; + } else { + PDEBUG(DCALL, DEBUG_INFO, "Disconnecting MNCC call towards fixed network (cause=%d)\n", -rc); + _indicate_disconnect_release(callref, -rc, 1); } - PDEBUG(DMNCC, DEBUG_INFO, "Rejecting MNCC call towards Network (cause=%d)\n", -rc); - memset(buf, 0, length); - mncc->msg_type = MNCC_REL_IND; - mncc->callref = callref; - mncc->fields |= MNCC_F_CAUSE; - mncc->cause.location = 1; /* private local */ - mncc->cause.value = -rc; - mncc_write(buf, sizeof(struct gsm_mncc)); - destroy_process(callref); + disconnect_process(callref, -rc); break; } - if (call.send_patterns) { + if (send_patterns) { PDEBUG(DCALL, DEBUG_DEBUG, "Early connecting after setup\n"); _indicate_answer(callref, number); break; } break; + case MNCC_ALERT_REQ: + PDEBUG(DCALL, DEBUG_INFO, "Received MNCC alerting from fixed network\n"); + new_state_process(callref, PROCESS_ALERTING_RO); + break; case MNCC_SETUP_RSP: - PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC answer from Network\n"); - set_state_process(callref, CALL_CONNECT); + PDEBUG(DCALL, DEBUG_INFO, "Received MNCC answer from fixed network\n"); + new_state_process(callref, PROCESS_CONNECT); PDEBUG(DCALL, DEBUG_INFO, "Call answered\n"); - call_out_answer(callref); + call_down_answer(callref); + PDEBUG(DCALL, DEBUG_INFO, "Indicate MNCC setup complete towards fixed network\n"); + memset(buf, 0, length); + mncc->msg_type = MNCC_SETUP_COMPL_IND; + mncc->callref = callref; + rc = mncc_up(buf, sizeof(struct gsm_mncc)); + if (rc < 0) + destroy_process(callref); break; case MNCC_DISC_REQ: - PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC disconnect from Network with cause %d\n", mncc->cause.value); + PDEBUG(DCALL, DEBUG_INFO, "Received MNCC disconnect from fixed network with cause %d\n", mncc->cause.value); - if (is_process_state(callref) == CALL_CONNECT && call.release_on_disconnect) { + process = get_process(callref); + if (process && process->state == PROCESS_CONNECT && release_on_disconnect) { PDEBUG(DCALL, DEBUG_INFO, "Releasing, because we don't send disconnect tones to mobile phone\n"); - PDEBUG(DMNCC, DEBUG_INFO, "Releasing MNCC call towards Network\n"); + PDEBUG(DCALL, DEBUG_INFO, "Indicate MNCC release towards fixed network\n"); mncc->msg_type = MNCC_REL_IND; - mncc_write(buf, sizeof(struct gsm_mncc)); + mncc_up(buf, sizeof(struct gsm_mncc)); goto release; } - set_state_process(callref, CALL_DISCONNECTED); + new_state_process(callref, PROCESS_DISCONNECT); PDEBUG(DCALL, DEBUG_INFO, "Call disconnected\n"); - call_out_disconnect(callref, mncc->cause.value); + call_down_disconnect(callref, mncc->cause.value); break; case MNCC_REL_REQ: - PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC release from Network with cause %d\n", mncc->cause.value); + PDEBUG(DCALL, DEBUG_INFO, "Received MNCC release from fixed network with cause %d\n", mncc->cause.value); release: destroy_process(callref); PDEBUG(DCALL, DEBUG_INFO, "Call released\n"); - call_out_release(callref, mncc->cause.value); + call_down_release(callref, mncc->cause.value); break; } } +int (*mncc_up)(uint8_t *buf, int length) = NULL; + /* break down of MNCC socket */ -void call_mncc_flush(void) +void mncc_flush(void) { while(process_head) { - PDEBUG(DMNCC, DEBUG_NOTICE, "MNCC socket closed, releasing call\n"); - call_out_release(process_head->callref, CAUSE_TEMPFAIL); + PDEBUG(DCALL, DEBUG_NOTICE, "MNCC socket closed, releasing call\n"); + call_down_release(process_head->callref, CAUSE_TEMPFAIL); destroy_process(process_head->callref); /* note: callref is released by sender's instance */ } diff --git a/src/common/call.h b/src/common/call.h index e9462d6..7273a1e 100644 --- a/src/common/call.h +++ b/src/common/call.h @@ -9,34 +9,32 @@ enum number_type { TYPE_INTERNATIONAL, }; -int call_init(const char *station_id, const char *audiodev, int samplerate, int latency, int dial_digits, int loopback, int use_mncc_sock, int send_patterns, int release_on_disconnect, int echo_test); -void call_cleanup(void); -int call_open_audio(int latspl); -int call_start_audio(void); -void process_call(int c); -void clear_console_text(void); -void print_console_text(void); +int call_init(int _send_patterns, int _release_on_disconnect); + +/* function pointer to delive MNCC messages to upper layer */ +extern int (*mncc_up)(uint8_t *buf, int length); +/* MNCC messages from upper layer */ +void mncc_down(uint8_t *buf, int length); +/* flush all calls in case of MNCC socket failure */ +void mncc_flush(void); /* received messages */ -int call_in_setup(int callref, const char *callerid, const char *dialing); -void call_in_alerting(int callref); -void call_in_answer(int callref, const char *connect_id); -void call_in_release(int callref, int cause); +int call_up_setup(int callref, const char *callerid, const char *dialing); +void call_up_alerting(int callref); +void call_up_answer(int callref, const char *connect_id); +void call_up_release(int callref, int cause); void call_tone_recall(int callref, int on); /* send messages */ -int call_out_setup(int callref, const char *caller_id, enum number_type caller_type, const char *dialing); -void call_out_answer(int callref); -void call_out_disconnect(int callref, int cause); -void call_out_release(int callref, int cause); +int call_down_setup(int callref, const char *caller_id, enum number_type caller_type, const char *dialing); +void call_down_answer(int callref); +void call_down_disconnect(int callref, int cause); +void call_down_release(int callref, int cause); /* send and receive audio */ -void call_rx_audio(int callref, sample_t *samples, int count); -void call_tx_audio(int callref, sample_t *samples, int count); +void call_up_audio(int callref, sample_t *samples, int count); +void call_down_audio(int callref, sample_t *samples, int count); -/* receive from mncc */ -void call_mncc_recv(uint8_t *buf, int length); -void call_mncc_flush(void); /* clock to transmit to */ -void call_mncc_clock(void); +void call_clock(void); diff --git a/src/common/cause.h b/src/common/cause.h index a6333c6..d7781a1 100644 --- a/src/common/cause.h +++ b/src/common/cause.h @@ -7,6 +7,9 @@ #define CAUSE_TEMPFAIL 41 #define CAUSE_INVALCALLREF 81 +#define LOCATION_USER 0 +#define LOCATION_PRIVATE_LOCAL 1 + const char *cause_name(int cause); diff --git a/src/common/debug.c b/src/common/debug.c index d2eedb8..2bf48d1 100644 --- a/src/common/debug.c +++ b/src/common/debug.c @@ -27,7 +27,7 @@ #include "sample.h" #include "debug.h" #include "display.h" -#include "call.h" +#include "mncc_console.h" const char *debug_level[] = { "debug ", @@ -51,7 +51,7 @@ struct debug_cat { { "amps", "\033[1;34m" }, { "r2000", "\033[1;34m" }, { "frame", "\033[0;36m" }, - { "call", "\033[1;37m" }, + { "call", "\033[0;37m" }, { "mncc", "\033[1;32m" }, { "database", "\033[0;33m" }, { "transaction", "\033[0;32m" }, diff --git a/src/common/main_mobile.c b/src/common/main_mobile.c index f906d6d..bab9579 100644 --- a/src/common/main_mobile.c +++ b/src/common/main_mobile.c @@ -35,6 +35,7 @@ #include "sender.h" #include "timer.h" #include "call.h" +#include "mncc_console.h" #include "mncc_sock.h" #ifdef HAVE_SDR #include "sdr.h" @@ -425,6 +426,20 @@ void main_mobile(int *quit, int latency, int interval, void (*myhandler)(void), /* latency of send buffer in samples */ latspl = samplerate * latency / 1000; + /* check MNCC support */ + if (use_mncc_sock && call_audiodev[0]) { + fprintf(stderr, "You selected MNCC interface, but it cannot be used with call device (headset).\n"); + return; + } + if (use_mncc_sock && echo_test) { + fprintf(stderr, "You selected MNCC interface, but it cannot be used with echo test.\n"); + return; + } + if (echo_test && call_audiodev[0]) { + fprintf(stderr, "You selected call device (headset), but it cannot be used with echo test.\n"); + return; + } + /* init mncc */ if (use_mncc_sock) { char mncc_sock_name[64]; @@ -433,15 +448,17 @@ void main_mobile(int *quit, int latency, int interval, void (*myhandler)(void), mncc_sock_name[sizeof(mncc_sock_name) - 1] = '\0'; } else strcpy(mncc_sock_name, "/tmp/bsc_mncc"); - rc = mncc_init(mncc_sock_name); + rc = mncc_sock_init(mncc_sock_name); if (rc < 0) { fprintf(stderr, "Failed to setup MNCC socket. Quitting!\n"); return; } + } else { + console_init(station_id, call_audiodev, call_samplerate, latency, station_id_digits, loopback, echo_test); } - /* init call device */ - rc = call_init(station_id, call_audiodev, call_samplerate, latency, station_id_digits, loopback, use_mncc_sock, send_patterns, release_on_disconnect, echo_test); + /* init call control instance */ + rc = call_init((use_mncc_sock) ? send_patterns : 0, release_on_disconnect); if (rc < 0) { fprintf(stderr, "Failed to create call control instance. Quitting!\n"); return; @@ -456,7 +473,7 @@ void main_mobile(int *quit, int latency, int interval, void (*myhandler)(void), /* open audio */ if (sender_open_audio(latspl)) return; - if (call_open_audio(latspl)) + if (console_open_audio(latspl)) return; /* real time priority */ @@ -488,7 +505,7 @@ void main_mobile(int *quit, int latency, int interval, void (*myhandler)(void), /* start streaming */ if (sender_start_audio()) *quit = 1; - if (call_start_audio()) + if (console_start_audio()) *quit = 1; while(!(*quit)) { @@ -512,7 +529,7 @@ void main_mobile(int *quit, int latency, int interval, void (*myhandler)(void), if (now - last_time_call >= 0.020) { last_time_call += 0.020; /* call clock every 20ms */ - call_mncc_clock(); + call_clock(); } next_char: @@ -578,8 +595,11 @@ next_char: goto next_char; } - /* process audio of built-in call control */ - process_call(c); + /* process call control */ + if (use_mncc_sock) + mncc_sock_handle(); + else + process_console(c); if (myhandler) myhandler(); @@ -619,10 +639,11 @@ next_char: } /* cleanup call control */ - call_cleanup(); + if (!use_mncc_sock) + console_cleanup(); /* close mncc socket */ if (use_mncc_sock) - mncc_exit(); + mncc_sock_exit(); } diff --git a/src/common/mncc_console.c b/src/common/mncc_console.c new file mode 100644 index 0000000..1e65d04 --- /dev/null +++ b/src/common/mncc_console.c @@ -0,0 +1,473 @@ +/* built-in console to talk to a phone + * + * (C) 2017 by Andreas Eversberg <jolly@eversberg.eu> + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + G* along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdint.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/time.h> +#include "sample.h" +#include "samplerate.h" +#include "jitter.h" +#include "debug.h" +#include "testton.h" +#include "mncc.h" +#include "call.h" +#include "cause.h" +#include "mncc_console.h" +#include "sound.h" + +static int new_callref = 0; /* toward mobile */ + +enum console_state { + CONSOLE_IDLE = 0, /* IDLE */ + CONSOLE_SETUP_RO, /* call from radio to console */ + CONSOLE_SETUP_RT, /* call from console to radio */ + CONSOLE_ALERTING_RO, /* call from radio to console */ + CONSOLE_ALERTING_RT, /* call from console to radio */ + CONSOLE_CONNECT, + CONSOLE_DISCONNECT, +}; + +static const char *console_state_name[] = { + "IDLE", + "SETUP_RO", + "SETUP_RT", + "ALERTING_RO", + "ALERTING_RT", + "CONNECT", + "DISCONNECT", +}; + +/* console call instance */ +typedef struct console { + uint32_t callref; + enum console_state state; + int disc_cause; /* cause that has been sent by transceiver instance for release */ + char station_id[16]; + char dialing[16]; + char audiodev[64]; /* headphone interface, if used */ + int samplerate; /* sample rate of headphone interface */ + void *sound; /* headphone interface */ + int latspl; /* sample latency at headphone interface */ + samplerate_t srstate; /* patterns/announcement upsampling */ + jitter_t dejitter; /* headphone audio dejittering */ + int test_audio_pos; /* position for test tone toward mobile */ + sample_t tx_buffer[160];/* transmit audio buffer */ + int tx_buffer_pos; /* current position in transmit audio buffer */ + int dial_digits; /* number of digits to be dialed */ + int loopback; /* loopback test for echo */ + int echo_test; /* send echo back to mobile phone */ +} console_t; + +static console_t console; + +/* stream test music */ +int16_t *test_spl = NULL; +int test_size = 0; +int test_max = 0; + +static void get_test_patterns(int16_t *samples, int length) +{ + const int16_t *spl; + int size, max, pos; + + spl = test_spl; + size = test_size; + max = test_max; + + /* stream sample */ + pos = console.test_audio_pos; + while(length--) { + if (pos >= size) + *samples++ = 0; + else + *samples++ = spl[pos] >> 2; + if (++pos == max) + pos = 0; + } + console.test_audio_pos = pos; +} + +static void console_new_state(enum console_state state) +{ + PDEBUG(DMNCC, DEBUG_DEBUG, "Call state '%s' -> '%s'\n", console_state_name[console.state], console_state_name[state]); + console.state = state; + console.test_audio_pos = 0; +} + +static int console_mncc_up(uint8_t *buf, int length) +{ + struct gsm_mncc *mncc = (struct gsm_mncc *)buf; + + if (mncc->msg_type == ANALOG_8000HZ) { + struct gsm_data_frame *data = (struct gsm_data_frame *)buf; + int count = 160; + sample_t samples[count]; + + /* save audio from transceiver to jitter buffer */ + if (console.sound) { + sample_t up[(int)((double)count * console.srstate.factor + 0.5) + 10]; + int16_to_samples(samples, (int16_t *)data->data, count); + count = samplerate_upsample(&console.srstate, samples, count, up); + jitter_save(&console.dejitter, up, count); + return 0; + } + /* if echo test is used, send echo back to mobile */ + if (console.echo_test) { + /* send down reused MNCC */ + mncc_down(buf, length); + return 0; + } + /* if no sound is used, send test tone to mobile */ + if (console.state == CONSOLE_CONNECT) { + /* send down reused MNCC */ + get_test_patterns((int16_t *)data->data, count); + mncc_down(buf, length); + return 0; + } + return 0; + } + + if (mncc->msg_type != MNCC_SETUP_IND && console.callref != mncc->callref) { + PDEBUG(DMNCC, DEBUG_ERROR, "invalid call ref.\n"); + /* send down reused MNCC */ + mncc->msg_type = MNCC_REL_REQ; + mncc->fields |= MNCC_F_CAUSE; + mncc->cause.location = LOCATION_USER; + mncc->cause.value = CAUSE_INVALCALLREF; + mncc_down(buf, length); + return 0; + } + + switch(mncc->msg_type) { + case MNCC_SETUP_IND: + PDEBUG(DMNCC, DEBUG_INFO, "Incoming call from '%s'\n", mncc->calling.number); + /* setup is also allowed on disconnected call */ + if (console.state == CONSOLE_DISCONNECT) + console_new_state(CONSOLE_IDLE); + if (console.state != CONSOLE_IDLE) { + PDEBUG(DMNCC, DEBUG_NOTICE, "We are busy, rejecting.\n"); + return -CAUSE_BUSY; + } + console.callref = mncc->callref; + if (mncc->calling.number[0]) { + strncpy(console.station_id, mncc->calling.number, console.dial_digits); + console.station_id[console.dial_digits] = '\0'; + } + strncpy(console.dialing, mncc->called.number, sizeof(console.dialing) - 1); + console.dialing[sizeof(console.dialing) - 1] = '\0'; + console_new_state(CONSOLE_CONNECT); + PDEBUG(DMNCC, DEBUG_INFO, "Call automatically answered\n"); + /* send down reused MNCC */ + mncc->msg_type = MNCC_SETUP_RSP; + mncc_down(buf, length); + break; + case MNCC_ALERT_IND: + PDEBUG(DMNCC, DEBUG_INFO, "Call alerting\n"); + console_new_state(CONSOLE_ALERTING_RT); + break; + case MNCC_SETUP_CNF: + PDEBUG(DMNCC, DEBUG_INFO, "Call connected to '%s'\n", mncc->connected.number); + console_new_state(CONSOLE_CONNECT); + strncpy(console.station_id, mncc->connected.number, console.dial_digits); + console.station_id[console.dial_digits] = '\0'; + /* send down reused MNCC */ + mncc->msg_type = MNCC_SETUP_COMPL_REQ; + mncc_down(buf, length); + break; + case MNCC_DISC_IND: + PDEBUG(DMNCC, DEBUG_INFO, "Call disconnected (%s)\n", cause_name(mncc->cause.value)); + console_new_state(CONSOLE_DISCONNECT); + console.disc_cause = mncc->cause.value; + break; + case MNCC_REL_IND: + PDEBUG(DMNCC, DEBUG_INFO, "Call released (%s)\n", cause_name(mncc->cause.value)); + console_new_state(CONSOLE_IDLE); + console.callref = 0; + break; + } + return 0; +} + +int console_init(const char *station_id, const char *audiodev, int samplerate, int latency, int dial_digits, int loopback, int echo_test) +{ + int rc = 0; + + init_testton(); + + memset(&console, 0, sizeof(console)); + strncpy(console.station_id, station_id, sizeof(console.station_id) - 1); + strncpy(console.audiodev, audiodev, sizeof(console.audiodev) - 1); + console.samplerate = samplerate; + console.latspl = latency * samplerate / 1000; + console.dial_digits = dial_digits; + console.loopback = loopback; + console.echo_test = echo_test; + + mncc_up = console_mncc_up; + + if (!audiodev[0]) + return 0; + + rc = init_samplerate(&console.srstate, 8000.0, (double)samplerate, 3300.0); + if (rc < 0) { + PDEBUG(DSENDER, DEBUG_ERROR, "Failed to init sample rate conversion!\n"); + goto error; + } + + rc = jitter_create(&console.dejitter, samplerate / 5); + if (rc < 0) { + PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create and init dejitter buffer!\n"); + goto error; + } + + return 0; + +error: + console_cleanup(); + return rc; +} + +int console_open_audio(int latspl) +{ + if (!console.audiodev[0]) + return 0; + + /* open sound device for call control */ + /* use factor 1.4 of speech level for complete range of sound card */ + console.sound = sound_open(console.audiodev, NULL, NULL, 1, 0.0, console.samplerate, latspl, 1.4, 4000.0); + if (!console.sound) { + PDEBUG(DSENDER, DEBUG_ERROR, "No sound device!\n"); + return -EIO; + } + + return 0; +} + +int console_start_audio(void) +{ + if (!console.audiodev[0]) + return 0; + + return sound_start(console.sound); +} + +void console_cleanup(void) +{ + /* close sound devoice */ + if (console.sound) + sound_close(console.sound); + + jitter_destroy(&console.dejitter); +} + +static char console_text[256]; +static char console_clear[256]; +static int console_len = 0; + +static void process_ui(int c) +{ + char text[256]; + int len; + + switch (console.state) { + case CONSOLE_IDLE: + if (c > 0) { + if (c >= '0' && c <= '9' && (int)strlen(console.station_id) < console.dial_digits) { + console.station_id[strlen(console.station_id) + 1] = '\0'; + console.station_id[strlen(console.station_id)] = c; + } + if ((c == 8 || c == 127) && strlen(console.station_id)) + console.station_id[strlen(console.station_id) - 1] = '\0'; +dial_after_hangup: + if (c == 'd' && (int)strlen(console.station_id) == console.dial_digits) { + int callref = ++new_callref; + uint8_t buf[sizeof(struct gsm_mncc)]; + struct gsm_mncc *mncc = (struct gsm_mncc *)buf; + + PDEBUG(DMNCC, DEBUG_INFO, "Outgoing call to '%s'\n", console.station_id); + console.dialing[0] = '\0'; + console_new_state(CONSOLE_SETUP_RT); + console.callref = callref; + memset(buf, 0, sizeof(buf)); + mncc->msg_type = MNCC_SETUP_REQ; + mncc->callref = callref; + mncc->fields |= MNCC_F_CALLED; + strncpy(mncc->called.number, console.station_id, sizeof(mncc->called.number) - 1); + mncc->called.type = 0; /* dialing is of type 'unknown' */ + mncc->lchan_type = GSM_LCHAN_TCH_F; + mncc->fields |= MNCC_F_BEARER_CAP; + mncc->bearer_cap.speech_ver[0] = BCAP_ANALOG_8000HZ; + mncc->bearer_cap.speech_ver[1] = -1; + mncc_down(buf, sizeof(struct gsm_mncc)); + } + } + if (console.dial_digits != (int)strlen(console.station_id)) + sprintf(text, "on-hook: %s%s (enter digits 0..9)\r", console.station_id, "..............." + 15 - console.dial_digits + strlen(console.station_id)); + else + sprintf(text, "on-hook: %s (press d=dial)\r", console.station_id); + break; + case CONSOLE_SETUP_RO: + case CONSOLE_SETUP_RT: + case CONSOLE_ALERTING_RO: + case CONSOLE_ALERTING_RT: + case CONSOLE_CONNECT: + case CONSOLE_DISCONNECT: + if (c > 0) { + if (c == 'h' || (c == 'd' && console.state == CONSOLE_DISCONNECT)) { + PDEBUG(DMNCC, DEBUG_INFO, "Call hangup\n"); + console_new_state(CONSOLE_IDLE); + if (console.callref) { + uint8_t buf[sizeof(struct gsm_mncc)]; + struct gsm_mncc *mncc = (struct gsm_mncc *)buf; + + memset(buf, 0, sizeof(buf)); + mncc->msg_type = MNCC_REL_REQ; + mncc->callref = console.callref; + mncc->fields |= MNCC_F_CAUSE; + mncc->cause.location = LOCATION_USER; + mncc->cause.value = CAUSE_NORMAL; + mncc_down(buf, sizeof(struct gsm_mncc)); + console.callref = 0; + } + if (c == 'd') + goto dial_after_hangup; + } + } + if (console.state == CONSOLE_SETUP_RT) + sprintf(text, "call setup: %s (press h=hangup)\r", console.station_id); + if (console.state == CONSOLE_ALERTING_RT) + sprintf(text, "call ringing: %s (press h=hangup)\r", console.station_id); + if (console.state == CONSOLE_CONNECT) { + if (console.dialing[0]) + sprintf(text, "call active: %s->%s (press h=hangup)\r", console.station_id, console.dialing); + else + sprintf(text, "call active: %s (press h=hangup)\r", console.station_id); + } + if (console.state == CONSOLE_DISCONNECT) + sprintf(text, "call disconnected: %s (press h=hangup d=redial)\r", cause_name(console.disc_cause)); + break; + } + /* skip if nothing has changed */ + len = strlen(text); + if (console_len == len && !memcmp(console_text, text, len)) + return; + clear_console_text(); + console_len = len; + memcpy(console_text, text, len); + memset(console_clear, ' ', len - 1); + console_clear[len - 1] = '\r'; + print_console_text(); + fflush(stdout); +} + +void clear_console_text(void) +{ + if (!console_len) + return; + + fwrite(console_clear, console_len, 1, stdout); + // note: fflused by user of this function + console_len = 0; +} + +void print_console_text(void) +{ + if (!console_len) + return; + + printf("\033[1;37m"); + fwrite(console_text, console_len, 1, stdout); + printf("\033[0;39m"); +} + +/* get keys from keyboad to control call via console + * returns 1 on exit (ctrl+c) */ +void process_console(int c) +{ + if (!console.loopback) + process_ui(c); + + if (!console.sound) + return; + + /* handle audio, if sound device is used */ + sample_t samples[console.latspl + 10], *samples_list[1]; + uint8_t *power_list[1]; + double rf_level_db[1]; + int count; + int rc; + + count = sound_get_tosend(console.sound, console.latspl); + if (count < 0) { + PDEBUG(DSENDER, DEBUG_ERROR, "Failed to get samples in buffer (rc = %d)!\n", count); + if (count == -EPIPE) + PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover.\n"); + return; + } + if (count > 0) { + jitter_load(&console.dejitter, samples, count); + samples_list[0] = samples; + power_list[0] = NULL; + rc = sound_write(console.sound, samples_list, power_list, count, NULL, NULL, 1); + if (rc < 0) { + PDEBUG(DSENDER, DEBUG_ERROR, "Failed to write TX data to sound device (rc = %d)\n", rc); + if (rc == -EPIPE) + PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover.\n"); + return; + } + } + samples_list[0] = samples; + count = sound_read(console.sound, samples_list, console.latspl, 1, rf_level_db); + if (count < 0) { + PDEBUG(DSENDER, DEBUG_ERROR, "Failed to read from sound device (rc = %d)!\n", count); + if (count == -EPIPE) + PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover.\n"); + return; + } + if (count) { + int i; + + if (console.loopback == 3) + jitter_save(&console.dejitter, samples, count); + count = samplerate_downsample(&console.srstate, samples, count); + /* put samples into ring buffer */ + for (i = 0; i < count; i++) { + console.tx_buffer[console.tx_buffer_pos] = samples[i]; + /* if ring buffer wrapps, deliver data down to call process */ + if (++console.tx_buffer_pos == 160) { + console.tx_buffer_pos = 0; + /* only if we have a call */ + if (console.callref) { + uint8_t buf[sizeof(struct gsm_data_frame) + 160 * sizeof(int16_t)]; + struct gsm_data_frame *data = (struct gsm_data_frame *)buf; + + data->msg_type = ANALOG_8000HZ; + data->callref = console.callref; + samples_to_int16((int16_t *)data->data, console.tx_buffer, 160); + mncc_down(buf, sizeof(struct gsm_mncc)); + } + } + } + } +} + diff --git a/src/common/mncc_console.h b/src/common/mncc_console.h new file mode 100644 index 0000000..f61c8a4 --- /dev/null +++ b/src/common/mncc_console.h @@ -0,0 +1,10 @@ + +int console_init(const char *station_id, const char *audiodev, int samplerate, int latency, int dial_digits, int loopback, int echo_test); +void console_cleanup(void); +int console_open_audio(int latspl); +int console_start_audio(void); +void console_process(int c); +void clear_console_text(void); +void print_console_text(void); +void process_console(int c); + diff --git a/src/common/mncc_sock.c b/src/common/mncc_sock.c index 437dec1..4e97c52 100644 --- a/src/common/mncc_sock.c +++ b/src/common/mncc_sock.c @@ -29,33 +29,34 @@ #include "sample.h" #include "debug.h" #include "call.h" +#include "cause.h" #include "mncc_sock.h" static int listen_sock = -1; static int mncc_sock = -1; /* write to mncc socket, return error or -EIO if no socket connection */ -int mncc_write(uint8_t *buf, int length) +static int mncc_write(uint8_t *buf, int length) { int rc; if (mncc_sock <= 0) { - PDEBUG(DMNCC, DEBUG_NOTICE, "MNCC not connected.\n"); - return -EIO; + PDEBUG(DMNCC, DEBUG_NOTICE, "We have no MNCC connection, rejecting.\n"); + return -CAUSE_TEMPFAIL; } rc = send(mncc_sock, buf, length, 0); if (rc < 0) { PDEBUG(DMNCC, DEBUG_ERROR, "MNCC connection failed (errno = %d).\n", errno); mncc_sock_close(); - return 0; + return -CAUSE_TEMPFAIL; } if (rc != length) { PDEBUG(DMNCC, DEBUG_NOTICE, "MNCC write failed.\n"); mncc_sock_close(); - return 0; + return -CAUSE_TEMPFAIL; } - return rc; + return 0; } @@ -80,7 +81,7 @@ static int mncc_read(void) return -errno; } - call_mncc_recv(buf, rc); + mncc_down(buf, rc); return rc; } @@ -141,7 +142,7 @@ static int mncc_accept(void) return 1; } -void mncc_handle(void) +void mncc_sock_handle(void) { mncc_accept(); @@ -159,11 +160,11 @@ void mncc_sock_close(void) close(mncc_sock); mncc_sock = -1; /* clear all call instances */ - call_mncc_flush(); + mncc_flush(); } } -int mncc_init(const char *sock_name) +int mncc_sock_init(const char *sock_name) { struct sockaddr_un local; unsigned int namelen; @@ -197,14 +198,14 @@ int mncc_init(const char *sock_name) if (rc < 0) { PDEBUG(DMNCC, DEBUG_ERROR, "Failed to bind the unix domain " "socket. '%s'\n", local.sun_path); - mncc_exit(); + mncc_sock_exit(); return rc; } rc = listen(listen_sock, 0); if (rc < 0) { PDEBUG(DMNCC, DEBUG_ERROR, "Failed to listen.\n"); - mncc_exit(); + mncc_sock_exit(); return rc; } @@ -213,16 +214,18 @@ int mncc_init(const char *sock_name) rc = fcntl(listen_sock, F_SETFL, flags | O_NONBLOCK); if (rc < 0) { PDEBUG(DMNCC, DEBUG_ERROR, "Failed to set socket into non-blocking IO mode.\n"); - mncc_exit(); + mncc_sock_exit(); return rc; } + mncc_up = mncc_write; + PDEBUG(DMNCC, DEBUG_DEBUG, "MNCC socket at '%s' initialized, waiting for connection.\n", sock_name); return 0; } -void mncc_exit(void) +void mncc_sock_exit(void) { mncc_sock_close(); diff --git a/src/common/mncc_sock.h b/src/common/mncc_sock.h index 3f2ac4b..7406dbe 100644 --- a/src/common/mncc_sock.h +++ b/src/common/mncc_sock.h @@ -1,8 +1,7 @@ #include "mncc.h" -int mncc_write(uint8_t *buf, int length); -void mncc_handle(void); +void mncc_sock_handle(void); void mncc_sock_close(void); -int mncc_init(const char *sock_name); -void mncc_exit(void); +int mncc_sock_init(const char *sock_name); +void mncc_sock_exit(void); |