Add 'trau2rtp' demo program illustrating the use of new TRAU code

This is just for development and hence in 'contrib', and not built by
default.  For details, see the included README

Change-Id: I0190872dd282bcfe0f97bb4f8ab8d09023f9f06b
This commit is contained in:
Harald Welte 2020-06-20 21:05:31 +02:00
parent cd90ed2dd5
commit 04fe6b5f05
8 changed files with 442 additions and 0 deletions

13
contrib/trau2rtp/Makefile Normal file
View File

@ -0,0 +1,13 @@
CFLAGS := -Wall `pkg-config --cflags libosmocore libosmogsm libosmotrau`
LIBS := `pkg-config --libs libosmocore libosmogsm libosmotrau`
all: trau2rtp
%.o: %.c
$(CC) $(CFLAGS) -o $@ -c $^
trau2rtp: trau2rtp.o
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
clean:
@rm -f *.o trau2rtp

67
contrib/trau2rtp/README Normal file
View File

@ -0,0 +1,67 @@
this is a small utility program illustrating the use of the new (2020)
TRAU related blocks like I.460 mux, TRAU frame synchronizer, TRAU frame
decoder and trau2rtp conversion.
The only argument to the progrmam is the input file, which can either be
* a DAHDI device like /dev/dahdi/chan/004/002 to use TS2 on span/line 4
* a pre-recorded capture file containing raw binary 64k timeslot data
The tool will automatically determine if a regular file or a DAHDI device
was passed, and behave accordingly.
== reading capture files
There are FR and EFR exampel captures included.
The input data (*.log.bz2) was generated using strace on an osmo-nitb process
while a MO-to-MT call was running on two sub-slots of TS2.
The strace log is converted to a binary stream of the raw 64bit E1 slot
using strace-write-parse.py
You can use the too like this:
* start osmo-gapk as a RTP to ALSA sink like
osmo-gapk -I 127.0.0.1/9999 -f gsm -g rawpcm-s16le -A default # for FR
osmo-gapk -I 127.0.0.1/9999 -f rtp-efr -g rawpcm-s16le -A default # for EFR
* run trau2rtp to read the bin file and generate RTP:
./trau2rtp e1_ts2_fr.bin # for FR
./trau2rtp e1_ts2_efr.bin # for EFR
== Interfacing a DAHDI device
It can be operated in two modes:
a) local loop between sub-slots 1+2 (bit-offset 2 + 4), i.e. connecting the first
two calls on a 'typical' TRX0 where the first sub-slot (bit-offset 0) is not
used,
OR
b) interface between 64k TRAU slot and RTP. IP addresses + port numbers are
compiled-in, you need to modify them accordingly.
In mode "a", you can use it to have voice calls for low-level debugging without a media
gateway.
In mode "b", you can e.g. play back audio from an exterenal RTP source and listen to it
over the GSM attached mobile phone.
Command line for playing back EFR via RTP:
./osmo-gapk -f amr-efr -i ../tests/ref-files/hhgttg_part1_5.s16.amr-efr -g rtp-efr -O 192.168.11.179/8002 -t -l
Command line for playing back FR via RTP:
./osmo-gapk -f gsm -i ../tests/ref-files/hhgttg_part1_5.s16.gsm -g gsm -O 192.168.11.179/8002 -t -l
The '-l' option is experimental and requires gapk patch I2d552695dfb4cc96039838e79e0f5ae25a6737c8. If you want
to use it with EFR, you need to change pq_file.c to skip the AMR file header when rewinding:
- fseek(state->fh, 0, SEEK_SET);
+ fseek(state->fh, 6, SEEK_SET);
The code has been tested against BS-11 and RBS6000/DUG20 in both modes (loop vs. RTP) and for FR and EFR.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,20 @@
#!/usr/bin/python3
# parses the output of strace and stroes the binary writes to a file
import re, binascii
from struct import *
p = re.compile('read\(\d+, "(.*)", \d+\) = \d+')
fi = open("e1_ts2_short.log", "r")
fo = open("e1_ts2_short.bin", "wb")
for line in fi:
m = p.match(line)
data = m.group(1)
snippets = [data[2+i:4+i] for i in range(0, len(data), 4)]
for s in snippets:
b = binascii.unhexlify(s)
fo.write(b)

340
contrib/trau2rtp/trau2rtp.c Normal file
View File

@ -0,0 +1,340 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <dahdi/user.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/application.h>
#include <osmocom/gsm/i460_mux.h>
#include <osmocom/trau/trau_sync.h>
#include <osmocom/trau/trau_frame.h>
#include <osmocom/trau/trau_rtp.h>
#include <osmocom/trau/osmo_ortp.h>
#define D_BCHAN_TX_GRAN 160
/***********************************************************************
* BEGIN CONFIGURATION
***********************************************************************/
/* HACK: Those do not have getopt but need to be modified in source code to match your enviroment */
/* do we just locally loop the calls from 1->2, or do we interface with RTP? */
static bool g_local_loop = false;
/* remote IP address to which we send RTP data (unless g_local_loop mode) */
const char *g_remote_host = "192.168.101.131";
/* remote UDP base port to which we send RTP data: +2 for every sub-slot */
const int g_remote_port = 9000;
/* local UDP base port on which we receive RTP data: +2 for every sub-slot */
const int g_local_port = 8000;
/* codec; can be OSMO_TRAU16_FT_FR or OSMO_TRAU16_FT_EFR */
const enum osmo_trau_frame_type g_ftype = OSMO_TRAU16_FT_EFR;
/***********************************************************************
* END CONFIGURATION
***********************************************************************/
/* do we have a DAHDI chardev to which we can write RTP->TRAU conversion data/ */
static bool g_ts_is_writable = false;
struct sc_state {
unsigned int num;
struct osmo_i460_subchan *i460_sc;
struct osmo_fsm_inst *sync_fi;
struct osmo_rtp_socket *rtps;
struct osmo_trau2rtp_state t2r;
};
struct state {
int in_fd;
struct osmo_i460_timeslot i460_ts;
struct sc_state sc[4];
};
static struct state g_st;
#define LOGSC(sc, fmt, args...) printf("SC%u: " fmt, (sc)->num, ## args)
static struct sc_state *opposite_schan(struct sc_state *sc)
{
/* we blindly assume that there is a call between sub-channel 1 + sub-channel 2 */
switch (sc->num) {
case 1:
return &g_st.sc[2];
case 2:
return &g_st.sc[1];
default:
OSMO_ASSERT(0);
}
}
/* called by I.460 de-multeiplexer; feed output of I.460 demux into TRAU frame sync */
static void i460_demux_bits_cb(struct osmo_i460_subchan *schan, void *user_data,
const ubit_t *bits, unsigned int num_bits)
{
struct sc_state *sc = user_data;
//printf("I460: %s\n", osmo_ubit_dump(bits, num_bits));
osmo_trau_sync_rx_ubits(sc->sync_fi, bits, num_bits);
}
/* called for each synchronized TRAU frame received; decode frame + convert to RTP */
static void sync_frame_out_cb(void *user_data, const ubit_t *bits, unsigned int num_bits)
{
struct sc_state *sc = user_data;
struct osmo_trau_frame fr;
int rc;
LOGSC(sc, "Rx TRAU: %s\n", osmo_ubit_dump(bits, num_bits));
if (!bits)
goto skip;
rc = osmo_trau_frame_decode_16k(&fr, bits, OSMO_TRAU_DIR_UL);
if (rc != 0)
goto skip;
uint8_t sid;
switch (fr.type) {
case OSMO_TRAU16_FT_FR:
case OSMO_TRAU16_FT_EFR:
sid = (fr.c_bits[13-1]) << 1 | (fr.c_bits[14-1] << 0);
LOGSC(sc, "-> FT=%s, BFI=%u, SID=%u, TAF=%u DTXd=%u\n",
osmo_trau_frame_type_name(fr.type), fr.c_bits[12-1], sid, fr.c_bits[15-1], fr.c_bits[17-1]);
break;
default:
LOGSC(sc, "-> FT=%s\n", osmo_trau_frame_type_name(fr.type));
break;
}
if (g_local_loop) {
/* Mirror back to other sub-slot */
struct sc_state *peer = opposite_schan(sc);
if (peer) {
struct msgb *msg = msgb_alloc(2*40*8, "mirror");
fr.c_bits[12-1] = 1; /* C12 = good u-link frame */
memset(&fr.c_bits[13-1], 1, 3); /* C13..C15: spare */
fr.c_bits[16-1] = 1; /* C16 = SP[eech]; no DTX */
memset(&fr.c_bits[6-1], 0, 6); /* C6..C11: tie alignment */
fr.dir = OSMO_TRAU_DIR_DL;
rc = osmo_trau_frame_encode(msgb_data(msg), 2*40*8, &fr);
OSMO_ASSERT(rc >= 0);
msgb_put(msg, rc);
osmo_i460_mux_enqueue(peer->i460_sc, msg);
}
} else {
/* Convert to RTP */
if (fr.type != OSMO_TRAU16_FT_FR && fr.type != OSMO_TRAU16_FT_EFR)
goto skip;
uint8_t rtpbuf[35];
struct osmo_trau2rtp_state t2rs = {
.type = fr.type,
};
memset(rtpbuf, 0, sizeof(rtpbuf));
rc = osmo_trau2rtp(rtpbuf, sizeof(rtpbuf), &fr, &t2rs);
LOGSC(sc, "Tx RTP: %s\n", osmo_hexdump(rtpbuf, rc));
if (rc)
osmo_rtp_send_frame_ext(sc->rtps, rtpbuf, rc, 160, false);
else {
osmo_rtp_skipped_frame(sc->rtps, 160);
}
return;
}
skip:
if (!g_local_loop)
osmo_rtp_skipped_frame(sc->rtps, 160);
}
static int dahdi_set_bufinfo(int fd, int as_sigchan)
{
struct dahdi_bufferinfo bi;
int x = 0;
if (ioctl(fd, DAHDI_GET_BUFINFO, &bi)) {
LOGP(DLINP, LOGL_ERROR, "Error getting bufinfo\n");
return -EIO;
}
if (as_sigchan) {
bi.numbufs = 4;
bi.bufsize = 512;
} else {
bi.numbufs = 8;
bi.bufsize = D_BCHAN_TX_GRAN;
bi.txbufpolicy = DAHDI_POLICY_WHEN_FULL;
}
if (ioctl(fd, DAHDI_SET_BUFINFO, &bi)) {
fprintf(stderr, "Error setting DAHDI bufinfo\n");
return -EIO;
}
if (!as_sigchan) {
if (ioctl(fd, DAHDI_AUDIOMODE, &x)) {
fprintf(stderr, "Error setting DAHDI bufinfo\n");
return -EIO;
}
} else {
int one = 1;
ioctl(fd, DAHDI_HDLCFCSMODE, &one);
/* we cannot reliably check for the ioctl return value here
* as this command will fail if the slot _already_ was a
* signalling slot before :( */
}
return 0;
}
static void mux_q_empty_cb(struct osmo_i460_subchan *schan, void *user_data);
/* RTP data was received on the socket */
static void ortp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *payload,
unsigned int payload_len, uint16_t seq_number, uint32_t timestamp, bool marker)
{
struct sc_state *sc = rs->priv;
struct osmo_trau_frame fr;
int rc;
LOGSC(sc, "RTP Rx: %s\n", osmo_hexdump_nospc(payload, payload_len));
sc->t2r.type = g_ftype;
memset(&fr, 0, sizeof(fr));
fr.dir = OSMO_TRAU_DIR_DL;
rc = osmo_rtp2trau(&fr, payload, payload_len, &sc->t2r);
if (rc < 0) {
LOGSC(sc, "Failed to convert RTP to TRAU");
return;
}
struct msgb *msg = msgb_alloc(2*40*8, "rtp2trau");
rc = osmo_trau_frame_encode(msgb_data(msg), 2*40*8, &fr);
OSMO_ASSERT(rc >= 0);
msgb_put(msg, rc);
osmo_i460_mux_enqueue(sc->i460_sc, msg);
}
static void init(const char *fname)
{
struct stat st;
int rc;
struct osmo_i460_schan_desc scd16_0 = {
.rate = OSMO_I460_RATE_16k,
.demux = {
.num_bits = 40*8,
.out_cb_bytes = NULL,
},
};
rc = stat(fname, &st);
OSMO_ASSERT(rc == 0);
if (S_ISCHR(st.st_mode)) {
/* if this is a char device, we assume DAHDI */
g_ts_is_writable = true;
g_st.in_fd = open(fname, O_RDWR);
OSMO_ASSERT(g_st.in_fd >= 0);
dahdi_set_bufinfo(g_st.in_fd, false);
} else {
g_st.in_fd = open(fname, O_RDONLY);
OSMO_ASSERT(g_st.in_fd >= 0);
}
osmo_i460_ts_init(&g_st.i460_ts);
//for (i = 0; i < ARRAY_SIZE(g_st.sc); i++) {
for (int i = 1; i < 3; i++) {
struct sc_state *sc = &g_st.sc[i];
sc->num = i;
scd16_0.bit_offset = i * 2;
scd16_0.demux.user_data = sc;
scd16_0.demux.out_cb_bits = i460_demux_bits_cb,
scd16_0.mux.in_cb_queue_empty = mux_q_empty_cb;
scd16_0.mux.user_data = sc;
sc->i460_sc = osmo_i460_subchan_add(NULL, &g_st.i460_ts, &scd16_0);
OSMO_ASSERT(sc->i460_sc != NULL);
char strbuf[16];
snprintf(strbuf, sizeof(strbuf), "SC%u", sc->num);
sc->sync_fi = osmo_trau_sync_alloc(NULL, strbuf, sync_frame_out_cb, OSMO_TRAU_SYNCP_16_FR_EFR, sc);
OSMO_ASSERT(sc->sync_fi);
sc->rtps = osmo_rtp_socket_create(NULL, OSMO_RTP_F_POLL);
OSMO_ASSERT(sc->rtps);
osmo_rtp_socket_set_pt(sc->rtps, RTP_PT_GSM_FULL);
sc->rtps->rx_cb = ortp_rx_cb;
sc->rtps->priv = sc;
osmo_rtp_socket_bind(sc->rtps, "0.0.0.0", g_local_port + i*2);
osmo_rtp_socket_connect(sc->rtps, g_remote_host, g_remote_port + i*2);
//osmo_rtp_socket_autoconnect(sc->rtps);
}
}
static void mux_q_empty_cb(struct osmo_i460_subchan *schan, void *user_data)
{
struct sc_state *sc = user_data;
struct msgb *msg = msgb_alloc(2*40*8, "mux-enq");
struct osmo_trau_frame traufr = {
.type = g_ftype,
.dir = OSMO_TRAU_DIR_DL,
};
int rc;
LOGSC(sc, "EMPTY -> Generating Tx\n");
if (traufr.type == OSMO_TRAU16_FT_EFR) {
traufr.c_bits[12-1] = 1; /* C12 = good u-link frame */
traufr.c_bits[16-1] = 1; /* C16 = SP[eech]; no DTX */
}
rc = osmo_trau_frame_encode(msgb_data(msg), 2*40*8, &traufr);
OSMO_ASSERT(rc >= 0);
msgb_put(msg, rc);
//LOGSC(sc, "Tx TRAU: %s\n", osmo_ubit_dump(cur, 40*8));
osmo_i460_mux_enqueue(sc->i460_sc, msg);
}
static void process(void)
{
uint8_t buf[D_BCHAN_TX_GRAN];
int rc, nread;
while (rc = read(g_st.in_fd, buf, sizeof(buf))) {
OSMO_ASSERT(rc == sizeof(buf));
nread = rc;
osmo_i460_demux_in(&g_st.i460_ts, buf, nread);
for (int i = 1; i < 3; i++) {
struct sc_state *sc = &g_st.sc[i];
int rc2 = osmo_rtp_socket_poll(sc->rtps);
sc->rtps->rx_user_ts += 160;
//printf("rtp_recv=%d (flags=%x)\n",rc2, sc->rtps->flags);
}
/* write as many bytes as we just received */
osmo_i460_mux_out(&g_st.i460_ts, buf, nread);
if (g_ts_is_writable) {
rc = write(g_st.in_fd, buf, nread);
if (rc != nread)
printf("rc=%d, nread=%d (%s)\n", rc, nread, strerror(errno));
OSMO_ASSERT(rc == nread);
}
//usleep(20000);
}
}
int main(int argc, char **argv)
{
osmo_init_logging2(NULL, NULL);
osmo_fsm_log_addr(false);
log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME);
log_set_category_filter(osmo_stderr_target, DLMIB, true, LOGL_DEBUG);
osmo_rtp_init(NULL);
init(argv[1]);
process();
}