Restructure: Move mobile from common code to 'libmobile'
This commit is contained in:
14
src/libmobile/Makefile.am
Normal file
14
src/libmobile/Makefile.am
Normal 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
796
src/libmobile/call.c
Normal 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
40
src/libmobile/call.h
Normal 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);
|
||||
|
147
src/libmobile/display_status.c
Normal file
147
src/libmobile/display_status.c
Normal 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
688
src/libmobile/main_mobile.c
Normal 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();
|
||||
}
|
||||
|
45
src/libmobile/main_mobile.h
Normal file
45
src/libmobile/main_mobile.h
Normal 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
449
src/libmobile/sender.c
Normal 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
101
src/libmobile/sender.h
Normal 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);
|
||||
|
Reference in New Issue
Block a user