Restructure: Move mobile from common code to 'libmobile'

This commit is contained in:
Andreas Eversberg
2017-11-18 08:33:07 +01:00
parent ed31a26eba
commit ab59a26a51
52 changed files with 81 additions and 65 deletions

14
src/libmobile/Makefile.am Normal file
View File

@@ -0,0 +1,14 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
noinst_LIBRARIES = libmobile.a
libmobile_a_SOURCES = \
sender.c \
call.c \
display_status.c \
main_mobile.c
if HAVE_SDR
AM_CPPFLAGS += -DHAVE_SDR
endif

796
src/libmobile/call.c Normal file
View File

@@ -0,0 +1,796 @@
/* interface between mobile network/phone implementation and MNCC
*
* (C) 2016 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
* 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 "../libsample/sample.h"
#include "../common/debug.h"
#include "sender.h"
#include "call.h"
#include "../libtimer/timer.h"
#include "../libmncc/mncc.h"
#include "../libmncc/cause.h"
#define DISC_TIMEOUT 30
//#define DEBUG_LEVEL
#ifdef DEBUG_LEVEL
static double level_of(double *samples, int count)
{
double level = 0;
int i;
for (i = 0; i < count; i++) {
if (samples[i] > level)
level = samples[i];
}
return level;
}
#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 *ringback_spl = NULL;
int ringback_size = 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 audio_pattern {
PATTERN_NONE = 0,
PATTERN_TEST,
PATTERN_RINGBACK,
PATTERN_HANGUP,
PATTERN_BUSY,
PATTERN_NOANSWER,
PATTERN_OUTOFORDER,
PATTERN_INVALIDNUMBER,
PATTERN_CONGESTION,
PATTERN_RECALL,
};
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_RINGBACK:
no_recall:
*spl = ringback_spl;
*size = ringback_size;
*max = ringback_max;
break;
case PATTERN_HANGUP:
if (!hangup_spl)
goto no_hangup;
*spl = hangup_spl;
*size = hangup_size;
*max = hangup_max;
break;
case PATTERN_BUSY:
no_hangup:
no_noanswer:
*spl = busy_spl;
*size = busy_size;
*max = busy_max;
break;
case PATTERN_NOANSWER:
if (!noanswer_spl)
goto no_noanswer;
*spl = noanswer_spl;
*size = noanswer_size;
*max = noanswer_max;
break;
case PATTERN_OUTOFORDER:
if (!outoforder_spl)
goto no_outoforder;
*spl = outoforder_spl;
*size = outoforder_size;
*max = outoforder_max;
break;
case PATTERN_INVALIDNUMBER:
if (!invalidnumber_spl)
goto no_invalidnumber;
*spl = invalidnumber_spl;
*size = invalidnumber_size;
*max = invalidnumber_max;
break;
case PATTERN_CONGESTION:
no_outoforder:
no_invalidnumber:
*spl = congestion_spl;
*size = congestion_size;
*max = congestion_max;
break;
case PATTERN_RECALL:
if (!recall_spl)
goto no_recall;
*spl = recall_spl;
*size = recall_size;
*max = recall_max;
break;
default:
;
}
}
static enum audio_pattern cause2pattern(int cause)
{
int pattern;
switch (cause) {
case CAUSE_NORMAL:
pattern = PATTERN_HANGUP;
break;
case CAUSE_BUSY:
pattern = PATTERN_BUSY;
break;
case CAUSE_NOANSWER:
pattern = PATTERN_NOANSWER;
break;
case CAUSE_OUTOFORDER:
pattern = PATTERN_OUTOFORDER;
break;
case CAUSE_INVALNUMBER:
pattern = PATTERN_INVALIDNUMBER;
break;
case CAUSE_NOCHANNEL:
pattern = PATTERN_CONGESTION;
break;
default:
pattern = PATTERN_HANGUP;
}
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 process_state state;
int audio_disconnected; /* if not associated with transceiver anymore */
enum audio_pattern pattern;
int audio_pos;
uint8_t cause;
struct timer timer;
} process_t;
static process_t *process_head = NULL;
static void process_timeout(struct timer *timer);
static process_t *create_process(int callref, enum process_state state)
{
process_t *process;
process = calloc(sizeof(*process), 1);
if (!process) {
PDEBUG(DCALL, DEBUG_ERROR, "No memory!\n");
abort();
}
timer_init(&process->timer, process_timeout, process);
process->next = process_head;
process_head = process;
process->callref = callref;
process->state = state;
return process;
}
static void destroy_process(int callref)
{
process_t *process = process_head;
process_t **process_p = &process_head;
while (process) {
if (process->callref == callref) {
*process_p = process->next;
timer_exit(&process->timer);
free(process);
return;
}
process_p = &process->next;
process = process->next;
}
PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref);
}
static process_t *get_process(int callref)
{
process_t *process = process_head;
while (process) {
if (process->callref == callref)
return process;
process = process->next;
}
return NULL;
}
static void new_state_process(int callref, enum process_state state)
{
process_t *process = get_process(callref);
if (!process) {
PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref);
return;
}
process->state = state;
}
static void set_pattern_process(int callref, enum audio_pattern pattern)
{
process_t *process = get_process(callref);
if (!process) {
PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref);
return;
}
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 = get_process(callref);
if (!process) {
PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref);
return;
}
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)
{
const int16_t *spl;
int size, max, pos;
get_pattern(&spl, &size, &max, process->pattern);
/* stream sample */
pos = process->audio_pos;
while(length--) {
if (pos >= size)
*samples++ = 0;
else
*samples++ = spl[pos] >> 1;
if (++pos == max)
pos = 0;
}
process->audio_pos = pos;
}
static void process_timeout(struct timer *timer)
{
process_t *process = (process_t *)timer->priv;
{
/* announcement timeout */
uint8_t buf[sizeof(struct gsm_mncc)];
struct gsm_mncc *mncc = (struct gsm_mncc *)buf;
if (process->state == PROCESS_DISCONNECT) {
PDEBUG(DCALL, DEBUG_INFO, "Call released toward mobile network (after timeout)\n");
call_down_release(process->callref, process->cause);
}
memset(buf, 0, sizeof(buf));
mncc->msg_type = MNCC_REL_IND;
mncc->callref = process->callref;
mncc->fields |= MNCC_F_CAUSE;
mncc->cause.location = LOCATION_PRIVATE_LOCAL;
mncc->cause.value = process->cause;
destroy_process(process->callref);
PDEBUG(DCALL, DEBUG_INFO, "Releasing MNCC call towards fixed network (after timeout)\n");
mncc_up(buf, sizeof(struct gsm_mncc));
}
}
int call_init(int _send_patterns, int _release_on_disconnect)
{
send_patterns = _send_patterns;
release_on_disconnect = _release_on_disconnect;
return 0;
}
/* Setup is received from transceiver. */
static int _indicate_setup(int callref, const char *callerid, const char *dialing)
{
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;
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;
}
int call_up_setup(int callref, const char *callerid, const char *dialing)
{
int rc;
if (!callref) {
PDEBUG(DCALL, DEBUG_DEBUG, "Ignoring setup, because callref not set. (not for us)\n");
return -CAUSE_INVALCALLREF;
}
if (callref < 0x4000000) {
PDEBUG(DCALL, DEBUG_ERROR, "Invalid callref from mobile station, please fix!\n");
abort();
}
PDEBUG(DCALL, DEBUG_INFO, "Incoming call from '%s' to '%s'\n", callerid ? : "unknown", dialing);
if (!strcmp(dialing, "010"))
PDEBUG(DCALL, DEBUG_INFO, " -> Call to Operator '%s'\n", dialing);
create_process(callref, PROCESS_SETUP_RO);
rc = _indicate_setup(callref, callerid, dialing);
return rc;
}
/* 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;
memset(buf, 0, sizeof(buf));
mncc->msg_type = MNCC_ALERT_IND;
mncc->callref = callref;
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);
}
void call_up_alerting(int callref)
{
if (!callref) {
PDEBUG(DCALL, DEBUG_DEBUG, "Ignoring alerting, because callref not set. (not for us)\n");
return;
}
PDEBUG(DCALL, DEBUG_INFO, "Call is alerting\n");
if (!send_patterns)
_indicate_alerting(callref);
set_pattern_process(callref, PATTERN_RINGBACK);
new_state_process(callref, PROCESS_ALERTING_RT);
}
/* Transceiver indicates answer. */
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;
mncc->callref = callref;
mncc->fields |= MNCC_F_CONNECTED;
/* copy connected number as subscriber number */
strncpy(mncc->connected.number, connect_id, sizeof(mncc->connected.number));
mncc->connected.type = 4;
mncc->connected.plan = 1;
mncc->connected.present = 0;
mncc->connected.screen = 3;
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_up_answer(int callref, const char *connect_id)
{
if (!callref) {
PDEBUG(DCALL, DEBUG_DEBUG, "Ignoring answer, because callref not set. (not for us)\n");
return;
}
PDEBUG(DCALL, DEBUG_INFO, "Call has been answered by '%s'\n", connect_id);
if (!send_patterns)
_indicate_answer(callref, connect_id);
set_pattern_process(callref, PATTERN_NONE);
new_state_process(callref, PROCESS_CONNECT);
}
/* Transceiver indicates release. */
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;
}
PDEBUG(DCALL, DEBUG_INFO, "Call has been released with cause=%d\n", cause);
process = get_process(callref);
if (process) {
/* just keep MNCC connection if 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 no tones shall be sent, release on disconnect
* or RO setup states */
if (process->state == PROCESS_DISCONNECT
|| process->state == PROCESS_SETUP_RO
|| process->state == PROCESS_ALERTING_RO) {
destroy_process(callref);
_indicate_disconnect_release(callref, cause, 0);
/* if no tones shall be sent, disconnect on all other states */
} else {
disconnect_process(callref, cause);
_indicate_disconnect_release(callref, cause, 1);
}
} else {
/* we don't know about the process, just send release to upper layer anyway */
_indicate_disconnect_release(callref, cause, 0);
}
}
/* turn recall tone on or off */
void call_tone_recall(int callref, int on)
{
set_pattern_process(callref, (on) ? PATTERN_RECALL : PATTERN_NONE);
}
/* forward audio to MNCC or call instance */
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;
/* 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;
#ifdef DEBUG_LEVEL
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_up(buf, sizeof(buf));
/* don't destroy process here in case of an error */
}
/* clock that is used to transmit patterns */
void call_clock(void)
{
process_t *process = process_head;
uint8_t buf[sizeof(struct gsm_data_frame) + 160 * sizeof(int16_t)];
struct gsm_data_frame *data = (struct gsm_data_frame *)buf;
while(process) {
if (process->pattern != PATTERN_NONE) {
data->msg_type = ANALOG_8000HZ;
data->callref = process->callref;
/* try to get patterns, else copy the samples we got */
get_process_patterns(process, (int16_t *)data->data, 160);
#ifdef DEBUG_LEVEL
sample_t samples[160];
int16_to_samples(samples, (int16_t *)data->data, 160);
double lev = level_of(samples, 160);
printf(" mobil-level: %s%.4f\n", debug_db(lev), (20 * log10(lev)));
samples_to_int16((int16_t *)data->data, samples, 160);
#endif
mncc_up(buf, sizeof(buf));
/* don't destroy process here in case of an error */
}
process = process->next;
}
}
/* 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)];
char caller_id[sizeof(mncc->calling.number)];
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;
sample_t samples[160];
/* if we are disconnected, ignore audio */
if (process->pattern != PATTERN_NONE)
return;
int16_to_samples(samples, (int16_t *)data->data, 160);
#ifdef DEBUG_LEVEL
double lev = level_of(samples, 160);
printf("festnetz-level: %s %.4f\n", debug_db(lev), (20 * log10(lev)));
#endif
call_down_audio(callref, samples, 160);
return;
}
if (process->audio_disconnected) {
switch(mncc->msg_type) {
case MNCC_DISC_REQ:
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(DCALL, DEBUG_INFO, "Indicate MNCC release towards fixed network\n");
mncc->msg_type = MNCC_REL_IND;
rc = mncc_up(buf, sizeof(struct gsm_mncc));
if (rc < 0)
destroy_process(callref);
break;
case MNCC_REL_REQ:
PDEBUG(DCALL, DEBUG_INFO, "Received MNCC release from fixed network with cause %d\n", mncc->cause.value);
PDEBUG(DCALL, DEBUG_INFO, "Call released\n");
destroy_process(callref);
break;
}
return;
}
switch(mncc->msg_type) {
case MNCC_SETUP_REQ:
strcpy(number, mncc->called.number);
/* caller ID conversion */
strcpy(caller_id, mncc->calling.number);
switch(mncc->calling.type) {
case 1:
caller_type = TYPE_INTERNATIONAL;
break;
case 2:
caller_type = TYPE_NATIONAL;
break;
case 4:
caller_type = TYPE_SUBSCRIBER;
break;
default: /* or 0 */
caller_type = TYPE_UNKNOWN;
break;
}
if (!caller_id[0])
caller_type = TYPE_NOTAVAIL;
if (mncc->calling.present == 1)
caller_type = TYPE_ANONYMOUS;
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 fixed network, please fix!\n");
abort();
}
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;
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_up(buf, sizeof(struct gsm_mncc));
PDEBUG(DCALL, DEBUG_INFO, "Outgoing call from '%s' to '%s'\n", caller_id, 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 (send_patterns) {
PDEBUG(DCALL, DEBUG_DEBUG, "Early connecting after setup\n");
_indicate_answer(callref, number);
} else {
PDEBUG(DCALL, DEBUG_INFO, "Disconnecting MNCC call towards fixed network (cause=%d)\n", -rc);
_indicate_disconnect_release(callref, -rc, 1);
}
disconnect_process(callref, -rc);
break;
}
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(DCALL, DEBUG_INFO, "Received MNCC answer from fixed network\n");
new_state_process(callref, PROCESS_CONNECT);
PDEBUG(DCALL, DEBUG_INFO, "Call answered\n");
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(DCALL, DEBUG_INFO, "Received MNCC disconnect from fixed network with cause %d\n", mncc->cause.value);
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(DCALL, DEBUG_INFO, "Indicate MNCC release towards fixed network\n");
mncc->msg_type = MNCC_REL_IND;
mncc_up(buf, sizeof(struct gsm_mncc));
goto release;
}
new_state_process(callref, PROCESS_DISCONNECT);
PDEBUG(DCALL, DEBUG_INFO, "Call disconnected\n");
call_down_disconnect(callref, mncc->cause.value);
timer_start(&process->timer, DISC_TIMEOUT);
break;
case MNCC_REL_REQ:
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 toward mobile network\n");
call_down_release(callref, mncc->cause.value);
break;
}
}
int (*mncc_up)(uint8_t *buf, int length) = NULL;
/* break down of MNCC socket */
void mncc_flush(void)
{
while(process_head) {
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 */
}
}

40
src/libmobile/call.h Normal file
View File

@@ -0,0 +1,40 @@
/* number type, includes presentation info */
enum number_type {
TYPE_NOTAVAIL,
TYPE_ANONYMOUS,
TYPE_UNKNOWN,
TYPE_SUBSCRIBER,
TYPE_NATIONAL,
TYPE_INTERNATIONAL,
};
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_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_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_up_audio(int callref, sample_t *samples, int count);
void call_down_audio(int callref, sample_t *samples, int count);
/* clock to transmit to */
void call_clock(void);

View File

@@ -0,0 +1,147 @@
/* display status functions
*
* (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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include "../libsample/sample.h"
#include "sender.h"
static int status_on = 0;
static int line_count = 0;
static int lines_total = 0;
static char screen[MAX_HEIGHT_STATUS][MAX_DISPLAY_WIDTH];
static void print_status(int on)
{
int i, j;
int w, h;
get_win_size(&w, &h);
if (w > MAX_DISPLAY_WIDTH)
w = MAX_DISPLAY_WIDTH;
h--;
if (h > lines_total)
h = lines_total;
printf("\0337\033[H\033[1;37m");
for (i = 0; i < h; i++) {
j = 0;
if (on) {
for (j = 0; j < w; j++)
putchar(screen[i][j]);
} else {
for (j = 0; j < w; j++)
putchar(' ');
}
putchar('\n');
}
printf("\0338"); fflush(stdout);
}
void display_status_on(int on)
{
if (status_on)
print_status(0);
if (on < 0)
status_on = 1 - status_on;
else
status_on = on;
if (status_on)
print_status(1);
}
void display_status_limit_scroll(int on)
{
int w, h;
if (!status_on)
return;
get_win_size(&w, &h);
printf("\0337");
printf("\033[%d;%dr", (on) ? lines_total + 1 : 1, h);
printf("\0338");
}
/* start status display */
void display_status_start(void)
{
memset(screen, ' ', sizeof(screen));
memset(screen[0], '-', sizeof(screen[0]));
strncpy(screen[0] + 4, "Channel Status", 14);
line_count = 1;
}
void display_status_channel(int channel, const char *type, const char *state)
{
char line[MAX_DISPLAY_WIDTH];
/* add empty line after previous channel+subscriber */
if (line_count > 1 && line_count < MAX_HEIGHT_STATUS)
line_count++;
if (line_count == MAX_HEIGHT_STATUS)
return;
if (type)
snprintf(line, sizeof(line), "Channel: %d Type: %s State: %s", channel, type, state);
else
snprintf(line, sizeof(line), "Channel: %d State: %s", channel, state);
line[sizeof(line) - 1] = '\0';
strncpy(screen[line_count++], line, strlen(line));
}
void display_status_subscriber(const char *number, const char *state)
{
char line[MAX_DISPLAY_WIDTH];
if (line_count == MAX_HEIGHT_STATUS)
return;
if (state)
snprintf(line, sizeof(line), " Subscriber: %s State: %s", number, state);
else
snprintf(line, sizeof(line), " Subscriber: %s", number);
line[sizeof(line) - 1] = '\0';
strncpy(screen[line_count++], line, strlen(line));
}
void display_status_end(void)
{
if (line_count < MAX_HEIGHT_STATUS) {
memset(screen[line_count], '-', sizeof(screen[line_count]));
line_count++;
}
/* if last total lines exceed current line count, keep it, so removed lines are overwritten with spaces */
if (line_count > lines_total)
lines_total = line_count;
if (status_on)
print_status(1);
/* set new total lines */
lines_total = line_count;
}

688
src/libmobile/main_mobile.c Normal file
View File

@@ -0,0 +1,688 @@
/* Common part for main.c of each base station type
*
* (C) 2016 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdint.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sched.h>
#include <unistd.h>
#include <math.h>
#include <termios.h>
#include <errno.h>
#include <getopt.h>
#include "../libsample/sample.h"
#include "main_mobile.h"
#include "../common/debug.h"
#include "sender.h"
#include "../libtimer/timer.h"
#include "call.h"
#include "../libmncc/mncc_console.h"
#include "../libmncc/mncc_sock.h"
#include "../libmncc/mncc_cross.h"
#ifdef HAVE_SDR
#include "../libsdr/sdr.h"
#include "../libsdr/sdr_config.h"
#endif
static int got_init = 0;
/* common mobile settings */
int num_kanal = 0;
int kanal[MAX_SENDER];
int num_audiodev = 0;
const char *audiodev[MAX_SENDER] = { "hw:0,0" };
int use_sdr = 0;
static const char *call_audiodev = "";
int samplerate = 48000;
static int call_samplerate = 48000;
int interval = 1;
int latency = 50;
int uses_emphasis = 1;
int do_pre_emphasis = 0;
int do_de_emphasis = 0;
double rx_gain = 1.0;
static int echo_test = 0;
static int use_mncc_sock = 0;
static int use_mncc_cross = 0;
const char *mncc_name = "";
static int send_patterns = 1;
static int release_on_disconnect = 1;
int loopback = 0;
int rt_prio = 0;
const char *write_tx_wave = NULL;
const char *write_rx_wave = NULL;
const char *read_tx_wave = NULL;
const char *read_rx_wave = NULL;
void main_mobile_init(void)
{
got_init = 1;
#ifdef HAVE_SDR
sdr_config_init();
#endif
}
void main_mobile_print_help(const char *arg0, const char *ext_usage)
{
printf("Usage: %s -k <kanal/channel> %s[options] [station-id]\n", arg0, ext_usage);
printf("\nGlobal options:\n");
/* - - */
printf(" -h --help\n");
printf(" This help\n");
printf(" -v --verbose <level> | <level>,<category>[,<category>[,...]] | list\n");
printf(" Use 'list' to get a list of all levels and categories\n");
printf(" Verbose level: digit of debug level (default = '%d')\n", debuglevel);
printf(" Verbose level+category: level digit followed by one or more categories\n");
printf(" -> If no category is specified, all categories are selected\n");
printf(" -k --kanal <channel>\n");
printf(" -k --channel <channel>\n");
printf(" Channel (German = Kanal) number of \"Sender\" (German = Transceiver)\n");
printf(" -a --audio-device hw:<card>,<device>\n");
printf(" Sound card and device number (default = '%s')\n", audiodev[0]);
printf(" Don't set it for SDR!\n");
printf(" -s --samplerate <rate>\n");
printf(" Sample rate of sound device (default = '%d')\n", samplerate);
printf(" -i --interval 1..25\n");
printf(" Interval of processing loop in ms (default = '%d' ms)\n", interval);
printf(" Use 25 to drastically reduce CPU usage. In case of buffer underrun,\n");
printf(" increase latency accordingly.\n");
printf(" -b --buffer <ms>\n");
printf(" How many milliseconds are processed in advance (default = '%d')\n", latency);
if (uses_emphasis) {
printf(" -p --pre-emphasis\n");
printf(" Enable pre-emphasis, if you directly connect to the oscillator of the\n");
printf(" transmitter. (No pre-emphasis done by the transmitter.)\n");
printf(" -d --de-emphasis\n");
printf(" Enable de-emphasis, if you directly connect to the discriminator of\n");
printf(" the receiver. (No de-emphasis done by the receiver.)\n");
}
printf(" -g --rx-gain <dB>\n");
printf(" Raise receiver RX level by given gain in dB. This is useful if input\n");
printf(" level of the sound device is too low, even after setting maximum level\n");
printf(" with the mixer settings. (Works with sound card only.)\n");
printf(" -e --echo-test\n");
printf(" Use echo test, to send back audio from mobile phone's microphone to\n");
printf(" the speaker. (German: 'Blasprobe').\n");
printf(" -c --call-device hw:<card>,<device>\n");
printf(" Sound card and device number for headset (default = '%s')\n", call_audiodev);
printf(" --call-samplerate <rate>\n");
printf(" Sample rate of sound device for headset (default = '%d')\n", call_samplerate);
printf(" -x --mncc-cross\n");
printf(" Enable built-in call forwarding between mobiles. Be sure to have\n");
printf(" at least one control channel and two voice channels. Alternatively\n");
printf(" use one combined control+voice channel and one voice channels.\n");
printf(" -m --mncc-sock\n");
printf(" Disable built-in call contol and offer socket (to LCR)\n");
printf(" --mncc-name <name>\n");
printf(" '/tmp/bsc_mncc' is used by default, give name to change socket to\n");
printf(" '/tmp/bsc_mncc_<name>'. (Useful to run multiple networks.)\n");
printf(" -t --tones 0 | 1\n");
printf(" Connect call on setup/release to provide classic tones towards fixed\n");
printf(" network (default = '%d')\n", send_patterns);
printf(" -l --loopback <type>\n");
printf(" Loopback test: 1 = internal | 2 = external | 3 = echo\n");
printf(" -r --realtime <prio>\n");
printf(" Set prio: 0 to diable, 99 for maximum (default = %d)\n", rt_prio);
printf(" --write-rx-wave <file>\n");
printf(" Write received audio to given wave file.\n");
printf(" --write-tx-wave <file>\n");
printf(" Write transmitted audio to given wave file.\n");
printf(" --read-rx-wave <file>\n");
printf(" Replace received audio by given wave file.\n");
printf(" --read-tx-wave <file>\n");
printf(" Replace transmitted audio by given wave file.\n");
#ifdef HAVE_SDR
sdr_config_print_help();
#endif
printf("\nNetwork specific options:\n");
}
void main_mobile_print_hotkeys(void)
{
printf("\n");
printf("Press digits '0'..'9' and then 'd' key to dial towards mobile station.\n");
printf("Press 'h' key to hangup.\n");
printf("Press 'w' key to toggle display of RX wave form.\n");
printf("Press 'c' key to toggle display of channel status.\n");
printf("Press 'm' key to toggle display of measurement value.\n");
#ifdef HAVE_SDR
sdr_config_print_hotkeys();
#endif
}
#define OPT_CHANNEL 1000
#define OPT_WRITE_RX_WAVE 1001
#define OPT_WRITE_TX_WAVE 1002
#define OPT_READ_RX_WAVE 1003
#define OPT_READ_TX_WAVE 1004
#define OPT_CALL_SAMPLERATE 1005
#define OPT_MNCC_NAME 1006
static struct option main_mobile_long_options[] = {
{"help", 0, 0, 'h'},
{"debug", 1, 0, 'v'},
{"kanal", 1, 0, 'k'},
{"channel", 1, 0, OPT_CHANNEL},
{"audio-device", 1, 0, 'a'},
{"samplerate", 1, 0, 's'},
{"interval", 1, 0, 'i'},
{"buffer", 1, 0, 'b'},
{"pre-emphasis", 0, 0, 'p'},
{"de-emphasis", 0, 0, 'd'},
{"rx-gain", 1, 0, 'g'},
{"echo-test", 0, 0, 'e'},
{"mncc-cross", 0, 0, 'x'},
{"mncc-sock", 0, 0, 'm'},
{"mncc-name", 1, 0, OPT_MNCC_NAME},
{"call-device", 1, 0, 'c'},
{"call-samplerate", 1, 0, OPT_CALL_SAMPLERATE},
{"tones", 0, 0, 't'},
{"loopback", 1, 0, 'l'},
{"realtime", 1, 0, 'r'},
{"write-rx-wave", 1, 0, OPT_WRITE_RX_WAVE},
{"write-tx-wave", 1, 0, OPT_WRITE_TX_WAVE},
{"read-rx-wave", 1, 0, OPT_READ_RX_WAVE},
{"read-tx-wave", 1, 0, OPT_READ_TX_WAVE},
{0, 0, 0, 0}
};
static const char *main_mobile_optstring = "hv:k:a:s:i:b:pdg:exmc:t:l:r:";
struct option *long_options;
char *optstring;
static void check_duplicate_option(int num, struct option *option)
{
int i;
for (i = 0; i < num; i++) {
if (long_options[i].val == option->val) {
fprintf(stderr, "Duplicate option %d. Please fix!\n", option->val);
abort();
}
}
}
void main_mobile_set_options(const char *optstring_special, struct option *long_options_special)
{
int i = 0, j;
long_options = calloc(sizeof(*long_options), 256);
for (j = 0; main_mobile_long_options[j].name; i++, j++) {
check_duplicate_option(i, &main_mobile_long_options[j]);
memcpy(&long_options[i], &main_mobile_long_options[j], sizeof(*long_options));
}
#ifdef HAVE_SDR
for (j = 0; sdr_config_long_options[j].name; i++, j++) {
check_duplicate_option(i, &sdr_config_long_options[j]);
memcpy(&long_options[i], &sdr_config_long_options[j], sizeof(*long_options));
}
#endif
for (; long_options_special->name; i++) {
check_duplicate_option(i, long_options_special);
memcpy(&long_options[i], long_options_special++, sizeof(*long_options));
}
optstring = calloc(256, 2);
strcpy(optstring, main_mobile_optstring);
#ifdef HAVE_SDR
strcat(optstring, sdr_config_optstring);
#endif
strcat(optstring, optstring_special);
}
void print_help(const char *arg0);
void main_mobile_opt_switch(int c, char *arg0, int *skip_args)
{
double gain_db;
#ifdef HAVE_SDR
int rc;
#endif
switch (c) {
case 'h':
print_help(arg0);
exit(0);
case 'v':
if (!strcasecmp(optarg, "list")) {
debug_list_cat();
exit(0);
}
if (parse_debug_opt(optarg)) {
fprintf(stderr, "Failed to parse debug option, please use -h for help.\n");
exit(0);
}
*skip_args += 2;
break;
case 'k':
case OPT_CHANNEL:
OPT_ARRAY(num_kanal, kanal, atoi(optarg))
*skip_args += 2;
break;
case 'a':
OPT_ARRAY(num_audiodev, audiodev, strdup(optarg))
*skip_args += 2;
break;
case 's':
samplerate = atoi(optarg);
*skip_args += 2;
break;
case 'i':
interval = atoi(optarg);
*skip_args += 2;
if (interval < 1)
interval = 1;
if (interval > 25)
interval = 25;
break;
case 'b':
latency = atoi(optarg);
*skip_args += 2;
break;
case 'p':
if (!uses_emphasis) {
no_emph:
fprintf(stderr, "This network does not use emphasis, please do not enable pre- or de-emphasis! Disable emphasis on transceiver, if possible.\n");
exit(0);
}
do_pre_emphasis = 1;
*skip_args += 1;
break;
case 'd':
if (!uses_emphasis)
goto no_emph;
do_de_emphasis = 1;
*skip_args += 1;
break;
case 'g':
gain_db = atof(optarg);
if (gain_db < 0.0) {
fprintf(stderr, "Given gain is below 0. To reduce RX signal, use sound card's mixer (or resistor net)!\n");
exit(0);
}
rx_gain = pow(10, gain_db / 20.0);
*skip_args += 2;
break;
case 'e':
echo_test = 1;
*skip_args += 1;
break;
case 'x':
use_mncc_cross = 1;
*skip_args += 1;
break;
case 'm':
use_mncc_sock = 1;
*skip_args += 1;
break;
case OPT_MNCC_NAME:
mncc_name = strdup(optarg);
*skip_args += 2;
break;
case 'c':
call_audiodev = strdup(optarg);
*skip_args += 2;
break;
case OPT_CALL_SAMPLERATE:
call_samplerate = atoi(optarg);
*skip_args += 2;
break;
case 't':
send_patterns = atoi(optarg);
*skip_args += 2;
break;
case 'l':
loopback = atoi(optarg);
*skip_args += 2;
break;
case 'r':
rt_prio = atoi(optarg);
*skip_args += 2;
break;
case OPT_WRITE_RX_WAVE:
write_rx_wave = strdup(optarg);
*skip_args += 2;
break;
case OPT_WRITE_TX_WAVE:
write_tx_wave = strdup(optarg);
*skip_args += 2;
break;
case OPT_READ_RX_WAVE:
read_rx_wave = strdup(optarg);
*skip_args += 2;
break;
case OPT_READ_TX_WAVE:
read_tx_wave = strdup(optarg);
*skip_args += 2;
break;
default:
#ifdef HAVE_SDR
rc = sdr_config_opt_switch(c, skip_args);
if (rc < 0)
exit (0);
#endif
break;
}
}
/* global variable to quit main loop */
int quit = 0;
void sighandler(int sigset)
{
if (sigset == SIGHUP)
return;
if (sigset == SIGPIPE)
return;
clear_console_text();
printf("Signal received: %d\n", sigset);
quit = 1;
}
static int get_char()
{
struct timeval tv = {0, 0};
fd_set fds;
char c = 0;
int __attribute__((__unused__)) rc;
FD_ZERO(&fds);
FD_SET(0, &fds);
select(0+1, &fds, NULL, NULL, &tv);
if (FD_ISSET(0, &fds)) {
rc = read(0, &c, 1);
return c;
} else
return -1;
}
/* Loop through all transceiver instances of one network. */
void main_mobile(int *quit, int latency, int interval, void (*myhandler)(void), const char *station_id, int station_id_digits)
{
int latspl;
sender_t *sender;
double last_time_call = 0, begin_time, now, sleep;
struct termios term, term_orig;
int c;
int rc;
if (!got_init) {
fprintf(stderr, "main_mobile_init was not called, please fix!\n");
abort();
}
/* latency of send buffer in samples */
latspl = samplerate * latency / 1000;
/* check MNCC support */
if (use_mncc_cross && num_kanal == 1) {
fprintf(stderr, "You selected built-in call forwarding, but only channel is used. Does this makes sense?\n");
return;
}
if (use_mncc_sock && use_mncc_cross) {
fprintf(stderr, "You selected MNCC socket interface and built-in call forwarding, but only one can be selected.\n");
return;
}
if (echo_test && call_audiodev[0]) {
fprintf(stderr, "You selected call device (headset) and echo test, but only one can be selected.\n");
return;
}
if (use_mncc_sock && call_audiodev[0]) {
fprintf(stderr, "You selected MNCC socket interface, but it cannot be used with call device (headset).\n");
return;
}
if (use_mncc_cross && call_audiodev[0]) {
fprintf(stderr, "You selected built-in call forwarding, but it cannot be used with call device (headset).\n");
return;
}
if (use_mncc_sock && echo_test) {
fprintf(stderr, "You selected MNCC socket interface, but it cannot be used with echo test.\n");
return;
}
if (use_mncc_cross && echo_test) {
fprintf(stderr, "You selected built-in call forwarding, but it cannot be used with echo test.\n");
return;
}
/* init mncc */
if (use_mncc_sock) {
char mncc_sock_name[64];
if (mncc_name[0]) {
snprintf(mncc_sock_name, sizeof(mncc_sock_name), "/tmp/bsc_mncc_%s", mncc_name);
mncc_sock_name[sizeof(mncc_sock_name) - 1] = '\0';
} else
strcpy(mncc_sock_name, "/tmp/bsc_mncc");
rc = mncc_sock_init(mncc_sock_name);
if (rc < 0) {
fprintf(stderr, "Failed to setup MNCC socket. Quitting!\n");
return;
}
} else if (use_mncc_cross) {
rc = mncc_cross_init();
if (rc < 0) {
fprintf(stderr, "Failed to setup MNCC crossing process. Quitting!\n");
return;
}
} else {
console_init(station_id, call_audiodev, call_samplerate, latency, station_id_digits, loopback, 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;
}
#ifdef HAVE_SDR
rc = sdr_configure(samplerate);
if (rc < 0)
return;
#endif
/* open audio */
if (sender_open_audio(latspl))
return;
if (console_open_audio(latspl))
return;
/* real time priority */
if (rt_prio > 0) {
struct sched_param schedp;
int rc;
memset(&schedp, 0, sizeof(schedp));
schedp.sched_priority = rt_prio;
rc = sched_setscheduler(0, SCHED_RR, &schedp);
if (rc)
fprintf(stderr, "Error setting SCHED_RR with prio %d\n", rt_prio);
}
/* prepare terminal */
tcgetattr(0, &term_orig);
term = term_orig;
term.c_lflag &= ~(ISIG|ICANON|ECHO);
term.c_cc[VMIN]=1;
term.c_cc[VTIME]=2;
tcsetattr(0, TCSANOW, &term);
/* catch signals */
signal(SIGINT, sighandler);
signal(SIGHUP, sighandler);
signal(SIGTERM, sighandler);
signal(SIGPIPE, sighandler);
/* start streaming */
if (sender_start_audio())
*quit = 1;
if (console_start_audio())
*quit = 1;
while(!(*quit)) {
begin_time = get_time();
/* process sound of all transceivers */
for (sender = sender_head; sender; sender = sender->next) {
/* do not process audio for an audio slave, since it is done by audio master */
if (sender->master) /* if master is set, we are an audio slave */
continue;
process_sender_audio(sender, quit, latspl);
}
/* process timers */
process_timer();
/* process audio for mncc call instances */
now = get_time();
if (now - last_time_call >= 0.1)
last_time_call = now;
if (now - last_time_call >= 0.020) {
last_time_call += 0.020;
/* call clock every 20ms */
call_clock();
}
next_char:
c = get_char();
switch (c) {
case 3:
/* quit */
clear_console_text();
printf("CTRL+c received, quitting!\n");
*quit = 1;
goto next_char;
case 'w':
/* toggle wave display */
display_status_on(0);
display_measurements_on(0);
#ifdef HAVE_SDR
display_iq_on(0);
display_spectrum_on(0);
#endif
display_wave_on(-1);
goto next_char;
case 'c':
/* toggle call state display */
display_wave_on(0);
display_measurements_on(0);
#ifdef HAVE_SDR
display_iq_on(0);
display_spectrum_on(0);
#endif
display_status_on(-1);
goto next_char;
case 'm':
/* toggle measurements display */
display_wave_on(0);
display_status_on(0);
#ifdef HAVE_SDR
display_iq_on(0);
display_spectrum_on(0);
#endif
display_measurements_on(-1);
goto next_char;
#ifdef HAVE_SDR
case 'q':
/* toggle IQ display */
display_wave_on(0);
display_status_on(0);
display_measurements_on(0);
display_spectrum_on(0);
display_iq_on(-1);
goto next_char;
case 's':
/* toggle spectrum display */
display_wave_on(0);
display_status_on(0);
display_measurements_on(0);
display_iq_on(0);
display_spectrum_on(-1);
goto next_char;
#endif
case 'i':
/* dump info */
dump_info();
goto next_char;
}
/* process call control */
if (use_mncc_sock)
mncc_sock_handle();
else if (use_mncc_cross)
mncc_cross_handle();
else
process_console(c);
if (myhandler)
myhandler();
display_measurements((double)interval / 1000.0);
now = get_time();
/* sleep interval */
sleep = ((double)interval / 1000.0) - (now - begin_time);
if (sleep > 0)
usleep(sleep * 1000000.0);
// now = get_time();
// printf("duration =%.6f\n", now - begin_time);
}
/* reset signals */
signal(SIGINT, SIG_DFL);
signal(SIGHUP, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
/* get rid of last entry */
clear_console_text();
/* reset terminal */
tcsetattr(0, TCSANOW, &term_orig);
/* reset real time prio */
if (rt_prio > 0) {
struct sched_param schedp;
memset(&schedp, 0, sizeof(schedp));
schedp.sched_priority = 0;
sched_setscheduler(0, SCHED_OTHER, &schedp);
}
/* cleanup call control */
if (!use_mncc_sock && !use_mncc_cross)
console_cleanup();
/* close mncc socket */
if (use_mncc_sock)
mncc_sock_exit();
/* close mncc forwarding */
if (use_mncc_cross)
mncc_cross_exit();
}

View File

@@ -0,0 +1,45 @@
extern int num_kanal;
extern int kanal[];
extern int swap_links;
extern int num_audiodev;
extern const char *audiodev[];
extern int use_sdr;
extern int samplerate;
extern int interval;
extern int latency;
extern int uses_emphasis;
extern int do_pre_emphasis;
extern int do_de_emphasis;
extern double rx_gain;
extern int loopback;
extern int rt_prio;
extern const char *write_rx_wave;
extern const char *write_tx_wave;
extern const char *read_rx_wave;
extern const char *read_tx_wave;
void main_mobile_init(void);
void main_mobile_print_help(const char *arg0, const char *ext_usage);
void main_mobile_print_hotkeys(void);
extern struct option *long_options;
extern char *optstring;
void main_mobile_set_options(const char *optstring_special, struct option *long_options_special);
void main_mobile_opt_switch(int c, char *arg0, int *skip_args);
#define OPT_ARRAY(num_name, name, value) \
{ \
if (num_name == MAX_SENDER) { \
fprintf(stderr, "Too many channels defined!\n"); \
exit(0); \
} \
name[num_name++] = value; \
}
extern int quit;
void sighandler(int sigset);
void main_mobile(int *quit, int latency, int interval, void (*myhandler)(void), const char *station_id, int station_id_digits);
void dump_info(void);

449
src/libmobile/sender.c Normal file
View File

@@ -0,0 +1,449 @@
/* Common transceiver functions
*
* (C) 2016 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define CHAN sender->kanal
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include "../libsample/sample.h"
#include "../common/debug.h"
#include "sender.h"
#include "../libtimer/timer.h"
/* debug time consumption of audio processing */
//#define DEBUG_TIME_CONSUMPTION
sender_t *sender_head = NULL;
static sender_t **sender_tailp = &sender_head;
int cant_recover = 0;
/* Init transceiver instance and link to list of transceivers. */
int sender_create(sender_t *sender, int kanal, double sendefrequenz, double empfangsfrequenz, const char *audiodev, int use_sdr, int samplerate, double rx_gain, int pre_emphasis, int de_emphasis, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback, enum paging_signal paging_signal)
{
sender_t *master, *slave;
int rc = 0;
sender->kanal = kanal;
sender->sendefrequenz = sendefrequenz;
sender->empfangsfrequenz = (loopback) ? sendefrequenz : empfangsfrequenz;
strncpy(sender->audiodev, audiodev, sizeof(sender->audiodev) - 1);
sender->samplerate = samplerate;
sender->rx_gain = rx_gain;
sender->pre_emphasis = pre_emphasis;
sender->de_emphasis = de_emphasis;
sender->loopback = loopback;
sender->paging_signal = paging_signal;
sender->write_rx_wave = write_rx_wave;
sender->write_tx_wave = write_tx_wave;
sender->read_rx_wave = read_rx_wave;
sender->read_tx_wave = read_tx_wave;
/* no gain with SDR */
if (use_sdr)
sender->rx_gain = 1.0;
PDEBUG_CHAN(DSENDER, DEBUG_DEBUG, "Creating 'Sender' instance\n");
/* if we find a channel that uses the same device as we do,
* we will link us as slave to this master channel. then we
* receive and send audio via second channel of the device
* of the master channel.
*/
for (master = sender_head; master; master = master->next) {
if (master->kanal == kanal) {
PDEBUG(DSENDER, DEBUG_ERROR, "Channel %d may not be defined for multiple transceivers!\n", kanal);
rc = -EIO;
goto error;
}
if (abs(master->kanal - kanal) == 1) {
PDEBUG(DSENDER, DEBUG_NOTICE, "------------------------------------------------------------------------\n");
PDEBUG(DSENDER, DEBUG_NOTICE, "NOTE: Channel %d is next to channel %d. This will cause interferences.\n", kanal, master->kanal);
PDEBUG(DSENDER, DEBUG_NOTICE, "Please use at least one channel distance to avoid that.\n");
PDEBUG(DSENDER, DEBUG_NOTICE, "------------------------------------------------------------------------\n");
}
if (!strcmp(master->audiodev, audiodev))
break;
}
if (master) {
if (master->paging_signal != PAGING_SIGNAL_NONE && !use_sdr) {
PDEBUG(DSENDER, DEBUG_ERROR, "Cannot share audio device with channel %d, because its second audio channel is used for paging signal! Use different audio device.\n", master->kanal);
rc = -EBUSY;
goto error;
}
if (paging_signal != PAGING_SIGNAL_NONE && !use_sdr) {
PDEBUG(DSENDER, DEBUG_ERROR, "Cannot share audio device with channel %d, because we need a second audio channel for paging signal! Use different audio device.\n", master->kanal);
rc = -EBUSY;
goto error;
}
/* link us to a master */
sender->master = master;
/* link master (or last slave) to us */
for (slave = master; ; slave = slave->slave) {
if (!slave->slave)
break;
}
slave->slave = sender;
} else {
/* link audio device */
#ifdef HAVE_SDR
if (use_sdr) {
sender->audio_open = sdr_open;
sender->audio_start = sdr_start;
sender->audio_close = sdr_close;
sender->audio_read = sdr_read;
sender->audio_write = sdr_write;
sender->audio_get_tosend = sdr_get_tosend;
} else
#endif
{
sender->audio_open = sound_open;
sender->audio_start = sound_start;
sender->audio_close = sound_close;
sender->audio_read = sound_read;
sender->audio_write = sound_write;
sender->audio_get_tosend = sound_get_tosend;
}
}
rc = init_samplerate(&sender->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(&sender->dejitter, samplerate / 5);
if (rc < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create and init audio buffer!\n");
goto error;
}
rc = init_emphasis(&sender->estate, samplerate, CUT_OFF_EMPHASIS_DEFAULT);
if (rc < 0)
goto error;
*sender_tailp = sender;
sender_tailp = &sender->next;
display_wave_init(sender, samplerate);
display_measurements_init(sender, samplerate);
return 0;
error:
sender_destroy(sender);
return rc;
}
int sender_open_audio(int latspl)
{
sender_t *master, *inst;
int channels;
int i;
int rc;
for (master = sender_head; master; master = master->next) {
/* skip audio slaves */
if (master->master)
continue;
/* get list of frequencies */
channels = 0;
for (inst = master; inst; inst = inst->slave) {
channels++;
}
double tx_f[channels], rx_f[channels], paging_frequency = 0.0;
for (i = 0, inst = master; inst; i++, inst = inst->slave) {
tx_f[i] = inst->sendefrequenz;
rx_f[i] = inst->empfangsfrequenz;
if (inst->ruffrequenz)
paging_frequency = inst->ruffrequenz;
}
if (master->write_rx_wave) {
rc = wave_create_record(&master->wave_rx_rec, master->write_rx_wave, master->samplerate, channels, master->max_deviation);
if (rc < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create WAVE recoding instance!\n");
return rc;
}
}
if (master->write_tx_wave) {
rc = wave_create_record(&master->wave_tx_rec, master->write_tx_wave, master->samplerate, channels, master->max_deviation);
if (rc < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create WAVE recoding instance!\n");
return rc;
}
}
if (master->read_rx_wave) {
rc = wave_create_playback(&master->wave_rx_play, master->read_rx_wave, master->samplerate, channels, master->max_deviation);
if (rc < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create WAVE playback instance!\n");
return rc;
}
}
if (master->read_tx_wave) {
rc = wave_create_playback(&master->wave_tx_play, master->read_tx_wave, master->samplerate, channels, master->max_deviation);
if (rc < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create WAVE playback instance!\n");
return rc;
}
}
/* open device */
master->audio = master->audio_open(master->audiodev, tx_f, rx_f, channels, paging_frequency, master->samplerate, latspl, master->max_deviation, master->max_modulation);
if (!master->audio) {
PDEBUG(DSENDER, DEBUG_ERROR, "No audio device!\n");
return -EIO;
}
}
return 0;
}
int sender_start_audio(void)
{
sender_t *master;
int rc = 0;
for (master = sender_head; master; master = master->next) {
/* skip audio slaves */
if (master->master)
continue;
rc = master->audio_start(master->audio);
if (rc)
break;
}
return rc;
}
/* Destroy transceiver instance and unlink from list. */
void sender_destroy(sender_t *sender)
{
PDEBUG_CHAN(DSENDER, DEBUG_DEBUG, "Destroying 'Sender' instance\n");
sender_tailp = &sender_head;
while (*sender_tailp) {
if (sender == *sender_tailp)
*sender_tailp = (*sender_tailp)->next;
else
sender_tailp = &((*sender_tailp)->next);
}
if (sender->audio) {
sender->audio_close(sender->audio);
sender->audio = NULL;
}
wave_destroy_record(&sender->wave_rx_rec);
wave_destroy_record(&sender->wave_tx_rec);
wave_destroy_playback(&sender->wave_rx_play);
wave_destroy_playback(&sender->wave_tx_play);
jitter_destroy(&sender->dejitter);
}
void sender_set_fm(sender_t *sender, double max_deviation, double max_modulation, double dBm0_deviation, double max_display)
{
sender->max_deviation = max_deviation;
sender->max_modulation = max_modulation;
sender->dBm0_deviation = dBm0_deviation;
sender->max_display = max_display;
PDEBUG_CHAN(DSENDER, DEBUG_DEBUG, "Maxium deviation: %.1f kHz, Maximum modulation: %.1f kHz\n", max_deviation / 1000.0, max_modulation / 1000.0);
PDEBUG_CHAN(DSENDER, DEBUG_DEBUG, "Deviation at dBm0 (audio level): %.1f kHz\n", dBm0_deviation / 1000.0);
}
static void gain_samples(sample_t *samples, int length, double gain)
{
int i;
for (i = 0; i < length; i++)
*samples++ *= gain;
}
/* Handle audio streaming of one transceiver. */
void process_sender_audio(sender_t *sender, int *quit, int latspl)
{
sender_t *inst;
int rc, count;
int num_chan, i;
#ifdef DEBUG_TIME_CONSUMPTION
static double t1, t2, t3, t4, t5, d1 = 0, d2 = 0, d3 = 0, d4 = 0, s = 0;
#endif
/* count instances for audio channel */
for (num_chan = 0, inst = sender; inst; num_chan++, inst = inst->slave);
sample_t buff[num_chan][latspl], *samples[num_chan];
uint8_t pbuff[num_chan][latspl], *power[num_chan];
enum paging_signal paging_signal[num_chan];
int on[num_chan];
double rf_level_db[num_chan];
for (i = 0; i < num_chan; i++) {
samples[i] = buff[i];
power[i] = pbuff[i];
}
#ifdef DEBUG_TIME_CONSUMPTION
t1 = get_time();
#endif
count = sender->audio_get_tosend(sender->audio, latspl);
if (count < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to get samples in buffer (rc = %d)!\n", count);
if (count == -EPIPE) {
if (cant_recover) {
cant_recover:
PDEBUG(DSENDER, DEBUG_ERROR, "Cannot recover due to measurements, quitting!\n");
*quit = 1;
return;
}
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover!\n");
}
return;
}
#ifdef DEBUG_TIME_CONSUMPTION
t2 = get_time();
#endif
if (count > 0) {
/* limit to our buffer */
if (count > latspl)
count = latspl;
/* loop through all channels */
for (i = 0, inst = sender; inst; i++, inst = inst->slave) {
/* load TX data from audio loop or from sender instance */
if (inst->loopback == 3)
jitter_load(&inst->dejitter, samples[i], count);
else
sender_send(inst, samples[i], power[i], count);
/* internal loopback: loop back TX audio to RX */
if (inst->loopback == 1) {
display_wave(inst, samples[i], count, inst->max_display);
sender_receive(inst, samples[i], count, 0.0);
}
/* do pre emphasis towards radio */
if (inst->pre_emphasis)
pre_emphasis(&inst->estate, samples[i], count);
/* normal level to frequency deviation of dBm0 */
gain_samples(samples[i], count, inst->dBm0_deviation);
/* set paging signal */
paging_signal[i] = inst->paging_signal;
on[i] = inst->paging_on;
}
#ifdef DEBUG_TIME_CONSUMPTION
t2 = get_time();
#endif
if (sender->wave_tx_rec.fp)
wave_write(&sender->wave_tx_rec, samples, count);
if (sender->wave_tx_play.fp)
wave_read(&sender->wave_tx_play, samples, count);
rc = sender->audio_write(sender->audio, samples, power, count, paging_signal, on, num_chan);
if (rc < 0) {
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to write TX data to audio device (rc = %d)\n", rc);
if (rc == -EPIPE) {
if (cant_recover)
goto cant_recover;
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover!\n");
}
return;
}
}
#ifdef DEBUG_TIME_CONSUMPTION
t3 = get_time();
#endif
count = sender->audio_read(sender->audio, samples, latspl, num_chan, rf_level_db);
if (count < 0) {
/* special case when audio_read wants us to quit */
if (count == -EPERM) {
*quit = 1;
return;
}
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to read from audio device (rc = %d)!\n", count);
if (count == -EPIPE) {
if (cant_recover)
goto cant_recover;
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover!\n");
}
return;
}
#ifdef DEBUG_TIME_CONSUMPTION
t4 = get_time();
#endif
if (count) {
if (sender->wave_rx_rec.fp)
wave_write(&sender->wave_rx_rec, samples, count);
if (sender->wave_rx_play.fp)
wave_read(&sender->wave_rx_play, samples, count);
/* loop through all channels */
for (i = 0, inst = sender; inst; i++, inst = inst->slave) {
/* frequency deviation of dBm0 to normal level */
gain_samples(samples[i], count, 1.0 / inst->dBm0_deviation);
/* rx gain */
if (inst->rx_gain != 1.0)
gain_samples(samples[i], count, inst->rx_gain);
/* do filter and de-emphasis from radio receive audio, process echo test */
if (inst->de_emphasis) {
dc_filter(&inst->estate, samples[i], count);
de_emphasis(&inst->estate, samples[i], count);
}
if (inst->loopback != 1) {
display_wave(inst, samples[i], count, inst->max_display);
sender_receive(inst, samples[i], count, rf_level_db[i]);
}
if (inst->loopback == 3)
jitter_save(&inst->dejitter, samples[i], count);
}
}
#ifdef DEBUG_TIME_CONSUMPTION
t5 = get_time();
d1 += (t2 - t1);
d2 += (t3 - t2);
d3 += (t4 - t3);
d4 += (t5 - t4);
if (get_time() - s >= 1.0) {
printf("duration: %.3f (process TX), %.3f (send TX), %.3f (receive RX), %.3f (process RX)\n", d1, d2, d3, d4);
s = get_time();
d1 = d2 = d3 = d4 = 0;
}
#endif
}
void sender_paging(sender_t *sender, int on)
{
sender->paging_on = on;
}
sender_t *get_sender_by_empfangsfrequenz(double freq)
{
sender_t *sender;
for (sender = sender_head; sender; sender = sender->next) {
if (sender->empfangsfrequenz == freq)
return sender;
}
return NULL;
}

101
src/libmobile/sender.h Normal file
View File

@@ -0,0 +1,101 @@
#include "../libsound/sound.h"
#ifdef HAVE_SDR
#include "../libsdr/sdr.h"
#endif
#include "../libwave/wave.h"
#include "../libsamplerate/samplerate.h"
#include "../libjitter/jitter.h"
#include "../libemphasis/emphasis.h"
#include "../common/display.h"
#define MAX_SENDER 16
/* how to send a 'paging' signal (trigger transmitter) */
enum paging_signal {
PAGING_SIGNAL_NONE = 0,
PAGING_SIGNAL_TONE,
PAGING_SIGNAL_NOTONE,
PAGING_SIGNAL_POSITIVE,
PAGING_SIGNAL_NEGATIVE,
};
/* common structure of each transmitter */
typedef struct sender {
struct sender *next;
struct sender *slave; /* points to 'slave' that uses next channel of audio device */
struct sender *master; /* if set, the audio device is owned by 'master' */
/* system info */
int kanal; /* channel number */
double sendefrequenz; /* transmitter frequency */
double empfangsfrequenz; /* receiver frequency */
double ruffrequenz; /* special paging frequency used for B-Netz */
/* fm levels */
double max_deviation; /* max frequency deviation */
double max_modulation; /* max frequency modulated */
double dBm0_deviation; /* deviation of 1000 Hz reference tone at dBm0 */
double max_display; /* level of displaying wave form */
/* audio */
void *audio;
char audiodev[64]; /* audio device name (alsa or sdr) */
void *(*audio_open)(const char *, double *, double *, int, double, int, int, double, double);
int (*audio_start)(void *);
void (*audio_close)(void *);
int (*audio_write)(void *, sample_t **, uint8_t **, int, enum paging_signal *, int *, int);
int (*audio_read)(void *, sample_t **, int, int, double *);
int (*audio_get_tosend)(void *, int);
int samplerate;
samplerate_t srstate; /* sample rate conversion state */
double rx_gain; /* factor of level to apply on rx samples */
int pre_emphasis; /* use pre_emhasis, done by sender */
int de_emphasis; /* use de_emhasis, done by sender */
emphasis_t estate; /* pre and de emphasis */
/* loopback test */
int loopback; /* 0 = off, 1 = internal, 2 = external */
/* record and playback */
const char *write_rx_wave; /* file name pointers */
const char *write_tx_wave;
const char *read_rx_wave;
const char *read_tx_wave;
wave_rec_t wave_rx_rec; /* wave recording (from rx) */
wave_rec_t wave_tx_rec; /* wave recording (from tx) */
wave_play_t wave_rx_play; /* wave playback (as rx) */
wave_play_t wave_tx_play; /* wave playback (as tx) */
/* audio buffer for audio to send to transmitter (also used as loopback buffer) */
jitter_t dejitter;
/* audio buffer for audio to send to caller (20ms = 160 samples @ 8000Hz) */
sample_t rxbuf[160];
int rxbuf_pos; /* current fill of buffer */
/* paging tone */
enum paging_signal paging_signal; /* if paging signal is used and how it is performed */
int paging_on; /* 1 or 0 for on or off */
/* display wave */
dispwav_t dispwav; /* display wave form */
/* display measurements */
dispmeas_t dispmeas; /* display measurements */
} sender_t;
/* list of all senders */
extern sender_t *sender_head;
extern int cant_recover;
int sender_create(sender_t *sender, int kanal, double sendefrequenz, double empfangsfrequenz, const char *audiodev, int use_sdr, int samplerate, double rx_gain, int pre_emphasis, int de_emphasis, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback, enum paging_signal paging_signal);
void sender_destroy(sender_t *sender);
void sender_set_fm(sender_t *sender, double max_deviation, double max_modulation, double dBm0_deviation, double max_display);
int sender_open_audio(int latspl);
int sender_start_audio(void);
void process_sender_audio(sender_t *sender, int *quit, int latspl);
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int count);
void sender_receive(sender_t *sender, sample_t *samples, int count, double rf_level_db);
void sender_paging(sender_t *sender, int on);
sender_t *get_sender_by_empfangsfrequenz(double freq);