From f90f421b165c7880cd88db8795f00073dd768f60 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Sat, 13 Mar 2021 17:10:08 +0100 Subject: Add libs --- src/libosmocc/rtp.c | 399 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 399 insertions(+) create mode 100644 src/libosmocc/rtp.c (limited to 'src/libosmocc/rtp.c') diff --git a/src/libosmocc/rtp.c b/src/libosmocc/rtp.c new file mode 100644 index 0000000..a6de25a --- /dev/null +++ b/src/libosmocc/rtp.c @@ -0,0 +1,399 @@ +/* Osmo-CC: RTP handling + * + * (C) 2016 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "../libtimer/timer.h" +#include "endpoint.h" + +#define RTP_VERSION 2 + +static uint16_t rtp_port_next = 16384; +static uint16_t rtp_port_from = 16384; +static uint16_t rtp_port_to = 32767; + +void osmo_cc_set_rtp_ports(uint16_t from, uint16_t to) +{ + rtp_port_next = from; + rtp_port_from = from; + rtp_port_to = to; +} + +struct rtp_hdr { + uint8_t byte0; + uint8_t byte1; + uint16_t sequence; + uint32_t timestamp; + uint32_t ssrc; +} __attribute__((packed)); + +struct rtp_x_hdr { + uint16_t by_profile; + uint16_t length; +} __attribute__((packed)); + +static int rtp_receive(int sock, uint8_t **payload_p, int *payload_len_p, uint8_t *marker_p, uint8_t *pt_p, uint16_t *sequence_p, uint32_t *timestamp_p) +{ + static uint8_t data[2048]; + int len; + struct rtp_hdr *rtph = (struct rtp_hdr *)data; + uint8_t version, padding, extension, csrc_count, marker, payload_type; + struct rtp_x_hdr *rtpxh; + uint8_t *payload; + int payload_len; + int x_len; + + len = read(sock, data, sizeof(data)); + if (len < 0) { + if (errno == EAGAIN) + return -EAGAIN; + PDEBUG(DCC, DEBUG_DEBUG, "Read errno = %d (%s)\n", errno, strerror(errno)); + return -EIO; + } + if (len < 12) { + PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame too short (len = %d).\n", len); + return -EINVAL; + } + + version = rtph->byte0 >> 6; + padding = (rtph->byte0 >> 5) & 1; + extension = (rtph->byte0 >> 4) & 1; + csrc_count = rtph->byte0 & 0x0f; + marker = rtph->byte1 >> 7; + payload_type = rtph->byte1 & 0x7f; + *sequence_p = ntohs(rtph->sequence); + *timestamp_p = ntohl(rtph->timestamp); + + if (version != RTP_VERSION) { + PDEBUG(DCC, DEBUG_NOTICE, "Received RTP version %d not supported.\n", version); + return -EINVAL; + } + + payload = data + sizeof(*rtph) + (csrc_count << 2); + payload_len = len - sizeof(*rtph) - (csrc_count << 2); + if (payload_len < 0) { + PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame too short (len = %d, csrc count = %d).\n", len, csrc_count); + return -EINVAL; + } + + if (extension) { + if (payload_len < (int)sizeof(*rtpxh)) { + PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame too short for extension header.\n"); + return -EINVAL; + } + rtpxh = (struct rtp_x_hdr *)payload; + x_len = ntohs(rtpxh->length) * 4 + sizeof(*rtpxh); + payload += x_len; + payload_len -= x_len; + if (payload_len < (int)sizeof(*rtpxh)) { + PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame too short, extension header exceeds frame length.\n"); + return -EINVAL; + } + } + + if (padding) { + if (payload_len < 1) { + PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame too short for padding length.\n"); + return -EINVAL; + } + payload_len -= payload[payload_len - 1]; + if (payload_len < 0) { + PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame padding is greater than payload.\n"); + return -EINVAL; + } + } + + *payload_p = payload; + *payload_len_p = payload_len; + *marker_p = marker; + *pt_p = payload_type; + + return 0; +} + +static void rtp_send(int sock, uint8_t *payload, int payload_len, uint8_t pt, uint16_t sequence, uint32_t timestamp, uint32_t ssrc) +{ + struct rtp_hdr *rtph; + char data[sizeof(*rtph) + payload_len]; + int len, rc; + + rtph = (struct rtp_hdr *)data; + len = sizeof(*rtph); + rtph->byte0 = RTP_VERSION << 6; + rtph->byte1 = pt; + rtph->sequence = htons(sequence); + rtph->timestamp = htonl(timestamp); + rtph->ssrc = htonl(ssrc); + len += payload_len; + if (len > (int)sizeof(data)) { + PDEBUG(DCC, DEBUG_NOTICE, "Buffer overflow, please fix!.\n"); + abort(); + } + memcpy(data + sizeof(*rtph), payload, payload_len); + + rc = write(sock, data, len); + if (rc < 0) + PDEBUG(DCC, DEBUG_DEBUG, "Write errno = %d (%s)\n", errno, strerror(errno)); +} + +/* open and bind RTP + * set local port to what we bound + */ +int osmo_cc_rtp_open(osmo_cc_session_media_t *media) +{ + int domain = 0; // make GCC happy + uint16_t start_port; + struct sockaddr_storage sa; + int slen = 0; // make GCC happy + struct sockaddr_in6 *sa6; + struct sockaddr_in *sa4; + uint16_t *sport; + int flags; + int rc; + + media->rtp_ssrc = rand(); + + osmo_cc_rtp_close(media); + + switch (media->connection_data_local.addrtype) { + case osmo_cc_session_addrtype_ipv4: + domain = AF_INET; + memset(&sa, 0, sizeof(sa)); + sa4 = (struct sockaddr_in *)&sa; + sa4->sin_family = domain; + sa4->sin_addr.s_addr = INADDR_ANY; + sport = &sa4->sin_port; + slen = sizeof(*sa4); + break; + case osmo_cc_session_addrtype_ipv6: + domain = AF_INET6; + memset(&sa, 0, sizeof(sa)); + sa6 = (struct sockaddr_in6 *)&sa; + sa6->sin6_family = domain; + sa6->sin6_addr = in6addr_any; + sport = &sa6->sin6_port; + slen = sizeof(*sa6); + break; + case osmo_cc_session_addrtype_unknown: + PDEBUG(DCC, DEBUG_NOTICE, "Unsupported address type '%s'.\n", media->connection_data_local.addrtype_name); + return -EINVAL; + } + + /* rtp_port_from/rtp_port_to may be changed at run time, so rtp_port_next can become out of range. */ + if (rtp_port_next < rtp_port_from || rtp_port_next > rtp_port_to) + rtp_port_next = rtp_port_from; + start_port = rtp_port_next; + while (1) { + /* open sockets */ + rc = socket(domain, SOCK_DGRAM, IPPROTO_UDP); + if (rc < 0) { +socket_error: + PDEBUG(DCC, DEBUG_ERROR, "Cannot create socket (domain=%d, errno=%d(%s))\n", domain, errno, strerror(errno)); + osmo_cc_rtp_close(media); + return -EIO; + } + media->rtp_socket = rc; + rc = socket(domain, SOCK_DGRAM, IPPROTO_UDP); + if (rc < 0) + goto socket_error; + media->rtcp_socket = rc; + + /* bind sockets */ + *sport = htons(rtp_port_next); + rc = bind(media->rtp_socket, (struct sockaddr *)&sa, slen); + if (rc < 0) { +bind_error: + osmo_cc_rtp_close(media); + rtp_port_next = (rtp_port_next + 2 > rtp_port_to) ? rtp_port_from : rtp_port_next + 2; + if (rtp_port_next == start_port) { + PDEBUG(DCC, DEBUG_ERROR, "Cannot bind socket (errno=%d(%s))\n", errno, strerror(errno)); + return -EIO; + } + continue; + } + *sport = htons(rtp_port_next + 1); + rc = bind(media->rtcp_socket, (struct sockaddr *)&sa, slen); + if (rc < 0) + goto bind_error; + media->description.port_local = rtp_port_next; + rtp_port_next = (rtp_port_next + 2 > rtp_port_to) ? rtp_port_from : rtp_port_next + 2; + /* set nonblocking io */ + flags = fcntl(media->rtp_socket, F_GETFL); + flags |= O_NONBLOCK; + fcntl(media->rtp_socket, F_SETFL, flags); + flags = fcntl(media->rtcp_socket, F_GETFL); + flags |= O_NONBLOCK; + fcntl(media->rtcp_socket, F_SETFL, flags); + break; + } + + PDEBUG(DCC, DEBUG_DEBUG, "Opening media port %d\n", media->description.port_local); + + return 0; +} + +/* connect RTP + * use remote port to connect to + */ +int osmo_cc_rtp_connect(osmo_cc_session_media_t *media) +{ + struct sockaddr_storage sa; + int slen = 0; // make GCC happy + struct sockaddr_in6 *sa6; + struct sockaddr_in *sa4; + uint16_t *sport; + int rc; + + PDEBUG(DCC, DEBUG_DEBUG, "Connecting media port %d->%d\n", media->description.port_local, media->description.port_remote); + + switch (media->connection_data_remote.addrtype) { + case osmo_cc_session_addrtype_ipv4: + memset(&sa, 0, sizeof(sa)); + sa4 = (struct sockaddr_in *)&sa; + sa4->sin_family = AF_INET; + rc = inet_pton(AF_INET, media->connection_data_remote.address, &sa4->sin_addr); + if (rc < 1) { +pton_error: + PDEBUG(DCC, DEBUG_NOTICE, "Cannot connect to address '%s'.\n", media->connection_data_remote.address); + return -EINVAL; + } + sport = &sa4->sin_port; + slen = sizeof(*sa4); + break; + case osmo_cc_session_addrtype_ipv6: + memset(&sa, 0, sizeof(sa)); + sa6 = (struct sockaddr_in6 *)&sa; + sa6->sin6_family = AF_INET6; + rc = inet_pton(AF_INET6, media->connection_data_remote.address, &sa6->sin6_addr); + if (rc < 1) + goto pton_error; + sport = &sa6->sin6_port; + slen = sizeof(*sa6); + break; + case osmo_cc_session_addrtype_unknown: + PDEBUG(DCC, DEBUG_NOTICE, "Unsupported address type '%s'.\n", media->connection_data_local.addrtype_name); + return -EINVAL; + } + + *sport = htons(media->description.port_remote); + rc = connect(media->rtp_socket, (struct sockaddr *)&sa, slen); + if (rc < 0) { +connect_error: + PDEBUG(DCC, DEBUG_NOTICE, "Cannot connect to address '%s'.\n", media->connection_data_remote.address); + osmo_cc_rtp_close(media); + return -EIO; + } + *sport = htons(media->description.port_remote + 1); + rc = connect(media->rtcp_socket, (struct sockaddr *)&sa, slen); + if (rc < 0) + goto connect_error; + + return 0; +} + +/* send rtp data with given codec */ +void osmo_cc_rtp_send(osmo_cc_session_codec_t *codec, uint8_t *data, int len, int inc_sequence, int inc_timestamp) +{ + uint8_t *payload = NULL; + int payload_len = 0; + + if (!codec || !codec->media->rtp_socket) + return; + + if (codec->encoder) + codec->encoder(data, len, &payload, &payload_len); + else { + payload = data; + payload_len = len; + } + + rtp_send(codec->media->rtp_socket, payload, payload_len, codec->payload_type_remote, codec->media->tx_sequence, codec->media->tx_timestamp, codec->media->rtp_ssrc); + codec->media->tx_sequence += inc_sequence; + codec->media->tx_timestamp += inc_timestamp; + + if (codec->encoder) + free(payload); +} + +/* receive rtp data for given media, return < 0, if there is nothing this time */ +int osmo_cc_rtp_receive(osmo_cc_session_media_t *media) +{ + int rc; + uint8_t *payload = NULL; + int payload_len = 0; + uint8_t marker; + uint8_t payload_type; + osmo_cc_session_codec_t *codec; + uint8_t *data; + int len; + + if (!media || media->rtp_socket <= 0) + return -EIO; + + rc = rtp_receive(media->rtp_socket, &payload, &payload_len, &marker, &payload_type, &media->rx_sequence, &media->rx_timestamp); + if (rc < 0) + return rc; + + /* search for codec */ + for (codec = media->codec_list; codec; codec = codec->next) { + + if (codec->payload_type_local == payload_type) + break; + } + if (!codec) { + PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame for unknown codec (payload_type = %d).\n", payload_type); + return 0; + } + + if (codec->decoder) + codec->decoder(payload, payload_len, &data, &len); + else { + data = payload; + len = payload_len; + } + + if (codec->media->receive) + codec->media->receiver(codec, media->rx_sequence, media->rx_timestamp, data, len); + + if (codec->decoder) + free(data); + + return 0; +} + +void osmo_cc_rtp_close(osmo_cc_session_media_t *media) +{ + if (media->rtp_socket) { + close(media->rtp_socket); + media->rtp_socket = 0; + } + if (media->rtcp_socket) { + close(media->rtcp_socket); + media->rtcp_socket = 0; + } +} + -- cgit v1.2.3