mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-mgw.git
synced 2025-10-23 08:12:01 +00:00
Compare commits
4 Commits
1.13.0
...
laforge/rt
Author | SHA1 | Date | |
---|---|---|---|
|
b7a0e043b5 | ||
|
004ea1398a | ||
|
4f0d72d4c1 | ||
|
c7d15fb70d |
11
contrib/rtp-load-gen/Makefile
Normal file
11
contrib/rtp-load-gen/Makefile
Normal file
@@ -0,0 +1,11 @@
|
||||
CFLAGS=-g -Wall $(shell pkg-config --cflags libosmocore libosmoctrl libosmo-netif liburing)
|
||||
LIBS=-lpthread $(shell pkg-config --libs libosmocore libosmoctrl libosmo-netif liburing)
|
||||
|
||||
rtp-load-gen: rtp-load-gen.o rtp_provider.o rtp_provider_static.o ctrl_if.o
|
||||
$(CC) -o $@ $^ $(LIBS)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -o $@ -c $^
|
||||
|
||||
clean:
|
||||
@rm rtp-load-gen *.o
|
214
contrib/rtp-load-gen/ctrl_if.c
Normal file
214
contrib/rtp-load-gen/ctrl_if.c
Normal file
@@ -0,0 +1,214 @@
|
||||
/* CTRL interface of rtpsource program
|
||||
*
|
||||
* (C) 2020 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* 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 2 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 <osmocom/ctrl/control_cmd.h>
|
||||
|
||||
#include "internal.h"
|
||||
#include "rtp_provider.h"
|
||||
|
||||
static struct rtpsim_connection *find_connection_by_cname(const char *cname)
|
||||
{
|
||||
struct rtpsim_connection *rtpc;
|
||||
struct rtpsim_instance *ri;
|
||||
|
||||
pthread_rwlock_rdlock(&g_rtpsim->rwlock);
|
||||
llist_for_each_entry(ri, &g_rtpsim->instances, list) {
|
||||
rtpc = rtpsim_conn_find(ri, cname);
|
||||
if (rtpc) {
|
||||
pthread_rwlock_unlock(&g_rtpsim->rwlock);
|
||||
return rtpc;
|
||||
}
|
||||
}
|
||||
pthread_rwlock_unlock(&g_rtpsim->rwlock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct rtpsim_connection *create_connection(const char *cname, enum codec_type codec)
|
||||
{
|
||||
struct rtpsim_connection *rtpc;
|
||||
struct rtpsim_instance *ri;
|
||||
|
||||
pthread_rwlock_rdlock(&g_rtpsim->rwlock);
|
||||
llist_for_each_entry(ri, &g_rtpsim->instances, list) {
|
||||
rtpc = rtpsim_conn_reserve(ri, cname, codec);
|
||||
if (rtpc) {
|
||||
pthread_rwlock_unlock(&g_rtpsim->rwlock);
|
||||
return rtpc;
|
||||
}
|
||||
}
|
||||
pthread_rwlock_unlock(&g_rtpsim->rwlock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int connect_connection(struct rtpsim_connection *rtpc, const char *remote_host,
|
||||
uint16_t remote_port, uint8_t pt)
|
||||
{
|
||||
int rc;
|
||||
|
||||
osmo_sockaddr_str_from_str(&rtpc->cfg.remote, remote_host, remote_port);
|
||||
rtpc->cfg.pt = pt;
|
||||
|
||||
rc = rtpsim_conn_connect(rtpc);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = rtpsim_conn_start(rtpc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int delete_connection(struct rtpsim_connection *rtpc)
|
||||
{
|
||||
rtpsim_conn_stop(rtpc);
|
||||
rtpsim_conn_unreserve(rtpc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CTRL_CMD_DEFINE_WO_NOVRF(rtp_create, "rtp_create");
|
||||
static int set_rtp_create(struct ctrl_cmd *cmd, void *data)
|
||||
{
|
||||
struct rtpsim_connection *conn;
|
||||
const char *cname, *codec_str;
|
||||
char *tmp, *saveptr;
|
||||
enum codec_type codec;
|
||||
|
||||
tmp = talloc_strdup(cmd, cmd->value);
|
||||
OSMO_ASSERT(tmp);
|
||||
|
||||
cname = strtok_r(tmp, ",", &saveptr);
|
||||
codec_str = strtok_r(NULL, ",", &saveptr);
|
||||
|
||||
if (!cname || !codec_str) {
|
||||
cmd->reply = "Format is cname,codec";
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (find_connection_by_cname(cname)) {
|
||||
cmd->reply = "Connection already exists for cname";
|
||||
goto error;
|
||||
}
|
||||
|
||||
codec = get_string_value(codec_type_names, codec_str);
|
||||
if (codec < 0) {
|
||||
cmd->reply = "Invalid codec name (try GSM_FR, GSM_EFR etc.)";
|
||||
goto error;
|
||||
}
|
||||
|
||||
conn = create_connection(cname, codec);
|
||||
if (!conn) {
|
||||
cmd->reply = "Error creating RTP connection";
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Respond */
|
||||
cmd->reply = talloc_asprintf(cmd, "%s,%s,%d", conn->cname, conn->cfg.local.ip, conn->cfg.local.port);
|
||||
talloc_free(tmp);
|
||||
return CTRL_CMD_REPLY;
|
||||
|
||||
error:
|
||||
talloc_free(tmp);
|
||||
return CTRL_CMD_ERROR;
|
||||
}
|
||||
|
||||
CTRL_CMD_DEFINE_WO_NOVRF(rtp_connect, "rtp_connect");
|
||||
static int set_rtp_connect(struct ctrl_cmd *cmd, void *data)
|
||||
{
|
||||
struct rtpsim_connection *conn;
|
||||
const char *cname, *remote_host, *remote_port, *pt;
|
||||
char *tmp, *saveptr;
|
||||
int rc;
|
||||
|
||||
tmp = talloc_strdup(cmd, cmd->value);
|
||||
OSMO_ASSERT(tmp);
|
||||
|
||||
/* FIXME: parse command */
|
||||
cname = strtok_r(tmp, ",", &saveptr);
|
||||
remote_host = strtok_r(NULL, ",", &saveptr);
|
||||
remote_port = strtok_r(NULL, ",", &saveptr);
|
||||
pt = strtok_r(NULL, ",", &saveptr);
|
||||
|
||||
if (!cname || !remote_host || !remote_port || !pt) {
|
||||
cmd->reply = "Format is cname,remote_host,remote_port,pt";
|
||||
talloc_free(tmp);
|
||||
return CTRL_CMD_ERROR;
|
||||
}
|
||||
|
||||
conn = find_connection_by_cname(cname);
|
||||
if (!conn) {
|
||||
cmd->reply = "Error finding RTP connection for connect";
|
||||
talloc_free(tmp);
|
||||
return CTRL_CMD_ERROR;
|
||||
}
|
||||
|
||||
rc = connect_connection(conn, remote_host, atoi(remote_port), atoi(pt));
|
||||
if (rc < 0) {
|
||||
cmd->reply = "Error binding RTP connection";
|
||||
talloc_free(tmp);
|
||||
return CTRL_CMD_ERROR;
|
||||
}
|
||||
|
||||
/* Respond */
|
||||
talloc_free(tmp);
|
||||
cmd->reply = talloc_asprintf(cmd, "%s,%s,%d,%d", conn->cname, conn->cfg.remote.ip,
|
||||
conn->cfg.remote.port, conn->cfg.pt);
|
||||
return CTRL_CMD_REPLY;
|
||||
}
|
||||
|
||||
CTRL_CMD_DEFINE_WO_NOVRF(rtp_delete, "rtp_delete");
|
||||
static int set_rtp_delete(struct ctrl_cmd *cmd, void *data)
|
||||
{
|
||||
struct rtpsim_connection *conn;
|
||||
const char *cname = cmd->value;
|
||||
|
||||
conn = find_connection_by_cname(cname);
|
||||
if (!conn) {
|
||||
cmd->reply = "Error finding RTP connection for delete";
|
||||
return CTRL_CMD_ERROR;
|
||||
}
|
||||
cmd->reply = talloc_asprintf(cmd, "%s", conn->cname);
|
||||
|
||||
delete_connection(conn);
|
||||
|
||||
/* Respond */
|
||||
return CTRL_CMD_REPLY;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int rtpsource_ctrl_cmds_install(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_rtp_create);
|
||||
if (rc)
|
||||
goto end;
|
||||
|
||||
rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_rtp_connect);
|
||||
if (rc)
|
||||
goto end;
|
||||
|
||||
rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_rtp_delete);
|
||||
if (rc)
|
||||
goto end;
|
||||
end:
|
||||
return rc;
|
||||
}
|
112
contrib/rtp-load-gen/internal.h
Normal file
112
contrib/rtp-load-gen/internal.h
Normal file
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <pthread.h>
|
||||
#include <liburing.h>
|
||||
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
#include <osmocom/core/rate_ctr.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/ctrl/control_if.h>
|
||||
|
||||
struct rtp_provider_instance;
|
||||
|
||||
/* configuration of one RTP connection/socket */
|
||||
struct rtpsim_connection_cfg {
|
||||
struct osmo_sockaddr_str local;
|
||||
struct osmo_sockaddr_str remote;
|
||||
uint8_t pt;
|
||||
uint32_t ssrc;
|
||||
uint32_t duration;
|
||||
};
|
||||
|
||||
/* TX side state of RTP connection/socket */
|
||||
struct rtpsim_connection_tx {
|
||||
bool enabled;
|
||||
uint32_t timestamp;
|
||||
uint16_t seq;
|
||||
|
||||
struct rtp_provider_instance *rtp_prov_inst;
|
||||
|
||||
/* transmit buffer for outgoing messages */
|
||||
uint8_t *buf;
|
||||
/* used part of buffer */
|
||||
size_t buf_len;
|
||||
};
|
||||
|
||||
/* RX side state of RTP connection/socket */
|
||||
struct rtpsim_connection_rx {
|
||||
bool enabled;
|
||||
/* receive buffer for incoming messages */
|
||||
uint8_t *buf;
|
||||
/* used length of buffer */
|
||||
size_t buf_len;
|
||||
};
|
||||
|
||||
struct rtpsim_instance;
|
||||
|
||||
/* One RTP connection/socket */
|
||||
struct rtpsim_connection {
|
||||
/* index in rtp_instance.connections */
|
||||
unsigned int idx;
|
||||
/* back-pointer */
|
||||
struct rtpsim_instance *inst;
|
||||
|
||||
struct rtpsim_connection_cfg cfg;
|
||||
struct rtpsim_connection_tx tx;
|
||||
struct rtpsim_connection_rx rx;
|
||||
struct rate_ctr_group *ctrg;
|
||||
|
||||
/* socket file descriptor */
|
||||
int fd;
|
||||
char *cname;
|
||||
};
|
||||
|
||||
struct rtpsim_instance_cfg {
|
||||
int num;
|
||||
uint16_t base_port;
|
||||
unsigned int num_flows;
|
||||
};
|
||||
|
||||
/* one instance of the RTP simulator; typically one per worker thread */
|
||||
struct rtpsim_instance {
|
||||
/* element in application global list of instances */
|
||||
struct llist_head list;
|
||||
struct rtpsim_instance_cfg cfg;
|
||||
/* per-instance io_uring */
|
||||
struct io_uring ring;
|
||||
/* per-instance timerfd */
|
||||
int timerfd;
|
||||
/* counter group of per-instance counters */
|
||||
struct rate_ctr_group *ctrg;
|
||||
|
||||
struct rtpsim_connection **connections;
|
||||
/* size of 'connections' in number of pointers */
|
||||
unsigned int connections_size;
|
||||
};
|
||||
|
||||
struct rtpsim_global {
|
||||
/* global list of instances */
|
||||
struct llist_head instances;
|
||||
pthread_rwlock_t rwlock;
|
||||
|
||||
struct ctrl_handle *ctrl;
|
||||
};
|
||||
|
||||
|
||||
enum {
|
||||
DMAIN,
|
||||
};
|
||||
|
||||
enum codec_type;
|
||||
|
||||
extern struct rtpsim_global *g_rtpsim;
|
||||
|
||||
struct rtpsim_connection *rtpsim_conn_find(struct rtpsim_instance *ri, const char *cname);
|
||||
struct rtpsim_connection *rtpsim_conn_reserve(struct rtpsim_instance *ri, const char *cname, enum codec_type codec);
|
||||
int rtpsim_conn_start(struct rtpsim_connection *rtpc);
|
||||
void rtpsim_conn_stop(struct rtpsim_connection *rtpc);
|
||||
void rtpsim_conn_unreserve(struct rtpsim_connection *rtpc);
|
||||
int rtpsim_conn_connect(struct rtpsim_connection *rtpc);
|
||||
|
||||
int rtpsource_ctrl_cmds_install(void);
|
560
contrib/rtp-load-gen/rtp-load-gen.c
Normal file
560
contrib/rtp-load-gen/rtp-load-gen.c
Normal file
@@ -0,0 +1,560 @@
|
||||
#include <liburing.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/timerfd.h>
|
||||
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/core/rate_ctr.h>
|
||||
#include <osmocom/core/application.h>
|
||||
#include <osmocom/core/stats.h>
|
||||
#include <osmocom/netif/rtp.h>
|
||||
|
||||
#include "internal.h"
|
||||
#include "rtp_provider.h"
|
||||
#include "internal.h"
|
||||
|
||||
/* use a separate rx-completion thread: submit from main, reap from completion */
|
||||
//#define USE_CQ_THREAD
|
||||
|
||||
/* use registered files: Doesn't seem to work with sockets? */
|
||||
//#define USE_REGISTERED_FILES
|
||||
|
||||
/* use registered buffers (mapped once into kernel, rather than at every write */
|
||||
#define USE_REGISTERED_BUFFERS
|
||||
|
||||
/* number of sockets/flows to create */
|
||||
#define NUM_FLOWS 4096
|
||||
|
||||
/* number of workers to spawn. Each worker will get an equal share of NR_FLOWS to work on */
|
||||
#define NR_WORKERS 4
|
||||
|
||||
/* size of rx/tx buffer for one RTP frame */
|
||||
#define BUF_SIZE 256
|
||||
|
||||
#define NUM_FLOWS_PER_WORKER (NUM_FLOWS/NR_WORKERS)
|
||||
|
||||
#define TX_BUF_IDX 0
|
||||
#define RX_BUF_IDX 1
|
||||
|
||||
struct rtpsim_global *g_rtpsim;
|
||||
|
||||
enum rtpsim_conn_ctr {
|
||||
RTP_CONN_CTR_TX_PKTS,
|
||||
RTP_CONN_CTR_TX_BYTES,
|
||||
RTP_CONN_CTR_RX_PKTS,
|
||||
RTP_CONN_CTR_RX_BYTES,
|
||||
RTP_CONN_CTR_RX_INVALID,
|
||||
};
|
||||
|
||||
static const struct rate_ctr_desc rtpsim_conn_ctrs[] = {
|
||||
[RTP_CONN_CTR_TX_PKTS] = { "tx_pkts:total", "Transmitted packets" },
|
||||
[RTP_CONN_CTR_TX_BYTES] = { "tx_bytes:total", "Transmitted bytes" },
|
||||
[RTP_CONN_CTR_RX_PKTS] = { "rx_pkts:total", "Received packets (total)" },
|
||||
[RTP_CONN_CTR_RX_BYTES] = { "rx_bytes:total", "Transmitted bytes" },
|
||||
[RTP_CONN_CTR_RX_INVALID] = { "rx_pkts:invalid", "Received packets (invalidl)" },
|
||||
};
|
||||
static const struct rate_ctr_group_desc rtpsim_conn_ctrg_desc = {
|
||||
.group_name_prefix = "rtpsim_conn",
|
||||
.group_description = "RTP Simulator Connection",
|
||||
.class_id = 0,
|
||||
.num_ctr = ARRAY_SIZE(rtpsim_conn_ctrs),
|
||||
.ctr_desc = rtpsim_conn_ctrs,
|
||||
};
|
||||
|
||||
enum rtpsim_ctr {
|
||||
RTP_INST_TIMERS_TOTAL,
|
||||
RTP_INST_TIMERS_LATE,
|
||||
};
|
||||
static const struct rate_ctr_desc rtpsim_ctrs[] = {
|
||||
[RTP_INST_TIMERS_TOTAL] = { "timers:total", "Timers expiring (total)" },
|
||||
[RTP_INST_TIMERS_LATE] = { "timers:late", "Timers expiring (late)" },
|
||||
};
|
||||
static const struct rate_ctr_group_desc rtpsim_ctrg_desc = {
|
||||
.group_name_prefix = "rtpsim",
|
||||
.group_description = "RTP Simulator Instance",
|
||||
.class_id = 0,
|
||||
.num_ctr = ARRAY_SIZE(rtpsim_ctrs),
|
||||
.ctr_desc = rtpsim_ctrs,
|
||||
};
|
||||
|
||||
struct rtpsim_instance *rtpsim_instance_init(void *ctx, const struct rtpsim_instance_cfg *rmp)
|
||||
{
|
||||
struct rtpsim_instance *ri = talloc_zero(ctx, struct rtpsim_instance);
|
||||
int rc;
|
||||
|
||||
if (!ri)
|
||||
return NULL;
|
||||
ri->connections_size = NUM_FLOWS_PER_WORKER;
|
||||
ri->connections = talloc_zero_size(ri, sizeof(struct rtpsim_connection *)*ri->connections_size);
|
||||
if (!ri->connections) {
|
||||
talloc_free(ri);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ri->cfg = *rmp;
|
||||
rc = io_uring_queue_init(NUM_FLOWS_PER_WORKER*2, &ri->ring, 0);
|
||||
if (rc < 0) {
|
||||
talloc_free(ri);
|
||||
return NULL;
|
||||
}
|
||||
ri->ctrg = rate_ctr_group_alloc(ri, &rtpsim_ctrg_desc, rmp->num);
|
||||
OSMO_ASSERT(ri->ctrg);
|
||||
return ri;
|
||||
}
|
||||
|
||||
static int rtpsim_instance_conn_add(struct rtpsim_instance *ri, struct rtpsim_connection *rtpc)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ri->connections_size; i++) {
|
||||
if (ri->connections[i] == NULL) {
|
||||
ri->connections[i] = rtpc;
|
||||
rtpc->idx = i;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
static struct rtpsim_connection *
|
||||
rtpsim_conn_open_bind(struct rtpsim_instance *ri, const struct rtpsim_connection_cfg *rcfg)
|
||||
{
|
||||
struct rtpsim_connection *rtpc = talloc_zero(ri, struct rtpsim_connection);
|
||||
struct osmo_sockaddr sa_local;
|
||||
int rc;
|
||||
|
||||
if (!rtpc)
|
||||
return NULL;
|
||||
|
||||
rtpc->inst = ri;
|
||||
rtpc->cfg = *rcfg;
|
||||
|
||||
osmo_sockaddr_str_to_sockaddr(&rtpc->cfg.local, &sa_local.u.sas);
|
||||
|
||||
rc = osmo_sock_init_osa(SOCK_DGRAM, IPPROTO_UDP, &sa_local, NULL, OSMO_SOCK_F_BIND);
|
||||
if (rc < 0) {
|
||||
talloc_free(rtpc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rtpc->fd = rc;
|
||||
rtpc->ctrg = rate_ctr_group_alloc(rtpc, &rtpsim_conn_ctrg_desc, rtpc->cfg.local.port);
|
||||
OSMO_ASSERT(rtpc->ctrg);
|
||||
#ifndef USE_REGISTERED_BUFFERS
|
||||
rtpc->tx.buf = talloc_zero_size(rtpc, BUF_SIZE);
|
||||
rtpc->rx.buf = talloc_zero_size(rtpc, BUF_SIZE);
|
||||
#endif
|
||||
OSMO_ASSERT(rtpsim_instance_conn_add(ri, rtpc) >= 0);
|
||||
|
||||
return rtpc;
|
||||
}
|
||||
|
||||
/* find a connection for given cname (may be NULL to find unused connection) */
|
||||
struct rtpsim_connection *rtpsim_conn_find(struct rtpsim_instance *ri, const char *cname)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ri->connections_size; i++) {
|
||||
struct rtpsim_connection *rtpc = ri->connections[i];
|
||||
if (!rtpc)
|
||||
continue;
|
||||
if (!rtpc->cname) {
|
||||
if (!cname)
|
||||
return rtpc;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(rtpc->cname, cname))
|
||||
return rtpc;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* reserve a connection; associates cname with it */
|
||||
struct rtpsim_connection *rtpsim_conn_reserve(struct rtpsim_instance *ri, const char *cname,
|
||||
enum codec_type codec)
|
||||
{
|
||||
struct rtpsim_connection *rtpc;
|
||||
const struct rtp_provider *rtp_prov;
|
||||
|
||||
rtp_prov = rtp_provider_find("static"); // TODO: configurable */
|
||||
OSMO_ASSERT(rtp_prov);
|
||||
|
||||
rtpc = rtpsim_conn_find(ri, NULL);
|
||||
if (!rtpc)
|
||||
return NULL;
|
||||
|
||||
/* this is called from main thread, we cannot use per-thread talloc contexts
|
||||
* such as ri or rtpc */
|
||||
rtpc->cname = talloc_strdup(g_rtpsim, cname);
|
||||
rtpc->tx.rtp_prov_inst = rtp_provider_instance_alloc(g_rtpsim, rtp_prov, codec);
|
||||
OSMO_ASSERT(rtpc->tx.rtp_prov_inst);
|
||||
|
||||
/* re-start from zero transmit sequence number */
|
||||
rtpc->tx.seq = 0;
|
||||
|
||||
return rtpc;
|
||||
}
|
||||
|
||||
int rtpsim_conn_start(struct rtpsim_connection *rtpc)
|
||||
{
|
||||
rtpc->tx.enabled = true;
|
||||
rtpc->rx.enabled = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rtpsim_conn_stop(struct rtpsim_connection *rtpc)
|
||||
{
|
||||
/* disable Rx and Tx */
|
||||
rtpc->tx.enabled = false;
|
||||
rtpc->rx.enabled = false;
|
||||
}
|
||||
|
||||
/* unreserve a connection; stops all rx/tx and removes cname */
|
||||
void rtpsim_conn_unreserve(struct rtpsim_connection *rtpc)
|
||||
{
|
||||
rtp_provider_instance_free(rtpc->tx.rtp_prov_inst);
|
||||
rtpc->tx.rtp_prov_inst = NULL;
|
||||
|
||||
talloc_free(rtpc->cname);
|
||||
rtpc->cname = NULL;
|
||||
}
|
||||
|
||||
/* connect a RTP connection to its remote peer (as in rtpc->cfg.remote) */
|
||||
int rtpsim_conn_connect(struct rtpsim_connection *rtpc)
|
||||
{
|
||||
struct osmo_sockaddr sa_remote;
|
||||
int rc;
|
||||
|
||||
osmo_sockaddr_str_to_sockaddr(&rtpc->cfg.remote, &sa_remote.u.sas);
|
||||
rc = connect(rtpc->fd, &sa_remote.u.sa, sizeof(struct osmo_sockaddr));
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* transmit one RTP frame for given connection */
|
||||
static int rtpsim_conn_tx_frame(struct rtpsim_connection *rtpc)
|
||||
{
|
||||
struct rtp_hdr *rtph = (struct rtp_hdr *) rtpc->tx.buf;
|
||||
struct io_uring_sqe *sqe;
|
||||
uint8_t *payload;
|
||||
int rc;
|
||||
|
||||
rtph->version = RTP_VERSION;
|
||||
rtph->padding = 0;
|
||||
rtph->extension = 0;
|
||||
rtph->csrc_count = 0;
|
||||
rtph->marker = 0;
|
||||
rtph->payload_type = rtpc->cfg.pt;
|
||||
rtph->sequence = htons(rtpc->tx.seq++);
|
||||
rtph->timestamp = htonl(rtpc->tx.timestamp);
|
||||
rtpc->tx.timestamp += rtpc->cfg.duration;
|
||||
rtph->ssrc = htonl(rtpc->cfg.ssrc);
|
||||
payload = rtpc->tx.buf + sizeof(*rtph);
|
||||
/* add payload data */
|
||||
|
||||
rc = rtp_provider_instance_gen_frame(rtpc->tx.rtp_prov_inst, payload, BUF_SIZE-sizeof(*rtph));
|
||||
OSMO_ASSERT(rc >= 0);
|
||||
rtpc->tx.buf_len = sizeof(*rtph) + rc;
|
||||
|
||||
sqe = io_uring_get_sqe(&rtpc->inst->ring);
|
||||
OSMO_ASSERT(sqe);
|
||||
sqe->user_data = rtpc->idx;
|
||||
#ifdef USE_REGISTERED_FILES
|
||||
io_uring_sqe_set_flags(sqe, IOSQE_FIXED_FILE);
|
||||
#ifdef USE_REGISTERED_BUFFERS
|
||||
io_uring_prep_write_fixed(sqe, rtpc->idx, rtpc->tx.buf, rtpc->tx.buf_len, 0, TX_BUF_IDX);
|
||||
#else
|
||||
io_uring_prep_write(sqe, rtpc->idx, rtpc->tx.buf, rtpc->tx.buf_len, 0);
|
||||
#endif
|
||||
#else /* REGISTERED FILES */
|
||||
#ifdef USE_REGISTERED_BUFFERS
|
||||
io_uring_prep_write_fixed(sqe, rtpc->fd, rtpc->tx.buf, rtpc->tx.buf_len, 0, TX_BUF_IDX);
|
||||
#else
|
||||
io_uring_prep_write(sqe, rtpc->fd, rtpc->tx.buf, rtpc->tx.buf_len, 0);
|
||||
#endif
|
||||
#endif /* REGISTERED_FILES */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* submit RX buffer for a RTP frame on given connection */
|
||||
static int rtpsim_conn_rx_prep(struct rtpsim_connection *rtpc)
|
||||
{
|
||||
struct io_uring_sqe *sqe;
|
||||
|
||||
sqe = io_uring_get_sqe(&rtpc->inst->ring);
|
||||
OSMO_ASSERT(sqe);
|
||||
sqe->user_data = 0x8000000 | rtpc->idx;
|
||||
#ifdef USE_REGISTERED_FILES
|
||||
io_uring_sqe_set_flags(sqe, IOSQE_FIXED_FILE);
|
||||
/* FIXME */
|
||||
#else /* REGISTERED FILES */
|
||||
#ifdef USE_REGISTERED_BUFFERS
|
||||
io_uring_prep_read_fixed(sqe, rtpc->fd, rtpc->rx.buf, BUF_SIZE, 0, RX_BUF_IDX);
|
||||
#else
|
||||
io_uring_prep_read(sqe, rtpc->fd, rtpc->rx.buf, BUF_SIZE, 0);
|
||||
#endif
|
||||
#endif /* REGISTERED_FILES */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* process one completion entry */
|
||||
static void handle_completion(struct rtpsim_instance *ri, struct io_uring_cqe *cqe)
|
||||
{
|
||||
struct rtpsim_connection *rtpc = ri->connections[cqe->user_data & 0x7fffffff];
|
||||
OSMO_ASSERT(rtpc);
|
||||
|
||||
if (cqe->user_data & 0x80000000) {
|
||||
/* read */
|
||||
rate_ctr_inc(&rtpc->ctrg->ctr[RTP_CONN_CTR_RX_PKTS]);
|
||||
rate_ctr_add(&rtpc->ctrg->ctr[RTP_CONN_CTR_RX_BYTES], cqe->res);
|
||||
OSMO_ASSERT(cqe->res >= 0);
|
||||
} else {
|
||||
/* write */
|
||||
rate_ctr_inc(&rtpc->ctrg->ctr[RTP_CONN_CTR_TX_PKTS]);
|
||||
rate_ctr_add(&rtpc->ctrg->ctr[RTP_CONN_CTR_TX_BYTES], cqe->res);
|
||||
OSMO_ASSERT(cqe->res == sizeof(struct rtp_hdr) + 33);
|
||||
}
|
||||
io_uring_cqe_seen(&ri->ring, cqe);
|
||||
}
|
||||
|
||||
#ifdef USE_CQ_THREAD
|
||||
/* 'main' function for separate completion queue reaping thread */
|
||||
static void *reap_completion(void *_ri)
|
||||
{
|
||||
struct rtpsim_instance *ri = _ri;
|
||||
while (1) {
|
||||
struct io_uring_cqe *cqe;
|
||||
int rc;
|
||||
|
||||
rc = io_uring_wait_cqe(&ri->ring, &cqe);
|
||||
OSMO_ASSERT(rc >= 0);
|
||||
handle_completion(ri, cqe);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
extern int osmo_ctx_init(const char *id);
|
||||
|
||||
static void rtpsim_main(const struct rtpsim_instance_cfg *rmp)
|
||||
{
|
||||
struct rtpsim_instance *ri;
|
||||
struct rtpsim_connection *rtpc;
|
||||
int i, rc;
|
||||
|
||||
char namebuf[32];
|
||||
snprintf(namebuf, sizeof(namebuf), "rtpsim_worker%d", rmp->num);
|
||||
osmo_ctx_init(namebuf);
|
||||
|
||||
ri = rtpsim_instance_init(OTC_GLOBAL, rmp);
|
||||
OSMO_ASSERT(ri);
|
||||
|
||||
pthread_rwlock_wrlock(&g_rtpsim->rwlock);
|
||||
llist_add_tail(&ri->list, &g_rtpsim->instances);
|
||||
pthread_rwlock_unlock(&g_rtpsim->rwlock);
|
||||
|
||||
/* create desired number of sockets */
|
||||
printf("binding sockets\n");
|
||||
for (i = 0; i < rmp->num_flows; i++) {
|
||||
struct rtpsim_connection *rtpc;
|
||||
struct rtpsim_connection_cfg rcfg = {};
|
||||
rcfg.local = (struct osmo_sockaddr_str) {
|
||||
.af = AF_INET,
|
||||
.ip = "127.0.0.1",
|
||||
.port = rmp->base_port + 2*i,
|
||||
};
|
||||
rcfg.remote = (struct osmo_sockaddr_str) {
|
||||
.af = AF_INET,
|
||||
.ip = "127.0.0.1",
|
||||
.port = rmp->base_port + 2*i,
|
||||
};
|
||||
rcfg.pt = 3;
|
||||
rcfg.ssrc = 0x80000000 + rmp->base_port + i;
|
||||
rcfg.duration = 160; /* 8000 Hz sampling rate / 50 Hz RTP rate */
|
||||
|
||||
rtpc = rtpsim_conn_open_bind(ri, &rcfg);
|
||||
OSMO_ASSERT(rtpc);
|
||||
}
|
||||
|
||||
#if 1
|
||||
/* HACK */
|
||||
printf("connecting sockets\n");
|
||||
for (i = 0; i < rmp->num_flows; i++) {
|
||||
char namebuf[32];
|
||||
snprintf(namebuf, sizeof(namebuf), "conn%d", i);
|
||||
struct rtpsim_connection *rtpc = rtpsim_conn_reserve(ri, namebuf, CODEC_GSM_FR);
|
||||
OSMO_ASSERT(rtpc);
|
||||
OSMO_ASSERT(rtpsim_conn_connect(rtpc) == 0);
|
||||
OSMO_ASSERT(rtpsim_conn_start(rtpc) == 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_REGISTERED_FILES
|
||||
/* register all our file descriptors; seems to fail on 5.8.x ? */
|
||||
int fds[NUM_FLOWS_PER_WORKER];
|
||||
for (i = 0; i < ri->connections_size; i++) {
|
||||
if (!rtpc) {
|
||||
fds[i] = -1;
|
||||
continue;
|
||||
}
|
||||
rtpc = ri->connections[i];
|
||||
fds[i] = rtpc->fd;
|
||||
}
|
||||
printf("Registering %d files\n", i);
|
||||
rc = io_uring_register_files(&ri->ring, fds, i);
|
||||
printf("rc = %d: %s\n", rc, strerror(-rc));
|
||||
OSMO_ASSERT(rc == 0);
|
||||
#endif
|
||||
|
||||
#ifdef USE_REGISTERED_BUFFERS
|
||||
/* register two large buffers for Rx and Tx; assign per-connection
|
||||
* buffers within those two registered buffers */
|
||||
void *largebuf_tx = talloc_zero_size(ri, rmp->num_flows * BUF_SIZE);
|
||||
void *largebuf_rx = talloc_zero_size(ri, rmp->num_flows * BUF_SIZE);
|
||||
struct iovec iov[2] = {
|
||||
[TX_BUF_IDX] = {
|
||||
.iov_base = largebuf_tx,
|
||||
.iov_len = rmp->num_flows * BUF_SIZE,
|
||||
},
|
||||
[RX_BUF_IDX] = {
|
||||
.iov_base = largebuf_rx,
|
||||
.iov_len = rmp->num_flows * BUF_SIZE,
|
||||
},
|
||||
};
|
||||
printf("Registering buffers for %d sockets\n", i);
|
||||
rc = io_uring_register_buffers(&ri->ring, iov, ARRAY_SIZE(iov));
|
||||
printf("rc = %d: %s\n", rc, strerror(-rc));
|
||||
OSMO_ASSERT(rc == 0);
|
||||
for (i = 0; i < ri->connections_size; i++) {
|
||||
rtpc = ri->connections[i];
|
||||
if (!rtpc)
|
||||
continue;
|
||||
rtpc->tx.buf = largebuf_tx + (i * BUF_SIZE);
|
||||
rtpc->rx.buf = largebuf_rx + (i * BUF_SIZE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CQ_THREAD
|
||||
/* start a separate completion thread instead of handling completions in-line */
|
||||
pthread_t complete;
|
||||
rc = pthread_create(&complete, NULL, reap_completion, ri);
|
||||
OSMO_ASSERT(rc >= 0);
|
||||
#endif
|
||||
|
||||
/* start timerfd every 20ms */
|
||||
ri->timerfd = timerfd_create(CLOCK_MONOTONIC, 0);
|
||||
OSMO_ASSERT(ri->timerfd >= 0);
|
||||
struct itimerspec its = (struct itimerspec) {
|
||||
.it_interval = { 0, 20*1000*1000 },
|
||||
.it_value = { 0, 20*1000*1000 },
|
||||
};
|
||||
rc = timerfd_settime(ri->timerfd, 0, &its, NULL);
|
||||
OSMO_ASSERT(rc == 0);
|
||||
|
||||
/* start transmitting */
|
||||
|
||||
while (1) {
|
||||
/* the assumption here is that every flow wants to write 50
|
||||
* packets per second, so we try try to submit one write for each
|
||||
* flow every 20ms */
|
||||
unsigned int submit_granularity = rmp->num_flows/50;
|
||||
/* number of 20ms timer expirations */
|
||||
uint64_t num_exp;
|
||||
unsigned int t;
|
||||
|
||||
if (submit_granularity <= 0)
|
||||
submit_granularity = 1;
|
||||
|
||||
/* read from timerfd to pace the 20ms inter packet interval */
|
||||
rc = read(ri->timerfd, &num_exp, sizeof(num_exp));
|
||||
OSMO_ASSERT(rc == sizeof(num_exp));
|
||||
|
||||
rate_ctr_add(&ri->ctrg->ctr[RTP_INST_TIMERS_TOTAL], num_exp);
|
||||
if (num_exp != 1) {
|
||||
fputc('X', stdout);
|
||||
rate_ctr_add(&ri->ctrg->ctr[RTP_INST_TIMERS_LATE], num_exp-1);
|
||||
} else {
|
||||
fputc('.', stdout);
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
for (t = 0; t < num_exp; t++) {
|
||||
for (i = 0; i < ri->connections_size; i++) {
|
||||
rtpc = ri->connections[i];
|
||||
if (!rtpc)
|
||||
continue;
|
||||
if (rtpc->tx.enabled)
|
||||
rtpsim_conn_tx_frame(rtpc);
|
||||
if (rtpc->rx.enabled)
|
||||
rtpsim_conn_rx_prep(rtpc);
|
||||
if ((i % submit_granularity) == 0) {
|
||||
int pending = io_uring_submit(&ri->ring);
|
||||
|
||||
#ifndef USE_CQ_THREAD
|
||||
for (int j = 0; j < pending; j++) {
|
||||
struct io_uring_cqe *cqe;
|
||||
int rc;
|
||||
|
||||
rc = io_uring_wait_cqe(&ri->ring, &cqe);
|
||||
OSMO_ASSERT(rc >= 0);
|
||||
handle_completion(ri, cqe);
|
||||
}
|
||||
#endif /* USE_CQ_THREAD */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void *rtpsim_worker_thread(void *_rmp)
|
||||
{
|
||||
rtpsim_main((struct rtpsim_instance_cfg *)_rmp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
pthread_t worker[NR_WORKERS];
|
||||
struct rtpsim_instance_cfg rmp[NR_WORKERS];
|
||||
int i, rc;
|
||||
|
||||
g_rtpsim = talloc_zero(NULL, struct rtpsim_global);
|
||||
OSMO_ASSERT(g_rtpsim);
|
||||
INIT_LLIST_HEAD(&g_rtpsim->instances);
|
||||
pthread_rwlock_init(&g_rtpsim->rwlock, NULL);
|
||||
|
||||
osmo_init_logging2(g_rtpsim, NULL);
|
||||
// osmo_stats_init(g_rtpsim);
|
||||
|
||||
/* Create worker threads */
|
||||
for (i = 0; i < NR_WORKERS; i++) {
|
||||
int rc;
|
||||
rmp[i].num = i;
|
||||
rmp[i].num_flows = NUM_FLOWS_PER_WORKER;
|
||||
rmp[i].base_port = 10000 + i * (2 * rmp[i].num_flows);
|
||||
rc = pthread_create(&worker[i], NULL, rtpsim_worker_thread, &rmp[i]);
|
||||
OSMO_ASSERT(rc >= 0);
|
||||
}
|
||||
|
||||
/* CTRL interface */
|
||||
//g_rtpsim->ctrl = ctrl_interface_setup_dynip(g_rss, ctrl_vty_get_bind_addr(), 11111, NULL);
|
||||
g_rtpsim->ctrl = ctrl_interface_setup_dynip(g_rtpsim, "127.0.0.1", 11111, NULL);
|
||||
OSMO_ASSERT(g_rtpsim->ctrl);
|
||||
rc = rtpsource_ctrl_cmds_install();
|
||||
OSMO_ASSERT(rc == 0);
|
||||
|
||||
while (1) {
|
||||
osmo_select_main(0);
|
||||
}
|
||||
|
||||
for (i = 0; i < NR_WORKERS; i++) {
|
||||
pthread_join(worker[i], NULL);
|
||||
}
|
||||
}
|
71
contrib/rtp-load-gen/rtp_provider.c
Normal file
71
contrib/rtp-load-gen/rtp_provider.c
Normal file
@@ -0,0 +1,71 @@
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
|
||||
#include "rtp_provider.h"
|
||||
#include "internal.h"
|
||||
|
||||
|
||||
static LLIST_HEAD(g_providers);
|
||||
|
||||
const struct value_string codec_type_names[] = {
|
||||
{ CODEC_ULAW, "ULAW" },
|
||||
{ CODEC_ALAW, "ALAW" },
|
||||
{ CODEC_GSM_FR, "GSM_FR" },
|
||||
{ CODEC_GSM_EFR, "GSM_EFR" },
|
||||
{ CODEC_GSM_HR, "GSM_HR" },
|
||||
{ CODEC_AMR_4_75, "AMR_4_75" },
|
||||
{ CODEC_AMR_5_15, "AMR_5_15" },
|
||||
{ CODEC_AMR_5_90, "AMR_5_90" },
|
||||
{ CODEC_AMR_6_70, "AMR_6_70" },
|
||||
{ CODEC_AMR_7_40, "AMR_7_40" },
|
||||
{ CODEC_AMR_7_95, "AMR_7_95" },
|
||||
{ CODEC_AMR_10_2, "AMR_10_2" },
|
||||
{ CODEC_AMR_12_2, "AMR_12_2" },
|
||||
{ CODEC_AMR_SID, "AMR_SID" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
void rtp_provider_register(struct rtp_provider *prov)
|
||||
{
|
||||
llist_add_tail(&prov->list, &g_providers);
|
||||
}
|
||||
|
||||
const struct rtp_provider *rtp_provider_find(const char *name)
|
||||
{
|
||||
struct rtp_provider *p;
|
||||
llist_for_each_entry(p, &g_providers, list) {
|
||||
if (!strcmp(name, p->name))
|
||||
return p;
|
||||
}
|
||||
LOGP(DMAIN, LOGL_ERROR, "Couldn't find RTP provider '%s'\n", name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct rtp_provider_instance *
|
||||
rtp_provider_instance_alloc(void *ctx, const struct rtp_provider *provider, enum codec_type codec)
|
||||
{
|
||||
struct rtp_provider_instance *pi;
|
||||
|
||||
pi = talloc_zero(ctx, struct rtp_provider_instance);
|
||||
if (!pi)
|
||||
return NULL;
|
||||
|
||||
pi->provider = provider;
|
||||
pi->codec = codec;
|
||||
|
||||
return pi;
|
||||
}
|
||||
|
||||
void rtp_provider_instance_free(struct rtp_provider_instance *pi)
|
||||
{
|
||||
llist_del(&pi->list);
|
||||
talloc_free(pi);
|
||||
}
|
||||
|
||||
int rtp_provider_instance_gen_frame(struct rtp_provider_instance *pi, uint8_t *out, size_t out_size)
|
||||
{
|
||||
OSMO_ASSERT(pi->provider);
|
||||
return pi->provider->rtp_gen(pi, out, out_size);
|
||||
}
|
57
contrib/rtp-load-gen/rtp_provider.h
Normal file
57
contrib/rtp-load-gen/rtp_provider.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
enum codec_type {
|
||||
CODEC_ULAW,
|
||||
CODEC_ALAW,
|
||||
CODEC_GSM_FR,
|
||||
CODEC_GSM_EFR,
|
||||
CODEC_GSM_HR,
|
||||
CODEC_AMR_4_75,
|
||||
CODEC_AMR_5_15,
|
||||
CODEC_AMR_5_90,
|
||||
CODEC_AMR_6_70,
|
||||
CODEC_AMR_7_40,
|
||||
CODEC_AMR_7_95,
|
||||
CODEC_AMR_10_2,
|
||||
CODEC_AMR_12_2,
|
||||
CODEC_AMR_SID,
|
||||
_NUM_CODECS
|
||||
};
|
||||
|
||||
extern const struct value_string codec_type_names[];
|
||||
|
||||
struct rtp_provider_instance;
|
||||
|
||||
struct rtp_provider {
|
||||
/* global list of RTP providers */
|
||||
struct llist_head list;
|
||||
const char *name;
|
||||
|
||||
/* create/initialie a RTP provider with specified argument string */
|
||||
int (*setup)(struct rtp_provider_instance *inst, const char *arg);
|
||||
|
||||
/* generate the next RTP packet; return length in octests or negative on error */
|
||||
int (*rtp_gen)(struct rtp_provider_instance *inst, uint8_t *out, size_t out_size);
|
||||
};
|
||||
|
||||
struct rtp_provider_instance {
|
||||
/* entry in global list of RTP provider instances */
|
||||
struct llist_head list;
|
||||
/* pointer to provider of which we are an instance */
|
||||
const struct rtp_provider *provider;
|
||||
/* codec payload we are to generate */
|
||||
enum codec_type codec;
|
||||
|
||||
/* private user data */
|
||||
void *priv;
|
||||
};
|
||||
|
||||
void rtp_provider_register(struct rtp_provider *prov);
|
||||
const struct rtp_provider *rtp_provider_find(const char *name);
|
||||
|
||||
struct rtp_provider_instance *rtp_provider_instance_alloc(void *ctx, const struct rtp_provider *provider, enum codec_type codec);
|
||||
void rtp_provider_instance_free(struct rtp_provider_instance *pi);
|
||||
int rtp_provider_instance_gen_frame(struct rtp_provider_instance *pi, uint8_t *out, size_t out_size);
|
108
contrib/rtp-load-gen/rtp_provider_static.c
Normal file
108
contrib/rtp-load-gen/rtp_provider_static.c
Normal file
@@ -0,0 +1,108 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <osmocom/codec/codec.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
|
||||
#include "rtp_provider.h"
|
||||
#include "internal.h"
|
||||
|
||||
static struct rtp_provider static_provider;
|
||||
|
||||
static const uint8_t len4codec[_NUM_CODECS] = {
|
||||
[CODEC_ULAW] = 160,
|
||||
[CODEC_ALAW] = 160,
|
||||
[CODEC_GSM_FR] = GSM_FR_BYTES,
|
||||
[CODEC_GSM_EFR] = GSM_EFR_BYTES,
|
||||
[CODEC_GSM_HR] = GSM_HR_BYTES,
|
||||
[CODEC_AMR_4_75] = 12,
|
||||
[CODEC_AMR_5_15] = 13,
|
||||
[CODEC_AMR_5_90] = 15,
|
||||
[CODEC_AMR_6_70] = 17,
|
||||
[CODEC_AMR_7_40] = 19,
|
||||
[CODEC_AMR_7_95] = 20,
|
||||
[CODEC_AMR_10_2] = 26,
|
||||
[CODEC_AMR_12_2] = 31,
|
||||
[CODEC_AMR_SID] = 5,
|
||||
};
|
||||
|
||||
/* generate a static / fixed RTP payload of matching codec/mode */
|
||||
static int rtp_gen_static(struct rtp_provider_instance *pi, uint8_t *out, size_t out_size)
|
||||
{
|
||||
uint8_t len;
|
||||
|
||||
OSMO_ASSERT(pi->provider == &static_provider);
|
||||
|
||||
len = len4codec[pi->codec];
|
||||
if (out_size < len) {
|
||||
LOGP(DMAIN, LOGL_ERROR, "out_size %zu < %u\n", out_size, len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
memset(out, 0, len);
|
||||
|
||||
switch (pi->codec) {
|
||||
case CODEC_ULAW:
|
||||
case CODEC_ALAW:
|
||||
break;
|
||||
case CODEC_GSM_FR:
|
||||
out[0] = (out[0] & 0x0f) | 0xD0; /* mask in first four bit for FR */
|
||||
break;
|
||||
case CODEC_GSM_EFR:
|
||||
out[0] = (out[0] & 0x0f) | 0xC0; /* mask in first four bit for EFR */
|
||||
break;
|
||||
case CODEC_GSM_HR:
|
||||
break;
|
||||
case CODEC_AMR_4_75:
|
||||
out[0] = 0 << 4;
|
||||
out[1] = 0 << 3;
|
||||
break;
|
||||
case CODEC_AMR_5_15:
|
||||
out[0] = 1 << 4;
|
||||
out[1] = 1 << 3;
|
||||
break;
|
||||
case CODEC_AMR_5_90:
|
||||
out[0] = 2 << 4;
|
||||
out[1] = 2 << 3;
|
||||
break;
|
||||
case CODEC_AMR_6_70:
|
||||
out[0] = 3 << 4;
|
||||
out[1] = 3 << 3;
|
||||
break;
|
||||
case CODEC_AMR_7_40:
|
||||
out[0] = 4 << 4;
|
||||
out[1] = 4 << 3;
|
||||
break;
|
||||
case CODEC_AMR_7_95:
|
||||
out[0] = 5 << 4;
|
||||
out[1] = 5 << 3;
|
||||
break;
|
||||
case CODEC_AMR_10_2:
|
||||
out[0] = 6 << 4;
|
||||
out[1] = 6 << 3;
|
||||
break;
|
||||
case CODEC_AMR_12_2:
|
||||
out[0] = 7 << 4;
|
||||
out[1] = 7 << 3;
|
||||
break;
|
||||
case CODEC_AMR_SID:
|
||||
out[0] = 2 << 4; /* CMR: 5.90 */
|
||||
out[0] = 8 << 3;
|
||||
break;
|
||||
default:
|
||||
OSMO_ASSERT(0);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
static struct rtp_provider static_provider = {
|
||||
.name = "static",
|
||||
.rtp_gen = &rtp_gen_static,
|
||||
};
|
||||
|
||||
static void __attribute__((constructor)) rtp_provider_static_constr(void)
|
||||
{
|
||||
rtp_provider_register(&static_provider);
|
||||
}
|
11
contrib/simcom2rtp/Makefile
Normal file
11
contrib/simcom2rtp/Makefile
Normal file
@@ -0,0 +1,11 @@
|
||||
CFLAGS:= -O2 -g -Wall $(shell pkg-config --cflags libosmocore libosmotrau)
|
||||
LIBS:= $(shell pkg-config --libs libosmocore libosmotrau)
|
||||
|
||||
all: osmo-simcom2rtp
|
||||
|
||||
osmo-simcom2rtp: g711.o g711_table.o simcom2rtp.o
|
||||
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -o $@ -c $^
|
||||
|
313
contrib/simcom2rtp/g711.c
Normal file
313
contrib/simcom2rtp/g711.c
Normal file
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* This source code is a product of Sun Microsystems, Inc. and is provided
|
||||
* for unrestricted use. Users may copy or modify this source code without
|
||||
* charge.
|
||||
*
|
||||
* SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING
|
||||
* THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
|
||||
*
|
||||
* Sun source code is provided with no support and without any obligation on
|
||||
* the part of Sun Microsystems, Inc. to assist in its use, correction,
|
||||
* modification or enhancement.
|
||||
*
|
||||
* SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
|
||||
* INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE
|
||||
* OR ANY PART THEREOF.
|
||||
*
|
||||
* In no event will Sun Microsystems, Inc. be liable for any lost revenue
|
||||
* or profits or other special, indirect and consequential damages, even if
|
||||
* Sun has been advised of the possibility of such damages.
|
||||
*
|
||||
* Sun Microsystems, Inc.
|
||||
* 2550 Garcia Avenue
|
||||
* Mountain View, California 94043
|
||||
*/
|
||||
/*
|
||||
* December 30, 1994:
|
||||
* Functions linear2alaw, linear2ulaw have been updated to correctly
|
||||
* convert unquantized 16 bit values.
|
||||
* Tables for direct u- to A-law and A- to u-law conversions have been
|
||||
* corrected.
|
||||
* Borge Lindberg, Center for PersonKommunikation, Aalborg University.
|
||||
* bli@cpk.auc.dk
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* Downloaded from comp.speech site in Cambridge.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "g711.h"
|
||||
|
||||
/*
|
||||
* g711.c
|
||||
*
|
||||
* u-law, A-law and linear PCM conversions.
|
||||
* Source: http://www.speech.kth.se/cost250/refsys/latest/src/g711.c
|
||||
*/
|
||||
#define SIGN_BIT (0x80) /* Sign bit for a A-law byte. */
|
||||
#define QUANT_MASK (0xf) /* Quantization field mask. */
|
||||
#define NSEGS (8) /* Number of A-law segments. */
|
||||
#define SEG_SHIFT (4) /* Left shift for segment number. */
|
||||
#define SEG_MASK (0x70) /* Segment field mask. */
|
||||
|
||||
static short seg_aend[8] = {0x1F, 0x3F, 0x7F, 0xFF,
|
||||
0x1FF, 0x3FF, 0x7FF, 0xFFF};
|
||||
static short seg_uend[8] = {0x3F, 0x7F, 0xFF, 0x1FF,
|
||||
0x3FF, 0x7FF, 0xFFF, 0x1FFF};
|
||||
|
||||
/* copy from CCITT G.711 specifications */
|
||||
unsigned char _u2a[128] = { /* u- to A-law conversions */
|
||||
1, 1, 2, 2, 3, 3, 4, 4,
|
||||
5, 5, 6, 6, 7, 7, 8, 8,
|
||||
9, 10, 11, 12, 13, 14, 15, 16,
|
||||
17, 18, 19, 20, 21, 22, 23, 24,
|
||||
25, 27, 29, 31, 33, 34, 35, 36,
|
||||
37, 38, 39, 40, 41, 42, 43, 44,
|
||||
46, 48, 49, 50, 51, 52, 53, 54,
|
||||
55, 56, 57, 58, 59, 60, 61, 62,
|
||||
64, 65, 66, 67, 68, 69, 70, 71,
|
||||
72, 73, 74, 75, 76, 77, 78, 79,
|
||||
/* corrected:
|
||||
81, 82, 83, 84, 85, 86, 87, 88,
|
||||
should be: */
|
||||
80, 82, 83, 84, 85, 86, 87, 88,
|
||||
89, 90, 91, 92, 93, 94, 95, 96,
|
||||
97, 98, 99, 100, 101, 102, 103, 104,
|
||||
105, 106, 107, 108, 109, 110, 111, 112,
|
||||
113, 114, 115, 116, 117, 118, 119, 120,
|
||||
121, 122, 123, 124, 125, 126, 127, 128};
|
||||
|
||||
unsigned char _a2u[128] = { /* A- to u-law conversions */
|
||||
1, 3, 5, 7, 9, 11, 13, 15,
|
||||
16, 17, 18, 19, 20, 21, 22, 23,
|
||||
24, 25, 26, 27, 28, 29, 30, 31,
|
||||
32, 32, 33, 33, 34, 34, 35, 35,
|
||||
36, 37, 38, 39, 40, 41, 42, 43,
|
||||
44, 45, 46, 47, 48, 48, 49, 49,
|
||||
50, 51, 52, 53, 54, 55, 56, 57,
|
||||
58, 59, 60, 61, 62, 63, 64, 64,
|
||||
65, 66, 67, 68, 69, 70, 71, 72,
|
||||
/* corrected:
|
||||
73, 74, 75, 76, 77, 78, 79, 79,
|
||||
should be: */
|
||||
73, 74, 75, 76, 77, 78, 79, 80,
|
||||
80, 81, 82, 83, 84, 85, 86, 87,
|
||||
88, 89, 90, 91, 92, 93, 94, 95,
|
||||
96, 97, 98, 99, 100, 101, 102, 103,
|
||||
104, 105, 106, 107, 108, 109, 110, 111,
|
||||
112, 113, 114, 115, 116, 117, 118, 119,
|
||||
120, 121, 122, 123, 124, 125, 126, 127};
|
||||
|
||||
static short search(
|
||||
short val,
|
||||
short *table,
|
||||
short size)
|
||||
{
|
||||
short i;
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
if (val <= *table++)
|
||||
return (i);
|
||||
}
|
||||
return (size);
|
||||
}
|
||||
|
||||
/*
|
||||
* linear2alaw() - Convert a 16-bit linear PCM value to 8-bit A-law
|
||||
*
|
||||
* linear2alaw() accepts an 16-bit integer and encodes it as A-law data.
|
||||
*
|
||||
* Linear Input Code Compressed Code
|
||||
* ------------------------ ---------------
|
||||
* 0000000wxyza 000wxyz
|
||||
* 0000001wxyza 001wxyz
|
||||
* 000001wxyzab 010wxyz
|
||||
* 00001wxyzabc 011wxyz
|
||||
* 0001wxyzabcd 100wxyz
|
||||
* 001wxyzabcde 101wxyz
|
||||
* 01wxyzabcdef 110wxyz
|
||||
* 1wxyzabcdefg 111wxyz
|
||||
*
|
||||
* For further information see John C. Bellamy's Digital Telephony, 1982,
|
||||
* John Wiley & Sons, pps 98-111 and 472-476.
|
||||
*/
|
||||
unsigned char
|
||||
linear2alaw(short pcm_val) /* 2's complement (16-bit range) */
|
||||
{
|
||||
short mask;
|
||||
short seg;
|
||||
unsigned char aval;
|
||||
|
||||
pcm_val = pcm_val >> 3;
|
||||
|
||||
if (pcm_val >= 0) {
|
||||
mask = 0xD5; /* sign (7th) bit = 1 */
|
||||
} else {
|
||||
mask = 0x55; /* sign bit = 0 */
|
||||
pcm_val = -pcm_val - 1;
|
||||
}
|
||||
|
||||
/* Convert the scaled magnitude to segment number. */
|
||||
seg = search(pcm_val, seg_aend, 8);
|
||||
|
||||
/* Combine the sign, segment, and quantization bits. */
|
||||
|
||||
if (seg >= 8) /* out of range, return maximum value. */
|
||||
return (unsigned char) (0x7F ^ mask);
|
||||
else {
|
||||
aval = (unsigned char) seg << SEG_SHIFT;
|
||||
if (seg < 2)
|
||||
aval |= (pcm_val >> 1) & QUANT_MASK;
|
||||
else
|
||||
aval |= (pcm_val >> seg) & QUANT_MASK;
|
||||
return (aval ^ mask);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* alaw2linear() - Convert an A-law value to 16-bit linear PCM
|
||||
*
|
||||
*/
|
||||
short
|
||||
alaw2linear(
|
||||
unsigned char a_val)
|
||||
{
|
||||
short t;
|
||||
short seg;
|
||||
|
||||
a_val ^= 0x55;
|
||||
|
||||
t = (a_val & QUANT_MASK) << 4;
|
||||
seg = ((unsigned)a_val & SEG_MASK) >> SEG_SHIFT;
|
||||
switch (seg) {
|
||||
case 0:
|
||||
t += 8;
|
||||
break;
|
||||
case 1:
|
||||
t += 0x108;
|
||||
break;
|
||||
default:
|
||||
t += 0x108;
|
||||
t <<= seg - 1;
|
||||
}
|
||||
return ((a_val & SIGN_BIT) ? t : -t);
|
||||
}
|
||||
|
||||
#define BIAS (0x84) /* Bias for linear code. */
|
||||
#define CLIP 8159
|
||||
|
||||
/*
|
||||
* linear2ulaw() - Convert a linear PCM value to u-law
|
||||
*
|
||||
* In order to simplify the encoding process, the original linear magnitude
|
||||
* is biased by adding 33 which shifts the encoding range from (0 - 8158) to
|
||||
* (33 - 8191). The result can be seen in the following encoding table:
|
||||
*
|
||||
* Biased Linear Input Code Compressed Code
|
||||
* ------------------------ ---------------
|
||||
* 00000001wxyza 000wxyz
|
||||
* 0000001wxyzab 001wxyz
|
||||
* 000001wxyzabc 010wxyz
|
||||
* 00001wxyzabcd 011wxyz
|
||||
* 0001wxyzabcde 100wxyz
|
||||
* 001wxyzabcdef 101wxyz
|
||||
* 01wxyzabcdefg 110wxyz
|
||||
* 1wxyzabcdefgh 111wxyz
|
||||
*
|
||||
* Each biased linear code has a leading 1 which identifies the segment
|
||||
* number. The value of the segment number is equal to 7 minus the number
|
||||
* of leading 0's. The quantization interval is directly available as the
|
||||
* four bits wxyz. * The trailing bits (a - h) are ignored.
|
||||
*
|
||||
* Ordinarily the complement of the resulting code word is used for
|
||||
* transmission, and so the code word is complemented before it is returned.
|
||||
*
|
||||
* For further information see John C. Bellamy's Digital Telephony, 1982,
|
||||
* John Wiley & Sons, pps 98-111 and 472-476.
|
||||
*/
|
||||
unsigned char
|
||||
linear2ulaw(
|
||||
short pcm_val) /* 2's complement (16-bit range) */
|
||||
{
|
||||
short mask;
|
||||
short seg;
|
||||
unsigned char uval;
|
||||
|
||||
/* Get the sign and the magnitude of the value. */
|
||||
pcm_val = pcm_val >> 2;
|
||||
if (pcm_val < 0) {
|
||||
pcm_val = -pcm_val;
|
||||
mask = 0x7F;
|
||||
} else {
|
||||
mask = 0xFF;
|
||||
}
|
||||
if ( pcm_val > CLIP ) pcm_val = CLIP; /* clip the magnitude */
|
||||
pcm_val += (BIAS >> 2);
|
||||
|
||||
/* Convert the scaled magnitude to segment number. */
|
||||
seg = search(pcm_val, seg_uend, 8);
|
||||
|
||||
/*
|
||||
* Combine the sign, segment, quantization bits;
|
||||
* and complement the code word.
|
||||
*/
|
||||
if (seg >= 8) /* out of range, return maximum value. */
|
||||
return (unsigned char) (0x7F ^ mask);
|
||||
else {
|
||||
uval = (unsigned char) (seg << 4) | ((pcm_val >> (seg + 1)) & 0xF);
|
||||
return (uval ^ mask);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* ulaw2linear() - Convert a u-law value to 16-bit linear PCM
|
||||
*
|
||||
* First, a biased linear code is derived from the code word. An unbiased
|
||||
* output can then be obtained by subtracting 33 from the biased code.
|
||||
*
|
||||
* Note that this function expects to be passed the complement of the
|
||||
* original code word. This is in keeping with ISDN conventions.
|
||||
*/
|
||||
short
|
||||
ulaw2linear(
|
||||
unsigned char u_val)
|
||||
{
|
||||
short t;
|
||||
|
||||
/* Complement to obtain normal u-law value. */
|
||||
u_val = ~u_val;
|
||||
|
||||
/*
|
||||
* Extract and bias the quantization bits. Then
|
||||
* shift up by the segment number and subtract out the bias.
|
||||
*/
|
||||
t = ((u_val & QUANT_MASK) << 3) + BIAS;
|
||||
t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT;
|
||||
|
||||
return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS));
|
||||
}
|
||||
|
||||
/* A-law to u-law conversion */
|
||||
unsigned char
|
||||
alaw2ulaw(
|
||||
unsigned char aval)
|
||||
{
|
||||
aval &= 0xff;
|
||||
return (unsigned char) ((aval & 0x80) ? (0xFF ^ _a2u[aval ^ 0xD5]) :
|
||||
(0x7F ^ _a2u[aval ^ 0x55]));
|
||||
}
|
||||
|
||||
/* u-law to A-law conversion */
|
||||
unsigned char
|
||||
ulaw2alaw(
|
||||
unsigned char uval)
|
||||
{
|
||||
uval &= 0xff;
|
||||
return (unsigned char) ((uval & 0x80) ? (0xD5 ^ (_u2a[0xFF ^ uval] - 1)) :
|
||||
(0x55 ^ (_u2a[0x7F ^ uval] - 1)));
|
||||
}
|
||||
|
||||
/* ---------- end of g711.c ----------------------------------------------------- */
|
27
contrib/simcom2rtp/g711.h
Normal file
27
contrib/simcom2rtp/g711.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* g711.h
|
||||
*
|
||||
* u-law, A-law and linear PCM conversions.
|
||||
* Source: http://www.speech.kth.se/cost250/refsys/latest/src/g711.h
|
||||
*/
|
||||
|
||||
#ifndef _G711_H_
|
||||
#define _G711_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
unsigned char linear2alaw(short pcm_val);
|
||||
short alaw2linear(unsigned char a_val);
|
||||
unsigned char linear2ulaw(short pcm_val);
|
||||
short ulaw2linear(unsigned char u_val);
|
||||
unsigned char alaw2ulaw(unsigned char aval);
|
||||
unsigned char ulaw2alaw(unsigned char uval);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _G711_H_ */
|
102
contrib/simcom2rtp/g711_table.c
Normal file
102
contrib/simcom2rtp/g711_table.c
Normal file
@@ -0,0 +1,102 @@
|
||||
#ifndef G711_TABLE_H
|
||||
#define G711_TABLE_H
|
||||
|
||||
#include "g711.h"
|
||||
|
||||
/* 16384 entries per table (16 bit) */
|
||||
unsigned char linear_to_alaw[65536];
|
||||
unsigned char linear_to_ulaw[65536];
|
||||
|
||||
/* 16384 entries per table (8 bit) */
|
||||
unsigned short alaw_to_linear[256];
|
||||
unsigned short ulaw_to_linear[256];
|
||||
|
||||
static void build_linear_to_xlaw_table(unsigned char *linear_to_xlaw,
|
||||
unsigned char (*linear2xlaw)(short))
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0; i<65536;i++){
|
||||
linear_to_xlaw[i] = linear2xlaw((short) i);
|
||||
}
|
||||
}
|
||||
|
||||
static void build_xlaw_to_linear_table(unsigned short *xlaw_to_linear,
|
||||
short (*xlaw2linear)(unsigned char))
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0; i<256;i++){
|
||||
xlaw_to_linear[i] = (unsigned short) xlaw2linear(i);
|
||||
}
|
||||
}
|
||||
|
||||
static void pcm16_to_xlaw(unsigned char *linear_to_xlaw, int src_length, const char *src_samples, char *dst_samples)
|
||||
{
|
||||
int i;
|
||||
const unsigned short *s_samples;
|
||||
|
||||
s_samples = (const unsigned short *)src_samples;
|
||||
|
||||
for (i=0; i < src_length / 2; i++)
|
||||
{
|
||||
dst_samples[i] = linear_to_xlaw[s_samples[i]];
|
||||
}
|
||||
}
|
||||
|
||||
static void xlaw_to_pcm16(unsigned short *xlaw_to_linear, int src_length, const char *src_samples, char *dst_samples)
|
||||
{
|
||||
int i;
|
||||
unsigned char *s_samples;
|
||||
unsigned short *d_samples;
|
||||
|
||||
s_samples = (unsigned char *) src_samples;
|
||||
d_samples = (unsigned short *)dst_samples;
|
||||
|
||||
for (i=0; i < src_length; i++)
|
||||
{
|
||||
d_samples[i] = xlaw_to_linear[s_samples[i]];
|
||||
}
|
||||
}
|
||||
|
||||
void pcm16_to_alaw(int src_length, const char *src_samples, char *dst_samples)
|
||||
{
|
||||
pcm16_to_xlaw(linear_to_alaw, src_length, src_samples, dst_samples);
|
||||
}
|
||||
|
||||
void pcm16_to_ulaw(int src_length, const char *src_samples, char *dst_samples)
|
||||
{
|
||||
pcm16_to_xlaw(linear_to_ulaw, src_length, src_samples, dst_samples);
|
||||
}
|
||||
|
||||
void alaw_to_pcm16(int src_length, const char *src_samples, char *dst_samples)
|
||||
{
|
||||
xlaw_to_pcm16(alaw_to_linear, src_length, src_samples, dst_samples);
|
||||
}
|
||||
|
||||
void ulaw_to_pcm16(int src_length, const char *src_samples, char *dst_samples)
|
||||
{
|
||||
xlaw_to_pcm16(ulaw_to_linear, src_length, src_samples, dst_samples);
|
||||
}
|
||||
|
||||
void pcm16_alaw_tableinit()
|
||||
{
|
||||
build_linear_to_xlaw_table(linear_to_alaw, linear2alaw);
|
||||
}
|
||||
|
||||
void pcm16_ulaw_tableinit()
|
||||
{
|
||||
build_linear_to_xlaw_table(linear_to_ulaw, linear2ulaw);
|
||||
}
|
||||
|
||||
void alaw_pcm16_tableinit()
|
||||
{
|
||||
build_xlaw_to_linear_table(alaw_to_linear, alaw2linear);
|
||||
}
|
||||
|
||||
void ulaw_pcm16_tableinit()
|
||||
{
|
||||
build_xlaw_to_linear_table(ulaw_to_linear, ulaw2linear);
|
||||
}
|
||||
|
||||
#endif // G711_TABLE_H
|
14
contrib/simcom2rtp/g711_table.h
Normal file
14
contrib/simcom2rtp/g711_table.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef G711_TABLE_H
|
||||
#define G711_TABLE_H
|
||||
|
||||
void pcm16_to_alaw(int length, const char *src_samples, char *dst_samples);
|
||||
void pcm16_to_ulaw(int length, const char *src_samples, char *dst_samples);
|
||||
void alaw_to_pcm16(int length, const char *src_samples, char *dst_samples);
|
||||
void ulaw_to_pcm16(int length, const char *src_samples, char *dst_samples);
|
||||
|
||||
void pcm16_alaw_tableinit();
|
||||
void pcm16_ulaw_tableinit();
|
||||
void alaw_pcm16_tableinit();
|
||||
void ulaw_pcm16_tableinit();
|
||||
|
||||
#endif // G711_TABLE_H
|
218
contrib/simcom2rtp/simcom2rtp.c
Normal file
218
contrib/simcom2rtp/simcom2rtp.c
Normal file
@@ -0,0 +1,218 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/fsm.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/application.h>
|
||||
#include <osmocom/core/serial.h>
|
||||
|
||||
#include <osmocom/trau/osmo_ortp.h>
|
||||
|
||||
#include "g711.h"
|
||||
|
||||
#define RTP_PT_PCMU 0
|
||||
#define RTP_PT_PCMA 8
|
||||
|
||||
struct modem_state {
|
||||
struct osmo_fd data_fd;
|
||||
struct osmo_rtp_socket *rtp;
|
||||
/* queue of linear PCM audio in RTP -> modem direction */
|
||||
struct llist_head rtp2modem;
|
||||
/* message buffer used if samples insufficient for next RTP frame were received */
|
||||
struct msgb *modem2rtp;
|
||||
};
|
||||
|
||||
static void *g_tall_ctx;
|
||||
|
||||
|
||||
/* call-back on received RTP data */
|
||||
static void ortp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *payload,
|
||||
unsigned int payload_len, uint16_t seq_nr, uint32_t timestamp, bool marker)
|
||||
{
|
||||
/* we received a RTP frame */
|
||||
struct modem_state *ms = rs->priv;
|
||||
struct msgb *msg = msgb_alloc(payload_len*2, "RTP Rx");
|
||||
unsigned int i;
|
||||
int16_t *out;
|
||||
|
||||
OSMO_ASSERT(msg);
|
||||
|
||||
out = (int16_t *) msgb_put(msg, payload_len*2);
|
||||
|
||||
if (payload_len != 160) {
|
||||
fprintf(stderr, "RTP payload length %d != 160, dropping\n", payload_len);
|
||||
msgb_free(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
/* convert from Alaw to linear PCM (160 -> 320 bytes) */
|
||||
for (i = 0; i < payload_len; i++)
|
||||
out[i] = alaw2linear(payload[i]);
|
||||
|
||||
/* append to the write queue */
|
||||
msgb_enqueue(&ms->rtp2modem, msg);
|
||||
ms->data_fd.when |= OSMO_FD_WRITE;
|
||||
}
|
||||
|
||||
static void modem2rtp(struct modem_state *ms, const uint8_t *data, unsigned int len)
|
||||
{
|
||||
const int16_t *data16 = (const int16_t *)data;
|
||||
unsigned int samples = len / 2;
|
||||
unsigned int offset = 0;
|
||||
unsigned int i;
|
||||
|
||||
/* samples are always 16bit, we cannot read half a sample */
|
||||
OSMO_ASSERT((len & 1) == 0);
|
||||
|
||||
/* first complete any pending incomplete RTP frame */
|
||||
if (ms->modem2rtp) {
|
||||
struct msgb *msg = ms->modem2rtp;
|
||||
unsigned int missing_samples = 160 - msgb_length(msg);
|
||||
for (i = 0; i < missing_samples; i++) {
|
||||
if (i >= samples)
|
||||
break;
|
||||
msgb_put_u8(msg, linear2alaw(data16[i]));
|
||||
}
|
||||
offset = i;
|
||||
if (msgb_length(msg) == 160) {
|
||||
osmo_rtp_send_frame_ext(ms->rtp, msgb_data(msg), msgb_length(msg), 160, false);
|
||||
msgb_free(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/* then send as many RTP frames as we have samples */
|
||||
for (offset = offset; offset + 160 <= samples; offset += 160) {
|
||||
uint8_t buf[160];
|
||||
for (i = 0; i < sizeof(buf); i++)
|
||||
buf[i] = linear2alaw(data16[offset + i]);
|
||||
osmo_rtp_send_frame_ext(ms->rtp, buf, sizeof(buf), 160, false);
|
||||
}
|
||||
|
||||
/* store remainder in msgb */
|
||||
if (offset < samples) {
|
||||
struct msgb *msg = msgb_alloc_c(ms, 160, "modem2rtp");
|
||||
OSMO_ASSERT(msg);
|
||||
OSMO_ASSERT(len - offset < 160);
|
||||
for (i = 0; i < len - offset; i++)
|
||||
msgb_put_u8(msg, linear2alaw(data16[offset + i]));
|
||||
ms->modem2rtp = msg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* call back on file descriptor events of the modem DATA ttyUSB device */
|
||||
static int modem_data_fd_cb(struct osmo_fd *ofd, unsigned int what)
|
||||
{
|
||||
struct modem_state *ms = ofd->data;
|
||||
int rc;
|
||||
|
||||
if (what & OSMO_FD_READ) {
|
||||
/* SIM5360 USB AUDIO Application Note v1.01 states 1600 bytes every 100ms */
|
||||
uint8_t rx_buf[1600];
|
||||
rc = read(ofd->fd, rx_buf, sizeof(rx_buf));
|
||||
OSMO_ASSERT(rc > 0);
|
||||
modem2rtp(ms, rx_buf, rc);
|
||||
}
|
||||
|
||||
if (what & OSMO_FD_WRITE) {
|
||||
struct msgb *msg = msgb_dequeue(&ms->rtp2modem);
|
||||
if (!msg)
|
||||
ofd->when &= ~OSMO_FD_WRITE;
|
||||
else {
|
||||
/* SIM5300 USB AUDIO Application Note v1.01 states 640 bytes every 40ms;
|
||||
* we simply write every RTP frame individually (320 bytes every 20ms) */
|
||||
rc = write(ofd->fd, msgb_data(msg), msgb_length(msg));
|
||||
if (rc != msgb_length(msg))
|
||||
fprintf(stderr, "Short write: %d < %u\n", rc, msgb_length(msg));
|
||||
msgb_free(msg);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int modem_data_open(struct modem_state *ms, const char *basepath)
|
||||
{
|
||||
char fname[PATH_MAX+1];
|
||||
int fd;
|
||||
|
||||
/* the assumption is that the caller provides something like
|
||||
* "/dev/serial/by-path/pci-0000:00:14.0-usb-0:2:1" */
|
||||
snprintf(fname, sizeof(fname), "%s.0-port0", basepath);
|
||||
|
||||
fd = osmo_serial_init(fname, 921600);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "failed to open device '%s': %s\n", fname, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
osmo_fd_setup(&ms->data_fd, fd, OSMO_FD_READ, modem_data_fd_cb, ms, 0);
|
||||
osmo_fd_register(&ms->data_fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct modem_state *modem_create(void *ctx)
|
||||
{
|
||||
struct modem_state *ms = talloc_zero(ctx, struct modem_state);
|
||||
int rc;
|
||||
|
||||
INIT_LLIST_HEAD(&ms->rtp2modem);
|
||||
|
||||
ms->rtp = osmo_rtp_socket_create(ms, 0);
|
||||
OSMO_ASSERT(ms->rtp);
|
||||
osmo_rtp_socket_set_pt(ms->rtp, RTP_PT_PCMA);
|
||||
ms->rtp->priv = ms;
|
||||
ms->rtp->rx_cb = ortp_rx_cb;
|
||||
|
||||
rc = osmo_rtp_socket_bind(ms->rtp, "0.0.0.0", 1111);
|
||||
OSMO_ASSERT(rc == 0);
|
||||
|
||||
rc = osmo_rtp_socket_connect(ms->rtp, "127.0.0.1", 2222);
|
||||
//rc = osmo_rtp_socket_autoconnect(ms->rtp);
|
||||
OSMO_ASSERT(rc == 0);
|
||||
|
||||
osmo_rtp_set_source_desc(ms->rtp, "cname", "simcom2rtp", NULL, NULL, NULL,
|
||||
"osmo-simcom2rtp", NULL);
|
||||
|
||||
return ms;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
|
||||
talloc_enable_null_tracking();
|
||||
g_tall_ctx = talloc_named_const(NULL, 1, "simcom2rtp");
|
||||
|
||||
msgb_talloc_ctx_init(g_tall_ctx, 0);
|
||||
osmo_init_logging2(g_tall_ctx, NULL);
|
||||
osmo_fsm_log_timeouts(true);
|
||||
osmo_fsm_log_addr(true);
|
||||
//osmo_stats_init(g_tall_ctx);
|
||||
osmo_rtp_init(g_tall_ctx);
|
||||
|
||||
struct modem_state *ms = modem_create(g_tall_ctx);
|
||||
int rc;
|
||||
|
||||
OSMO_ASSERT(ms);
|
||||
rc = modem_data_open(ms, "/dev/serial/by-path/pci-0000:00:14.0-usb-0:2:1");
|
||||
OSMO_ASSERT(rc == 0);
|
||||
|
||||
while (1) {
|
||||
osmo_select_main(0);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user