parent
7cbebaeb75
commit
ee3fbeb03b
File diff suppressed because it is too large
Load Diff
@ -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; |
||||
} |
||||