From da727875ac954b13ecb16521d255499511bb7424 Mon Sep 17 00:00:00 2001 From: mannol Date: Sun, 13 Oct 2013 16:16:47 +0200 Subject: tox A/V: RTP/MSI implementation --- toxrtp/toxrtp.c | 693 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 693 insertions(+) create mode 100644 toxrtp/toxrtp.c (limited to 'toxrtp/toxrtp.c') diff --git a/toxrtp/toxrtp.c b/toxrtp/toxrtp.c new file mode 100644 index 00000000..6844b0b1 --- /dev/null +++ b/toxrtp/toxrtp.c @@ -0,0 +1,693 @@ +/* rtp_impl.c + * + * Rtp implementation includes rtp_session_s struct which is a session identifier. + * It contains session information and it's a must for every session. + * It's best if you don't touch any variable directly but use callbacks to do so. !Red! + * + * + * Copyright (C) 2013 Tox project All Rights Reserved. + * + * This file is part of Tox. + * + * Tox 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. + * + * Tox 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 Tox. If not, see . + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "toxrtp.h" +#include "toxrtp_message.h" +#include "toxrtp_helper.h" +#include +#include +#include "../toxcore/util.h" +#include "../toxcore/network.h" + +/* Some defines */ +#define PAYLOAD_ID_VALUE_OPUS 1 +#define PAYLOAD_ID_VALUE_VP8 2 + +#define size_32 4 +/* End of defines */ + +#ifdef _USE_ERRORS +#include "toxrtp_error_id.h" +#endif /* _USE_ERRORS */ + +static const uint32_t _payload_table[] = /* PAYLOAD TABLE */ +{ + 8000, 8000, 8000, 8000, 8000, 8000, 16000, 8000, 8000, 8000, /* 0-9 */ + 44100, 44100, 0, 0, 90000, 8000, 11025, 22050, 0, 0, /* 10-19 */ + 0, 0, 0, 0, 0, 90000, 90000, 0, 90000, 0, /* 20-29 */ + 0, 90000, 90000, 90000, 90000, 0, 0, 0, 0, 0, /* 30-39 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40-49 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50-59 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60-69 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70-79 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80-89 */ + 0, 0, 0, 0, 0, 0, PAYLOAD_ID_VALUE_OPUS, 0, 0, 0, /* 90-99 */ + 0, 0, 0, 0, 0, 0, PAYLOAD_ID_VALUE_VP8, 0, 0, 0, /* 100-109 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 110-119 */ + 0, 0, 0, 0, 0, 0, 0, 0 /* 120-127 */ +}; + +/* Current compatibility solution */ +int m_sendpacket(Networking_Core* _core_handler, void *ip_port, uint8_t *data, uint32_t length) +{ + return sendpacket(_core_handler, *((IP_Port*) ip_port), data, length); +} + +rtp_session_t* rtp_init_session ( int max_users, int _multi_session ) +{ +#ifdef _USE_ERRORS + REGISTER_RTP_ERRORS +#endif /* _USE_ERRORS */ + + rtp_session_t* _retu = calloc(sizeof(rtp_session_t), 1); + assert(_retu); + + _retu->_dest_list = _retu->_last_user = NULL; + + _retu->_max_users = max_users; + _retu->_packets_recv = 0; + _retu->_packets_sent = 0; + _retu->_bytes_sent = 0; + _retu->_bytes_recv = 0; + _retu->_last_error = NULL; + _retu->_packet_loss = 0; + + /* + * SET HEADER FIELDS + */ + + _retu->_version = RTP_VERSION; /* It's always 2 */ + _retu->_padding = 0; /* If some additional data is needed about + * the packet */ + _retu->_extension = 0; /* If extension to header is needed */ + _retu->_cc = 1; /* It basically represents amount of contributors */ + _retu->_csrc = NULL; /* Container */ + _retu->_ssrc = t_random ( -1 ); + _retu->_marker = 0; + _retu->_payload_type = 0; /* You should specify payload type */ + + /* Sequence starts at random number and goes to _MAX_SEQU_NUM */ + _retu->_sequence_number = t_random ( _MAX_SEQU_NUM ); + _retu->_last_sequence_number = _retu->_sequence_number; /* Do not touch this variable */ + + _retu->_initial_time = t_time(); /* In seconds */ + assert(_retu->_initial_time); + _retu->_time_elapsed = 0; /* In seconds */ + + _retu->_ext_header = NULL; /* When needed allocate */ + _retu->_exthdr_framerate = -1; + _retu->_exthdr_resolution = -1; + + _retu->_csrc = calloc(sizeof(uint32_t), 1); + assert(_retu->_csrc); + + _retu->_csrc[0] = _retu->_ssrc; /* Set my ssrc to the list receive */ + + _retu->_prefix_length = 0; + _retu->_prefix = NULL; + + _retu->_multi_session = _multi_session; + + /* Initial */ + _retu->_current_framerate = 0; + + + _retu->_oldest_msg = _retu->_last_msg = NULL; + + pthread_mutex_init(&_retu->_mutex, NULL); + /* + * + */ + return _retu; +} + +int rtp_terminate_session ( rtp_session_t* _session ) +{ + if ( !_session ) + return FAILURE; + + if ( _session->_dest_list ){ + rtp_dest_list_t* _fordel = NULL; + rtp_dest_list_t* _tmp = _session->_dest_list; + + while( _tmp ){ + _fordel = _tmp; + _tmp = _tmp->next; + free(_fordel); + } + } + + if ( _session->_ext_header ) + free ( _session->_ext_header ); + + if ( _session->_csrc ) + free ( _session->_csrc ); + + if ( _session->_prefix ) + free ( _session->_prefix ); + + pthread_mutex_destroy(&_session->_mutex); + + /* And finally free session */ + free ( _session ); + + return SUCCESS; +} + +uint16_t rtp_get_resolution_marking_height ( rtp_ext_header_t* _header, uint32_t _position ) +{ + if ( _header->_ext_type & RTP_EXT_TYPE_RESOLUTION ) + return _header->_hd_ext[_position]; + else + return 0; +} + +uint16_t rtp_get_resolution_marking_width ( rtp_ext_header_t* _header, uint32_t _position ) +{ + if ( _header->_ext_type & RTP_EXT_TYPE_RESOLUTION ) + return ( _header->_hd_ext[_position] >> 16 ); + else + return 0; +} + +void rtp_free_msg ( rtp_session_t* _session, rtp_msg_t* _message ) +{ + free ( _message->_data ); + + if ( !_session ){ + free ( _message->_header->_csrc ); + if ( _message->_ext_header ){ + free ( _message->_ext_header->_hd_ext ); + free ( _message->_ext_header ); + } + } else { + if ( _session->_csrc != _message->_header->_csrc ) + free ( _message->_header->_csrc ); + if ( _message->_ext_header && _session->_ext_header != _message->_ext_header ) { + free ( _message->_ext_header->_hd_ext ); + free ( _message->_ext_header ); + } + } + + free ( _message->_header ); + free ( _message ); +} + +rtp_header_t* rtp_build_header ( rtp_session_t* _session ) +{ + rtp_header_t* _retu; + _retu = calloc ( sizeof * _retu, 1 ); + assert(_retu); + + rtp_header_add_flag_version ( _retu, _session->_version ); + rtp_header_add_flag_padding ( _retu, _session->_padding ); + rtp_header_add_flag_extension ( _retu, _session->_extension ); + rtp_header_add_flag_CSRC_count ( _retu, _session->_cc ); + rtp_header_add_setting_marker ( _retu, _session->_marker ); + rtp_header_add_setting_payload ( _retu, _session->_payload_type ); + + _retu->_sequence_number = _session->_sequence_number; + _session->_time_elapsed = t_time() - _session->_initial_time; + _retu->_timestamp = t_time(); + _retu->_ssrc = _session->_ssrc; + + if ( _session->_cc > 0 ) { + _retu->_csrc = calloc(sizeof(uint32_t), _session->_cc); + assert(_retu->_csrc); + + int i; + + for ( i = 0; i < _session->_cc; i++ ) { + _retu->_csrc[i] = _session->_csrc[i]; + } + } else { + _retu->_csrc = NULL; + } + + _retu->_length = _MIN_HEADER_LENGTH + ( _session->_cc * size_32 ); + + return _retu; +} + +void rtp_set_payload_type ( rtp_session_t* _session, uint8_t _payload_value ) +{ + _session->_payload_type = _payload_value; +} +uint32_t rtp_get_payload_type ( rtp_session_t* _session ) +{ + return _payload_table[_session->_payload_type]; +} + +int rtp_add_receiver ( rtp_session_t* _session, tox_IP_Port* _dest ) +{ + if ( !_session ) + return FAILURE; + + rtp_dest_list_t* _new_user = calloc(sizeof(rtp_dest_list_t), 1); + assert(_new_user); + + _new_user->next = NULL; + _new_user->_dest = *_dest; + + if ( _session->_last_user == NULL ) { /* New member */ + _session->_dest_list = _session->_last_user = _new_user; + + } else { /* Append */ + _session->_last_user->next = _new_user; + _session->_last_user = _new_user; + } + + return SUCCESS; +} + +int rtp_send_msg ( rtp_session_t* _session, rtp_msg_t* _msg, void* _core_handler ) +{ + if ( !_msg || _msg->_data == NULL || _msg->_length <= 0 ) { + t_perror ( RTP_ERROR_EMPTY_MESSAGE ); + return FAILURE; + } + + int _last; + unsigned long long _total = 0; + + size_t _length = _msg->_length; + uint8_t _send_data [ MAX_UDP_PACKET_SIZE ]; + + uint16_t _prefix_length = _session->_prefix_length; + + _send_data[0] = 70; + + if ( _session->_prefix && _length + _prefix_length < MAX_UDP_PACKET_SIZE ) { + /*t_memcpy(_send_data, _session->_prefix, _prefix_length);*/ + t_memcpy ( _send_data + 1, _msg->_data, _length ); + } else { + t_memcpy ( _send_data + 1, _msg->_data, _length ); + } + + /* Set sequ number */ + if ( _session->_sequence_number >= _MAX_SEQU_NUM ) { + _session->_sequence_number = 0; + } else { + _session->_sequence_number++; + } + + /* Start sending loop */ + rtp_dest_list_t* _it; + for ( _it = _session->_dest_list; _it != NULL; _it = _it->next ) { + + _last = m_sendpacket ( _core_handler, &_it->_dest, _send_data, _length + 1); + + if ( _last < 0 ) { + t_perror ( RTP_ERROR_STD_SEND_FAILURE ); + printf("Stderror: %s", strerror(errno)); + } else { + _session->_packets_sent ++; + _total += _last; + } + + } + + rtp_free_msg ( _session, _msg ); + _session->_bytes_sent += _total; + return SUCCESS; +} + +rtp_msg_t* rtp_recv_msg ( rtp_session_t* _session ) +{ + if ( !_session ) + return NULL; + + rtp_msg_t* _retu = _session->_oldest_msg; + + pthread_mutex_lock(&_session->_mutex); + + if ( _retu ) + _session->_oldest_msg = _retu->_next; + + if ( !_session->_oldest_msg ) + _session->_last_msg = NULL; + + pthread_mutex_unlock(&_session->_mutex); + + return _retu; +} + +void rtp_store_msg ( rtp_session_t* _session, rtp_msg_t* _msg ) +{ + if ( rtp_check_late_message(_session, _msg) < 0 ) { + rtp_register_msg(_session, _msg); + } + + pthread_mutex_lock(&_session->_mutex); + + if ( _session->_last_msg ) { + _session->_last_msg->_next = _msg; + _session->_last_msg = _msg; + } else { + _session->_last_msg = _session->_oldest_msg = _msg; + } + + pthread_mutex_unlock(&_session->_mutex); + return; +} + +int rtp_release_session_recv ( rtp_session_t* _session ) +{ + if ( !_session ){ + return FAILURE; + } + + rtp_msg_t* _tmp,* _it; + + pthread_mutex_lock(&_session->_mutex); + + for ( _it = _session->_oldest_msg; _it; _it = _tmp ){ + _tmp = _it->_next; + rtp_free_msg(_session, _it); + } + + _session->_last_msg = _session->_oldest_msg = NULL; + + pthread_mutex_unlock(&_session->_mutex); + + return SUCCESS; +} + +rtp_msg_t* rtp_msg_new ( rtp_session_t* _session, const uint8_t* _data, uint32_t _length ) +{ + if ( !_session ) + return NULL; + + uint8_t* _from_pos; + rtp_msg_t* _retu = calloc(sizeof(rtp_msg_t), 1); + assert(_retu); + + /* Sets header values and copies the extension header in _retu */ + _retu->_header = rtp_build_header ( _session ); /* It allocates memory and all */ + _retu->_ext_header = _session->_ext_header; + + uint32_t _total_lenght = _length + _retu->_header->_length; + + if ( _retu->_ext_header ) { + + _total_lenght += ( _MIN_EXT_HEADER_LENGTH + _retu->_ext_header->_ext_len * size_32 ); + /* Allocate Memory for _retu->_data */ + _retu->_data = calloc ( sizeof _retu->_data, _total_lenght ); + assert(_retu->_data); + + _from_pos = rtp_add_header ( _retu->_header, _retu->_data ); + _from_pos = rtp_add_extention_header ( _retu->_ext_header, _from_pos + 1 ); + } else { + /* Allocate Memory for _retu->_data */ + _retu->_data = calloc ( sizeof _retu->_data, _total_lenght ); + assert(_retu->_data); + + _from_pos = rtp_add_header ( _retu->_header, _retu->_data ); + } + + /* + * Parses the extension header into the message + * Of course if any + */ + + /* Appends _data on to _retu->_data */ + t_memcpy ( _from_pos + 1, _data, _length ); + + _retu->_length = _total_lenght; + + _retu->_next = NULL; + + return _retu; +} + +rtp_msg_t* rtp_msg_parse ( rtp_session_t* _session, const uint8_t* _data, uint32_t _length ) +{ + rtp_msg_t* _retu = calloc(sizeof(rtp_msg_t), 1); + assert(_retu); + + _retu->_header = rtp_extract_header ( _data, _length ); /* It allocates memory and all */ + if ( !_retu->_header ){ + free(_retu); + return NULL; + } + + _retu->_length = _length - _retu->_header->_length; + + uint16_t _from_pos = _retu->_header->_length; + + + if ( rtp_header_get_flag_extension ( _retu->_header ) ) { + _retu->_ext_header = rtp_extract_ext_header ( _data + _from_pos, _length ); + if ( _retu->_ext_header ){ + _retu->_length -= ( _MIN_EXT_HEADER_LENGTH + _retu->_ext_header->_ext_len * size_32 ); + _from_pos += ( _MIN_EXT_HEADER_LENGTH + _retu->_ext_header->_ext_len * size_32 ); + } else { + free (_retu->_ext_header); + free (_retu->_header); + free (_retu); + return NULL; + } + } else { + _retu->_ext_header = NULL; + } + + /* Get the payload */ + _retu->_data = calloc ( sizeof ( uint8_t ), _retu->_length ); + assert(_retu->_data); + + t_memcpy ( _retu->_data, _data + _from_pos, _length - _from_pos ); + + _retu->_next = NULL; + + + if ( _session && !_session->_multi_session && rtp_check_late_message(_session, _retu) < 0 ){ + rtp_register_msg(_session, _retu); + } + + return _retu; +} + +int rtp_check_late_message (rtp_session_t* _session, rtp_msg_t* _msg) +{ + /* + * Check Sequence number. If this new msg has lesser number then the _session->_last_sequence_number + * it shows that the message came in late + */ + if ( _msg->_header->_sequence_number < _session->_last_sequence_number && + _msg->_header->_timestamp < _session->_current_timestamp + ) { + return SUCCESS; /* Drop the packet. You can check if the packet dropped by checking _packet_loss increment. */ + } + return FAILURE; +} + +void rtp_register_msg ( rtp_session_t* _session, rtp_msg_t* _msg ) +{ + _session->_last_sequence_number = _msg->_header->_sequence_number; + _session->_current_timestamp = _msg->_header->_timestamp; +} + + +int rtp_add_resolution_marking ( rtp_session_t* _session, uint16_t _width, uint16_t _height ) +{ + if ( !_session ) + return FAILURE; + + rtp_ext_header_t* _ext_header = _session->_ext_header; + _session->_exthdr_resolution = 0; + + if ( ! ( _ext_header ) ) { + _session->_ext_header = calloc (sizeof(rtp_ext_header_t), 1); + assert(_session->_ext_header); + + _session->_extension = 1; + _session->_ext_header->_ext_len = 1; + _ext_header = _session->_ext_header; + _session->_ext_header->_hd_ext = calloc(sizeof(uint32_t), 1); + assert(_session->_ext_header->_hd_ext); + + } else { /* If there is need for more headers this will be needed to change */ + if ( !(_ext_header->_ext_type & RTP_EXT_TYPE_RESOLUTION) ){ + uint32_t _exthdr_framerate = _ext_header->_hd_ext[_session->_exthdr_framerate]; + /* it's position is at 2nd place by default */ + _session->_exthdr_framerate ++; + + /* Update length */ + _ext_header->_ext_len++; + + /* Allocate the value */ + _ext_header->_hd_ext = realloc(_ext_header->_hd_ext, sizeof(rtp_ext_header_t) * _ext_header->_ext_len); + assert(_ext_header->_hd_ext); + + /* Reset other values */ + _ext_header->_hd_ext[_session->_exthdr_framerate] = _exthdr_framerate; + } + } + + /* Add flag */ + _ext_header->_ext_type |= RTP_EXT_TYPE_RESOLUTION; + + _ext_header->_hd_ext[_session->_exthdr_resolution] = _width << 16 | ( uint32_t ) _height; + + return SUCCESS; +} + +int rtp_remove_resolution_marking ( rtp_session_t* _session ) +{ + if ( _session->_extension == 0 || ! ( _session->_ext_header ) ) { + t_perror ( RTP_ERROR_INVALID_EXTERNAL_HEADER ); + return FAILURE; + } + + if ( !( _session->_ext_header->_ext_type & RTP_EXT_TYPE_RESOLUTION ) ) { + t_perror ( RTP_ERROR_INVALID_EXTERNAL_HEADER ); + return FAILURE; + } + + _session->_ext_header->_ext_type &= ~RTP_EXT_TYPE_RESOLUTION; /* Remove the flag */ + _session->_exthdr_resolution = -1; /* Remove identifier */ + + /* Check if extension is empty */ + if ( _session->_ext_header->_ext_type == 0 ){ + + free ( _session->_ext_header->_hd_ext ); + free ( _session->_ext_header ); + + _session->_ext_header = NULL; /* It's very important */ + _session->_extension = 0; + + } else { + _session->_ext_header->_ext_len --; + + /* this will also be needed to change if there are more than 2 headers */ + if ( _session->_ext_header->_ext_type & RTP_EXT_TYPE_FRAMERATE ){ + memcpy(_session->_ext_header->_hd_ext + 1, _session->_ext_header->_hd_ext, _session->_ext_header->_ext_len); + _session->_exthdr_framerate = 0; + _session->_ext_header->_hd_ext = realloc( _session->_ext_header->_hd_ext, sizeof( rtp_ext_header_t ) * _session->_ext_header->_ext_len ); + assert(_session->_ext_header->_hd_ext); + } + } + + return SUCCESS; +} + +int rtp_add_framerate_marking ( rtp_session_t* _session, uint32_t _value ) +{ + if ( !_session ) + return FAILURE; + + rtp_ext_header_t* _ext_header = _session->_ext_header; + _session->_exthdr_framerate = 0; + + if ( ! ( _ext_header ) ) { + _session->_ext_header = calloc (sizeof(rtp_ext_header_t), 1); + assert(_session->_ext_header); + + _session->_extension = 1; + _session->_ext_header->_ext_len = 1; + _ext_header = _session->_ext_header; + _session->_ext_header->_hd_ext = calloc(sizeof(uint32_t), 1); + assert(_session->_ext_header->_hd_ext); + } else { /* If there is need for more headers this will be needed to change */ + if ( !(_ext_header->_ext_type & RTP_EXT_TYPE_FRAMERATE) ){ + /* it's position is at 2nd place by default */ + _session->_exthdr_framerate ++; + + /* Update length */ + _ext_header->_ext_len++; + + /* Allocate the value */ + _ext_header->_hd_ext = realloc(_ext_header->_hd_ext, sizeof(rtp_ext_header_t) * _ext_header->_ext_len); + assert(_ext_header->_hd_ext); + + } + } + + /* Add flag */ + _ext_header->_ext_type |= RTP_EXT_TYPE_FRAMERATE; + + _ext_header->_hd_ext[_session->_exthdr_framerate] = _value; + + return SUCCESS; +} + + +int rtp_remove_framerate_marking ( rtp_session_t* _session ) +{ + if ( _session->_extension == 0 || ! ( _session->_ext_header ) ) { + t_perror ( RTP_ERROR_INVALID_EXTERNAL_HEADER ); + return FAILURE; + } + + if ( !( _session->_ext_header->_ext_type & RTP_EXT_TYPE_FRAMERATE ) ) { + t_perror ( RTP_ERROR_INVALID_EXTERNAL_HEADER ); + return FAILURE; + } + + _session->_ext_header->_ext_type &= ~RTP_EXT_TYPE_FRAMERATE; /* Remove the flag */ + _session->_exthdr_framerate = -1; /* Remove identifier */ + _session->_ext_header->_ext_len --; + + /* Check if extension is empty */ + if ( _session->_ext_header->_ext_type == 0 ){ + + free ( _session->_ext_header->_hd_ext ); + free ( _session->_ext_header ); + + _session->_ext_header = NULL; /* It's very important */ + _session->_extension = 0; + + } else if ( !_session->_ext_header->_ext_len ) { + + /* this will also be needed to change if there are more than 2 headers */ + _session->_ext_header->_hd_ext = realloc( _session->_ext_header->_hd_ext, sizeof( rtp_ext_header_t ) * _session->_ext_header->_ext_len ); + assert(_session->_ext_header->_hd_ext); + + } + + return SUCCESS; +} + +uint32_t rtp_get_framerate_marking ( rtp_ext_header_t* _header ) +{ + if ( _header->_ext_len == 1 ){ + return _header->_hd_ext[0]; + } else { + return _header->_hd_ext[1]; + } +} + +int rtp_set_prefix ( rtp_session_t* _session, uint8_t* _prefix, uint16_t _prefix_length ) +{ + if ( !_session ) + return FAILURE; + + if ( _session->_prefix ) { + free ( _session->_prefix ); + } + + _session->_prefix = calloc ( ( sizeof * _session->_prefix ), _prefix_length ); + assert(_session->_prefix); + + t_memcpy ( _session->_prefix, _prefix, _prefix_length ); + _session->_prefix_length = _prefix_length; + + return SUCCESS; +} -- cgit v1.2.3