diff options
Diffstat (limited to 'src/trau/osmo_ortp.c')
-rw-r--r-- | src/trau/osmo_ortp.c | 577 |
1 files changed, 577 insertions, 0 deletions
diff --git a/src/trau/osmo_ortp.c b/src/trau/osmo_ortp.c new file mode 100644 index 0000000..65ec269 --- /dev/null +++ b/src/trau/osmo_ortp.c @@ -0,0 +1,577 @@ +/* (C) 2011 by Harald Welte <laforge@gnumonks.org> + * (C) 2011 by On-Waves e.h.f + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +/*! \file osmo_ortp.c + * \brief Integration of libortp into osmocom framework (select, logging) + */ + +#include <stdint.h> +#include <inttypes.h> +#include <netdb.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/trau/osmo_ortp.h> + +#include <ortp/ortp.h> +#include <ortp/rtp.h> +#include <ortp/port.h> +#include <ortp/rtpsession.h> + +#include "config.h" + +static PayloadType *payload_type_efr; +static PayloadType *payload_type_hr; +static RtpProfile *osmo_pt_profile; + +static void *tall_rtp_ctx; + +/* malloc integration */ + +static void *osmo_ortp_malloc(size_t sz) +{ + return talloc_size(tall_rtp_ctx, sz); +} + +static void *osmo_ortp_realloc(void *ptr, size_t sz) +{ + return talloc_realloc_size(tall_rtp_ctx, ptr, sz); +} + +static void osmo_ortp_free(void *ptr) +{ + talloc_free(ptr); +} + +static OrtpMemoryFunctions osmo_ortp_memfn = { + .malloc_fun = osmo_ortp_malloc, + .realloc_fun = osmo_ortp_realloc, + .free_fun = osmo_ortp_free +}; + +/* logging */ + +struct level_map { + OrtpLogLevel ortp; + int osmo_level; +}; +static const struct level_map level_map[] = { + { ORTP_DEBUG, LOGL_DEBUG }, + { ORTP_MESSAGE, LOGL_INFO }, + { ORTP_WARNING, LOGL_NOTICE }, + { ORTP_ERROR, LOGL_ERROR }, + { ORTP_FATAL, LOGL_FATAL }, +}; +static int ortp_to_osmo_lvl(OrtpLogLevel lev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(level_map); i++) { + if (level_map[i].ortp == lev) + return level_map[i].osmo_level; + } + /* default */ + return LOGL_ERROR; +} + +static void my_ortp_logfn(OrtpLogLevel lev, const char *fmt, + va_list args) +{ + osmo_vlogp(DLMIB, ortp_to_osmo_lvl(lev), __FILE__, 0, + 0, fmt, args); +} + +/* ORTP signal callbacks */ + +static void ortp_sig_cb_ssrc(RtpSession *rs, void *data) +{ + int port = rtp_session_get_local_port(rs); +#if 0 /* post 0.20.0 ORTP has this function */ + uint32_t ssrc = rtp_session_get_recv_ssrc(rs); +#else + uint32_t ssrc = rs->rcv.ssrc; +#endif + + LOGP(DLMIB, LOGL_INFO, + "osmo-ortp(%d): ssrc_changed to 0x%08x\n", port, ssrc); +} + +static void ortp_sig_cb_pt(RtpSession *rs, void *data) +{ + int port = rtp_session_get_local_port(rs); + int pt = rtp_session_get_recv_payload_type(rs); + + LOGP(DLMIB, LOGL_NOTICE, + "osmo-ortp(%d): payload_type_changed to 0x%02x\n", port, pt); +} + +static void ortp_sig_cb_net(RtpSession *rs, void *data) +{ + int port = rtp_session_get_local_port(rs); + + LOGP(DLMIB, LOGL_ERROR, + "osmo-ortp(%d): network_error\n", port); +} + +static void ortp_sig_cb_ts(RtpSession *rs, void *data) +{ + int port = rtp_session_get_local_port(rs); + uint32_t ts = rtp_session_get_current_recv_ts(rs); + + LOGP(DLMIB, LOGL_NOTICE, + "osmo-ortp(%d): timestamp_jump, new TS %d\n", port, ts); +} + + +/*! \brief poll the socket for incoming data + * \param[in] rs the socket to be polled + * \returns number of packets received + handed to the rx_cb + */ +int osmo_rtp_socket_poll(struct osmo_rtp_socket *rs) +{ + mblk_t *mblk; + + mblk = rtp_session_recvm_with_ts(rs->sess, rs->rx_user_ts); + if (mblk) { + rtp_get_payload(mblk, &mblk->b_rptr); + /* hand into receiver */ + if (rs->rx_cb) + rs->rx_cb(rs, mblk->b_rptr, + mblk->b_wptr - mblk->b_rptr); + //rs->rx_user_ts += 160; + freemsg(mblk); + return 1; + } else { + LOGP(DLMIB, LOGL_INFO, "osmo_rtp_poll(%u): ERROR!\n", + rs->rx_user_ts); + return 0; + } +} + +/* Osmo FD callbacks */ + +static int osmo_rtp_fd_cb(struct osmo_fd *fd, unsigned int what) +{ + struct osmo_rtp_socket *rs = fd->data; + mblk_t *mblk; + + if (what & BSC_FD_READ) { + /* in polling mode, we don't want to be called here */ + if (rs->flags & OSMO_RTP_F_POLL) { + fd->when &= ~BSC_FD_READ; + return 0; + } + mblk = rtp_session_recvm_with_ts(rs->sess, rs->rx_user_ts); + if (mblk) { + rtp_get_payload(mblk, &mblk->b_rptr); + /* hand into receiver */ + if (rs->rx_cb) + rs->rx_cb(rs, mblk->b_rptr, + mblk->b_wptr - mblk->b_rptr); + freemsg(mblk); + } else + LOGP(DLMIB, LOGL_INFO, "recvm_with_ts(%u): ERROR!\n", + rs->rx_user_ts); + rs->rx_user_ts += 160; + } + /* writing is not queued at the moment, so BSC_FD_WRITE + * shouldn't occur */ + return 0; +} + +static int osmo_rtcp_fd_cb(struct osmo_fd *fd, unsigned int what) +{ + struct osmo_rtp_socket *rs = fd->data; + + /* We probably don't need this at all, as + * rtp_session_recvm_with_ts() will alway also poll the RTCP + * file descriptor for new data */ + return rtp_session_rtcp_recv(rs->sess); +} + +static int osmo_rtp_socket_fdreg(struct osmo_rtp_socket *rs) +{ + rs->rtp_bfd.fd = rtp_session_get_rtp_socket(rs->sess); + rs->rtcp_bfd.fd = rtp_session_get_rtcp_socket(rs->sess); + rs->rtp_bfd.when = rs->rtcp_bfd.when = BSC_FD_READ; + rs->rtp_bfd.when = rs->rtcp_bfd.when = 0; + rs->rtp_bfd.data = rs->rtcp_bfd.data = rs; + rs->rtp_bfd.cb = osmo_rtp_fd_cb; + rs->rtcp_bfd.cb = osmo_rtcp_fd_cb; + + osmo_fd_register(&rs->rtp_bfd); + osmo_fd_register(&rs->rtcp_bfd); + + return 0; +} + +static void create_payload_types() +{ + PayloadType *pt; + + /* EFR */ + pt = payload_type_new(); + pt->type = PAYLOAD_AUDIO_PACKETIZED; + pt->clock_rate = 8000; + pt->mime_type = "EFR"; + pt->normal_bitrate = 12200; + pt->channels = 1; + payload_type_efr = pt; + + /* HR */ + pt = payload_type_new(); + pt->type = PAYLOAD_AUDIO_PACKETIZED; + pt->clock_rate = 8000; + pt->mime_type = "HR"; + pt->normal_bitrate = 6750; /* FIXME */ + pt->channels = 1; + payload_type_hr = pt; + + /* create a new RTP profile as clone of AV profile */ + osmo_pt_profile = rtp_profile_clone(&av_profile); + + /* add the GSM specific payload types. They are all dynamically + * assigned, but in the Osmocom GSM system we have allocated + * them as follows: */ + rtp_profile_set_payload(osmo_pt_profile, RTP_PT_GSM_EFR, payload_type_efr); + rtp_profile_set_payload(osmo_pt_profile, RTP_PT_GSM_HALF, payload_type_hr); + rtp_profile_set_payload(osmo_pt_profile, RTP_PT_AMR, &payload_type_amr); +} + +/* public functions */ + +/*! \brief initialize Osmocom RTP code + * \param[in] ctx default talloc context for library-internal allocations + */ +void osmo_rtp_init(void *ctx) +{ + tall_rtp_ctx = ctx; + ortp_set_memory_functions(&osmo_ortp_memfn); + ortp_init(); + ortp_set_log_level_mask(0xffff); + ortp_set_log_handler(my_ortp_logfn); + create_payload_types(); +} + +int osmo_rtp_socket_set_param(struct osmo_rtp_socket *rs, + enum osmo_rtp_param param, int val) +{ + int rc = 0; + + switch (param) { + case OSMO_RTP_P_JITBUF: + rtp_session_enable_jitter_buffer(rs->sess, + (val) ? TRUE : FALSE); + if (val) + rtp_session_set_jitter_compensation(rs->sess, val); + break; +#if 0 + case OSMO_RTP_P_JIT_ADAP: + rc = jitter_control_enable_adaptive(rs->sess, val); + break; +#endif + default: + return -EINVAL; + } + + return rc; +} + +/*! \brief Create a new RTP socket + * \param[in] talloc_cxt talloc context for this allocation. NULL for + * dafault context + * \param[in] flags Flags like OSMO_RTP_F_POLL + * \returns pointer to library-allocated \a struct osmo_rtp_socket + */ +struct osmo_rtp_socket *osmo_rtp_socket_create(void *talloc_ctx, unsigned int flags) +{ + struct osmo_rtp_socket *rs; + + if (!talloc_ctx) + talloc_ctx = tall_rtp_ctx; + + rs = talloc_zero(talloc_ctx, struct osmo_rtp_socket); + if (!rs) + return NULL; + + rs->flags = flags; + rs->sess = rtp_session_new(RTP_SESSION_SENDRECV); + if (!rs->sess) { + talloc_free(rs); + return NULL; + } + rtp_session_set_data(rs->sess, rs); + rtp_session_set_profile(rs->sess, osmo_pt_profile); + rtp_session_set_jitter_compensation(rs->sess, 100); + //jitter_control_enable_adaptive(rs->sess, 0); + + rtp_session_signal_connect(rs->sess, "ssrc_changed", + (RtpCallback) ortp_sig_cb_ssrc, + (unsigned long) rs); + rtp_session_signal_connect(rs->sess, "payload_type_changed", + (RtpCallback) ortp_sig_cb_pt, + (unsigned long) rs); + rtp_session_signal_connect(rs->sess, "network_error", + (RtpCallback) ortp_sig_cb_net, + (unsigned long) rs); + rtp_session_signal_connect(rs->sess, "timestamp_jump", + (RtpCallback) ortp_sig_cb_ts, + (unsigned long) rs); + + /* initialize according to the RFC */ + rtp_session_set_seq_number(rs->sess, random()); + rs->tx_timestamp = random(); + + + return rs; +} + +/*! \brief bind a RTP socket to a local port + * \param[in] rs OsmoRTP socket + * \param[in] ip hostname/ip as string + * \param[in] port UDP port number, -1 for random selection + * \returns 0 on success, <0 on error + */ +int osmo_rtp_socket_bind(struct osmo_rtp_socket *rs, const char *ip, int port) +{ + int rc; +#if HAVE_ORTP_021 + rc = rtp_session_set_local_addr(rs->sess, ip, port, port+1); +#else + rc = rtp_session_set_local_addr(rs->sess, ip, port); +#endif + if (rc < 0) + return rc; + + rs->rtp_bfd.fd = rtp_session_get_rtp_socket(rs->sess); + rs->rtcp_bfd.fd = rtp_session_get_rtcp_socket(rs->sess); + + return 0; +} + +/*! \brief connect a OsmoRTP socket to a remote port + * \param[in] rs OsmoRTP socket + * \param[in] ip String representation of remote hostname or IP address + * \param[in] port UDP port number to connect to + * + * If the OsmoRTP socket is not in POLL mode, this function will also + * cause the RTP and RTCP file descriptors to be registred with the + * libosmocore select() loop integration. + * + * \returns 0 on success, <0 in case of error + */ +int osmo_rtp_socket_connect(struct osmo_rtp_socket *rs, const char *ip, uint16_t port) +{ + int rc; + + /* enable the use of connect() so later getsockname() will + * actually return the IP address that was chosen for the local + * sid of the connection */ + rtp_session_set_connected_mode(rs->sess, 1); + + rc = rtp_session_set_remote_addr(rs->sess, ip, port); + if (rc < 0) + return rc; + + if (rs->flags & OSMO_RTP_F_POLL) + return rc; + else + return osmo_rtp_socket_fdreg(rs); +} + +/*! \brief Send one RTP frame via a RTP socket + * \param[in] rs OsmoRTP socket + * \param[in] payload pointer to buffer with RTP payload data + * \param[in] payload_len length of \a payload in bytes + * \param[in] duration duration in number of RTP clock ticks + * \returns 0 on success, <0 in case of error. + */ +int osmo_rtp_send_frame(struct osmo_rtp_socket *rs, const uint8_t *payload, + unsigned int payload_len, unsigned int duration) +{ + mblk_t *mblk; + int rc; + + mblk = rtp_session_create_packet(rs->sess, RTP_FIXED_HEADER_SIZE, + payload, payload_len); + if (!mblk) + return -ENOMEM; + + rc = rtp_session_sendm_with_ts(rs->sess, mblk, + rs->tx_timestamp); + rs->tx_timestamp += duration; + if (rc < 0) { + /* no need to free() the mblk, as rtp_session_rtp_send() + * unconditionally free()s the mblk even in case of + * error */ + return rc; + } + + return rc; +} + +/*! \brief Set the payload type of a RTP socket + * \param[in] rs OsmoRTP socket + * \param[in] payload_type RTP payload type + * \returns 0 on success, < 0 otherwise + */ +int osmo_rtp_socket_set_pt(struct osmo_rtp_socket *rs, int payload_type) +{ + int rc; + + rc = rtp_session_set_payload_type(rs->sess, payload_type); + //rtp_session_set_rtcp_report_interval(rs->sess, 5*1000); + + return rc; +} + +/*! \brief completely close the RTP socket and release all resources + * \param[in] rs OsmoRTP socket to be released + * \returns 0 on success + */ +int osmo_rtp_socket_free(struct osmo_rtp_socket *rs) +{ + if (rs->rtp_bfd.list.next && rs->rtp_bfd.list.next != LLIST_POISON1) + osmo_fd_unregister(&rs->rtp_bfd); + + if (rs->rtcp_bfd.list.next && rs->rtcp_bfd.list.next != LLIST_POISON1) + osmo_fd_unregister(&rs->rtcp_bfd); + + if (rs->sess) { + rtp_session_release_sockets(rs->sess); + rtp_session_destroy(rs->sess); + rs->sess = NULL; + } + + talloc_free(rs); + + return 0; +} + +/*! \brief obtain the locally bound IPv4 address and UDP port + * \param[in] rs OsmoRTP socket + * \param[out] ip Pointer to caller-allocated uint32_t for IPv4 address + * \oaram[out] port Pointer to caller-allocated int for UDP port number + * \returns 0 on success, <0 on error, -EIO in case of IPv6 socket + */ +int osmo_rtp_get_bound_ip_port(struct osmo_rtp_socket *rs, + uint32_t *ip, int *port) +{ + int rc; + struct sockaddr_storage ss; + struct sockaddr_in *sin = (struct sockaddr_in *) &ss; + socklen_t alen = sizeof(ss); + + rc = getsockname(rs->rtp_bfd.fd, (struct sockaddr *)&ss, &alen); + if (rc < 0) + return rc; + + if (ss.ss_family != AF_INET) + return -EIO; + + *ip = ntohl(sin->sin_addr.s_addr); + *port = rtp_session_get_local_port(rs->sess); + + return 0; +} + +/*! \brief obtain the locally bound address and port + * \param[in] rs OsmoRTP socket + * \param[out] addr caller-allocated char ** to which the string pointer for + * the address is stored + * \param[out] port caller-allocated int * to which the port number is + * stored + * \returns 0 on success, <0 in case of error + */ +int osmo_rtp_get_bound_addr(struct osmo_rtp_socket *rs, + const char **addr, int *port) +{ + int rc; + struct sockaddr_storage ss; + socklen_t alen = sizeof(ss); + static char hostbuf[256]; + + memset(hostbuf, 0, sizeof(hostbuf)); + + rc = getsockname(rs->rtp_bfd.fd, (struct sockaddr *)&ss, &alen); + if (rc < 0) + return rc; + + rc = getnameinfo((struct sockaddr *)&ss, alen, + hostbuf, sizeof(hostbuf), NULL, 0, + NI_NUMERICHOST); + if (rc < 0) + return rc; + + *port = rtp_session_get_local_port(rs->sess); + *addr = hostbuf; + + return 0; +} + + +void osmo_rtp_socket_log_stats(struct osmo_rtp_socket *rs, + int subsys, int level, + const char *pfx) +{ + const rtp_stats_t *stats; + + stats = rtp_session_get_stats(rs->sess); + if (!stats) + return; + + LOGP(subsys, level, "%sRTP Tx(%"PRIu64" pkts, %"PRIu64" bytes) " + "Rx(%"PRIu64" pkts, %"PRIu64" bytes, %"PRIu64" late, " + "%"PRIu64" loss, %"PRIu64" qmax)\n", + pfx, stats->packet_sent, stats->sent, + stats->packet_recv, stats->hw_recv, stats->outoftime, + stats->cum_packet_loss, stats->discarded); +} + +void osmo_rtp_socket_stats(struct osmo_rtp_socket *rs, + uint32_t *sent_packets, uint32_t *sent_octets, + uint32_t *recv_packets, uint32_t *recv_octets, + uint32_t *recv_lost, uint32_t *last_jitter) +{ + const rtp_stats_t *stats; + + *sent_packets = *sent_octets = *recv_packets = *recv_octets = 0; + *recv_lost = *last_jitter = 0; + + stats = rtp_session_get_stats(rs->sess); + if (stats) { + /* truncate from 64bit to 32bit here */ + *sent_packets = stats->packet_sent; + *sent_octets = stats->sent; + *recv_packets = stats->packet_recv; + *recv_octets = stats->recv; + *recv_lost = stats->cum_packet_loss; + } + +#if HAVE_ORTP_021 + const jitter_stats_t *jitter; + + jitter = rtp_session_get_jitter_stats(rs->sess); + if (jitter) + *last_jitter = jitter->jitter; +#endif +} |