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:
parent
cd90ed2dd5
commit
04fe6b5f05
|
@ -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
|
|
@ -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.
|
@ -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)
|
|
@ -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();
|
||||
}
|
Loading…
Reference in New Issue