blob: db4cfa78da00ebdcb266e36632cde0b79901bd89 [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2000-2026
* All rights reserved
*
* This file is part of GPAC / IETF RTP/RTSP/SDP sub-project
*
* GPAC is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* GPAC 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <gpac/internal/ietf_dev.h>
#ifndef GPAC_DISABLE_STREAMING
#define MAX_RTP_SN 0x10000
GF_EXPORT
GF_RTPChannel *gf_rtp_new_ex(const char *netcap_id)
{
GF_RTPChannel *tmp;
GF_SAFEALLOC(tmp, GF_RTPChannel);
if (!tmp)
return NULL;
tmp->first_SR = 1;
tmp->SSRC = gf_rand();
tmp->bs_r = gf_bs_new("d", 1, GF_BITSTREAM_READ);
tmp->bs_w = gf_bs_new("d", 1, GF_BITSTREAM_WRITE);
tmp->netcap_id = netcap_id;
return tmp;
}
GF_EXPORT
GF_RTPChannel *gf_rtp_new()
{
return gf_rtp_new_ex(NULL);
}
GF_EXPORT
void gf_rtp_del(GF_RTPChannel *ch)
{
if (!ch) return;
if (ch->rtp) gf_sk_del(ch->rtp);
if (ch->rtcp) gf_sk_del(ch->rtcp);
if (ch->net_info.source) gf_free(ch->net_info.source);
if (ch->net_info.destination) gf_free(ch->net_info.destination);
if (ch->net_info.Profile) gf_free(ch->net_info.Profile);
if (ch->po) gf_rtp_reorderer_del(ch->po);
if (ch->send_buffer) gf_free(ch->send_buffer);
if (ch->CName) gf_free(ch->CName);
if (ch->s_name) gf_free(ch->s_name);
if (ch->s_email) gf_free(ch->s_email);
if (ch->s_location) gf_free(ch->s_location);
if (ch->s_phone) gf_free(ch->s_phone);
if (ch->s_tool) gf_free(ch->s_tool);
if (ch->s_note) gf_free(ch->s_note);
if (ch->s_priv) gf_free(ch->s_priv);
if (ch->bs_r) gf_bs_del(ch->bs_r);
if (ch->bs_w) gf_bs_del(ch->bs_w);
memset(ch, 0, sizeof(GF_RTPChannel));
gf_free(ch);
}
GF_EXPORT
GF_Err gf_rtp_setup_transport(GF_RTPChannel *ch, GF_RTSPTransport *trans_info, const char *remote_address)
{
if (!ch || !trans_info) return GF_BAD_PARAM;
//assert we have at least ONE source ID
if (!trans_info->source && !remote_address) return GF_BAD_PARAM;
if (ch->net_info.destination) gf_free(ch->net_info.destination);
ch->net_info.destination = NULL;
if (ch->net_info.Profile) gf_free(ch->net_info.Profile);
ch->net_info.Profile = NULL;
if (ch->net_info.source) gf_free(ch->net_info.source);
ch->net_info.source = NULL;
memcpy(&ch->net_info, trans_info, sizeof(GF_RTSPTransport));
if (trans_info->destination)
ch->net_info.destination = gf_strdup(trans_info->destination);
if (trans_info->Profile)
ch->net_info.Profile = gf_strdup(trans_info->Profile);
if (!ch->net_info.IsUnicast && trans_info->destination) {
ch->net_info.source = gf_strdup(trans_info->destination);
if (ch->net_info.client_port_first) {
ch->net_info.port_first = ch->net_info.client_port_first;
ch->net_info.port_last = ch->net_info.client_port_last;
}
} else if (trans_info->source) {
ch->net_info.source = gf_strdup(trans_info->source);
} else {
ch->net_info.source = gf_strdup(remote_address);
}
if (trans_info->SSRC) {
if (trans_info->is_sender) {
ch->SSRC = trans_info->SSRC;
} else {
ch->SenderSSRC = trans_info->SSRC;
}
}
//check we REALLY have unicast or multicast
if (gf_sk_is_multicast_address(ch->net_info.source) && ch->net_info.IsUnicast) return GF_SERVICE_ERROR;
return GF_OK;
}
GF_EXPORT
void gf_rtp_reset_buffers(GF_RTPChannel *ch)
{
if (ch->rtp) gf_sk_reset(ch->rtp);
if (ch->rtcp) gf_sk_reset(ch->rtcp);
if (ch->po) gf_rtp_reorderer_reset(ch->po);
ch->first_SR = 1;
}
GF_EXPORT
void gf_rtp_reset_ssrc(GF_RTPChannel *ch)
{
if (ch) ch->SenderSSRC = 0;
}
GF_EXPORT
void gf_rtp_enable_nat_keepalive(GF_RTPChannel *ch, u32 nat_timeout)
{
if (ch) {
ch->nat_keepalive_time_period = nat_timeout;
ch->last_nat_keepalive_time = 0;
}
}
GF_EXPORT
GF_Err gf_rtp_set_info_rtp(GF_RTPChannel *ch, u32 seq_num, u32 rtp_time, u32 ssrc)
{
if (!ch) return GF_BAD_PARAM;
if (seq_num) {
ch->CurrentTime = rtp_time;
ch->rtp_time = 1 + rtp_time;
} else {
ch->CurrentTime = ch->rtp_time = 0;
}
ch->last_pck_sn = 0;
ch->rtp_first_SN = seq_num;
ch->num_sn_loops = 0;
//reset RTCP
ch->ntp_init = 0;
ch->first_SR = 1;
if (ssrc) ch->SenderSSRC = ssrc;
ch->total_pck = ch->total_bytes = ch->last_num_pck_rcv = ch->last_num_pck_expected = ch->last_num_pck_loss = ch->tot_num_pck_rcv = ch->tot_num_pck_expected = ch->rtcp_bytes_sent = 0;
return GF_OK;
}
GF_EXPORT
GF_Err gf_rtp_stop(GF_RTPChannel *ch)
{
if (!ch) return GF_BAD_PARAM;
if (ch->rtp) gf_sk_del(ch->rtp);
ch->rtp = NULL;
if (ch->rtcp) gf_sk_del(ch->rtcp);
ch->rtcp = NULL;
if (ch->po) gf_rtp_reorderer_del(ch->po);
ch->po = NULL;
return GF_OK;
}
GF_EXPORT
GF_Err gf_rtp_set_ssm(GF_RTPChannel *ch, const char **src_ip_inc, u32 nb_src_ip_inc, const char **src_ip_exc, u32 nb_src_ip_exc)
{
if (!ch) return GF_BAD_PARAM;
ch->ssm = src_ip_inc;
ch->nb_ssm = nb_src_ip_inc;
ch->ssmx = src_ip_exc;
ch->nb_ssmx = nb_src_ip_exc;
return GF_OK;
}
GF_EXPORT
GF_Err gf_rtp_initialize(GF_RTPChannel *ch, u32 UDPBufferSize, Bool IsSource, u32 PathMTU, u32 ReorederingSize, u32 MaxReorderDelay, char *local_ip)
{
u16 port;
GF_Err e;
if (!ch || (IsSource && !PathMTU)) return GF_BAD_PARAM;
if (ch->rtp) gf_sk_del(ch->rtp);
ch->rtp = NULL;
if (ch->rtcp) gf_sk_del(ch->rtcp);
ch->rtcp = NULL;
if (ch->po) gf_rtp_reorderer_del(ch->po);
ch->po = NULL;
ch->CurrentTime = 0;
ch->rtp_time = 0;
//create sockets for RTP/AVP profile only
if (ch->net_info.Profile &&
( !stricmp(ch->net_info.Profile, GF_RTSP_PROFILE_RTP_AVP)
|| !stricmp(ch->net_info.Profile, "RTP/AVP/UDP")
|| !stricmp(ch->net_info.Profile, "RTP/SAVP")
)
) {
//destination MUST be specified for unicast
if (IsSource && ch->net_info.IsUnicast && !ch->net_info.destination) return GF_BAD_PARAM;
/* forcing unicast when the address is not a multicast */
if (!ch->net_info.IsUnicast) {
if (IsSource) {
if (ch->net_info.destination && !gf_sk_is_multicast_address(ch->net_info.destination)) {
ch->net_info.IsUnicast = GF_TRUE;
}
} else {
if (ch->net_info.source && !gf_sk_is_multicast_address(ch->net_info.source)) {
ch->net_info.IsUnicast = GF_TRUE;
}
}
}
//
// RTP
//
ch->rtp = gf_sk_new_ex(GF_SOCK_TYPE_UDP, ch->netcap_id);
if (!ch->rtp) return GF_IP_NETWORK_FAILURE;
if (ch->net_info.IsUnicast) {
//if client, bind and connect the socket
if (!IsSource) {
port = ch->net_info.port_first;
if (!port) port = ch->net_info.client_port_first;
/*if a destination address was given (rtsd) use it*/
if (!local_ip && ch->net_info.destination) local_ip = ch->net_info.destination;
e = gf_sk_bind(ch->rtp, local_ip, ch->net_info.client_port_first, ch->net_info.source, port, GF_SOCK_REUSE_PORT);
if (e) return e;
}
//else bind and set remote destination
else {
if (!ch->net_info.port_first) ch->net_info.port_first = ch->net_info.client_port_first;
e = gf_sk_bind(ch->rtp, local_ip,ch->net_info.port_first, ch->net_info.destination, ch->net_info.client_port_first, GF_SOCK_REUSE_PORT|GF_SOCK_IS_SENDER);
if (e) return e;
}
} else {
//Bind to multicast (auto-join the group).
//we do not bind the socket if this is a source-only channel because some servers
//don't like that on local loop ...
if (!IsSource && (ch->nb_ssm || ch->nb_ssmx)) {
e = gf_sk_setup_multicast_ex(ch->rtp, ch->net_info.source, ch->net_info.port_first, ch->net_info.TTL, GF_FALSE, local_ip, ch->ssm, ch->nb_ssm, ch->ssmx, ch->nb_ssmx);
} else {
e = gf_sk_setup_multicast(ch->rtp, ch->net_info.source, ch->net_info.port_first, ch->net_info.TTL, GF_FALSE, local_ip);
}
if (e) return e;
}
if (UDPBufferSize) gf_sk_set_buffer_size(ch->rtp, IsSource, UDPBufferSize);
//create re-ordering queue for UDP only, and receive
if (ReorederingSize && !IsSource) {
if (!MaxReorderDelay) MaxReorderDelay = 200;
ch->po = gf_rtp_reorderer_new(ReorederingSize, MaxReorderDelay, ch->TimeScale);
}
//
// RTCP
//
ch->rtcp = gf_sk_new_ex(GF_SOCK_TYPE_UDP, ch->netcap_id);
if (!ch->rtcp) return GF_IP_NETWORK_FAILURE;
if (ch->net_info.IsUnicast) {
if (!IsSource) {
port = ch->net_info.port_last;
if (!port) port = ch->net_info.client_port_last;
/*if a destination address was given (rtsd) use it*/
if (!local_ip && ch->net_info.destination) local_ip = ch->net_info.destination;
e = gf_sk_bind(ch->rtcp, local_ip, ch->net_info.client_port_last, ch->net_info.source, port, GF_SOCK_REUSE_PORT);
if (e) return e;
} else {
e = gf_sk_bind(ch->rtcp, local_ip, ch->net_info.port_last, ch->net_info.destination, ch->net_info.client_port_last, GF_SOCK_REUSE_PORT|GF_SOCK_IS_SENDER);
if (e) return e;
}
} else {
if (!ch->net_info.port_last) ch->net_info.port_last = ch->net_info.client_port_last;
//Bind to multicast (auto-join the group)
if (!IsSource && (ch->nb_ssm || ch->nb_ssmx)) {
e = gf_sk_setup_multicast_ex(ch->rtcp, ch->net_info.source, ch->net_info.port_last, ch->net_info.TTL, GF_FALSE, local_ip, ch->ssm, ch->nb_ssm, ch->ssmx, ch->nb_ssmx);
} else {
e = gf_sk_setup_multicast(ch->rtcp, ch->net_info.source, ch->net_info.port_last, ch->net_info.TTL, GF_FALSE, local_ip);
}
if (e) return e;
}
}
if (IsSource) {
if (ch->send_buffer) gf_free(ch->send_buffer);
ch->send_buffer_size = PathMTU + 12;
ch->send_buffer = (char *) gf_malloc(sizeof(char) * ch->send_buffer_size);
}
//format CNAME if not done yet
if (!ch->CName) {
//this is the real CName setup
if (!ch->rtp) {
ch->CName = gf_strdup("mpeg4rtp");
} else {
char name[GF_MAX_IP_NAME_LEN];
size_t start;
gf_get_user_name(name);
if (strlen(name)) gf_strcat(name, "@");
start = strlen(name);
//get host IP or loopback if error
if (gf_sk_get_local_ip(ch->rtp, name+start) != GF_OK) gf_strlcpy(name+start, "127.0.0.1", GF_MAX_IP_NAME_LEN-start);
ch->CName = gf_strdup(name);
}
}
#ifndef GPAC_DISABLE_LOG
if (gf_log_tool_level_on(GF_LOG_RTP, GF_LOG_DEBUG)) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Packet Log Format: SSRC SequenceNumber TimeStamp NTP@recvTime deviance, Jiter, PckLost PckTotal BytesTotal\n"));
}
#endif
return GF_OK;
}
/*get the UTC time expressed in RTP timescale*/
u32 gf_rtp_channel_time(GF_RTPChannel *ch)
{
u32 sec, frac, res;
gf_net_get_ntp(&sec, &frac);
res = ( (u32) ( (frac>>26)*ch->TimeScale) ) >> 6;
res += ch->TimeScale*(sec - ch->ntp_init);
return (u32) res;
}
u32 gf_rtp_get_report_time()
{
u32 sec, frac;
gf_net_get_ntp(&sec, &frac);
/*in units of 1/65536 seconds*/
return (u32) ( (frac>>16) + 0x10000L*sec );
}
void gf_rtp_get_next_report_time(GF_RTPChannel *ch)
{
Double d;
/*offset between .5 and 1.5 sec*/
d = 0.5 + ((Double) gf_rand()) / ((Double) RAND_MAX);
/*of a minimal 5sec interval expressed in 1/65536 of a sec*/
d = 5.0 * d * 65536;
/*we should estimate bandwidth sharing too*/
ch->next_report_time = gf_rtp_get_report_time() + (u32) d;
}
GF_EXPORT
u32 gf_rtp_read_flush(GF_RTPChannel *ch, u8 *buffer, u32 buffer_size)
{
char *pck;
u32 res = 0;
if (!ch->po) return 0;
//pck queue may need to be flushed
pck = (char *) gf_rtp_reorderer_get(ch->po, &res, GF_TRUE, NULL);
if (pck) {
memcpy(buffer, pck, res);
gf_free(pck);
}
return res;
}
GF_EXPORT
u32 gf_rtp_flush_rtp(GF_RTPChannel *ch, u8 *buffer, u32 buffer_size)
{
u32 res;
char *pck;
//only if the socket exist (otherwise RTSP interleaved channel)
if (!ch || !ch->rtp || !ch->po) return 0;
//pck queue may need to be flushed
pck = (char *) gf_rtp_reorderer_get(ch->po, &res, GF_FALSE, NULL);
if (pck) {
memcpy(buffer, pck, res);
gf_free(pck);
return res;
}
return 0;
}
GF_EXPORT
u32 gf_rtp_read_rtp(GF_RTPChannel *ch, u8 *buffer, u32 buffer_size)
{
GF_Err e;
u32 seq_num, res;
//only if the socket exist (otherwise RTSP interleaved channel)
if (!ch || !ch->rtp) return 0;
if (ch->no_select) {
e = gf_sk_receive_no_select(ch->rtp, buffer, buffer_size, &res);
} else {
e = gf_sk_receive(ch->rtp, buffer, buffer_size, &res);
}
if (!res || e || (res < 12)) {
res = 0;
}
if (res) {
ch->total_bytes+=res;
ch->total_pck++;
}
//add the packet to our Queue if any
if (ch->po) {
Bool is_disc;
char *pck;
if (res) {
seq_num = ((((u32)buffer[2]) << 8) & 0xFF00) | (buffer[3] & 0xFF);
gf_rtp_reorderer_add(ch->po, (void *) buffer, res, seq_num);
}
//pck queue may need to be flushed
pck = (char *) gf_rtp_reorderer_get(ch->po, &res, GF_FALSE, &is_disc);
if (pck) {
memcpy(buffer, pck, res);
gf_free(pck);
}
if (is_disc && !ch->disc_state) {
ch->disc_state = 1;
} else if (ch->disc_state) {
ch->disc_state = 2;
ch->first_SR = 1;
}
}
/*monitor keep-alive period*/
if (ch->nat_keepalive_time_period && !ch->send_interleave) {
u32 now = gf_sys_clock();
if (res) {
ch->last_nat_keepalive_time = now;
} else {
if (now - ch->last_nat_keepalive_time >= ch->nat_keepalive_time_period) {
char rtp_nat[12];
rtp_nat[0] = (u8) 0xC0;
rtp_nat[1] = ch->PayloadType;
rtp_nat[2] = (ch->last_pck_sn>>8)&0xFF;
rtp_nat[3] = (ch->last_pck_sn)&0xFF;
rtp_nat[4] = (ch->last_pck_ts>>24)&0xFF;
rtp_nat[5] = (ch->last_pck_ts>>16)&0xFF;
rtp_nat[6] = (ch->last_pck_ts>>8)&0xFF;
rtp_nat[7] = (ch->last_pck_ts)&0xFF;
rtp_nat[8] = (ch->SenderSSRC>>24)&0xFF;
rtp_nat[9] = (ch->SenderSSRC>>16)&0xFF;
rtp_nat[10] = (ch->SenderSSRC>>8)&0xFF;
rtp_nat[11] = (ch->SenderSSRC)&0xFF;
e = gf_sk_send(ch->rtp, rtp_nat, 12);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTP] Error sending NAT keep-alive packet: %s - disabling NAT\n", gf_error_to_string(e) ));
ch->nat_keepalive_time_period = 0;
} else {
GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Sending NAT keep-alive packet - response %s\n", gf_error_to_string(e) ));
}
ch->last_nat_keepalive_time = now;
}
}
}
return res;
}
GF_EXPORT
GF_Err gf_rtp_decode_rtp(GF_RTPChannel *ch, u8 *pck, u32 pck_size, GF_RTPHeader *rtp_hdr, u32 *PayloadStart)
{
GF_Err e;
s32 deviance, delta;
u32 CurrSeq, LastSeq;
u32 ntp, lost, low16;
if (!rtp_hdr) return GF_BAD_PARAM;
e = GF_OK;
gf_bs_reassign_buffer(ch->bs_r, pck, pck_size);
//we need to uncompress the RTP header
rtp_hdr->Version = gf_bs_read_int(ch->bs_r, 2);
if (rtp_hdr->Version != 2) return GF_NOT_SUPPORTED;
rtp_hdr->Padding = gf_bs_read_int(ch->bs_r, 1);
rtp_hdr->Extension = gf_bs_read_int(ch->bs_r, 1);
rtp_hdr->CSRCCount = gf_bs_read_int(ch->bs_r, 4);
rtp_hdr->Marker = gf_bs_read_int(ch->bs_r, 1);
rtp_hdr->PayloadType = gf_bs_read_int(ch->bs_r, 7);
/*we don't support multiple CSRC now. Only one source (the server) is allowed*/
if (rtp_hdr->CSRCCount) return GF_NOT_SUPPORTED;
/*SeqNum*/
rtp_hdr->SequenceNumber = gf_bs_read_u16(ch->bs_r);
/*TS*/
rtp_hdr->TimeStamp = gf_bs_read_u32(ch->bs_r);
/*SSRC*/
rtp_hdr->SSRC = gf_bs_read_u32(ch->bs_r);
/*update RTP time if we didn't get the info*/
if (!ch->rtp_time) {
ch->rtp_time = 1 + rtp_hdr->TimeStamp;
ch->rtp_first_SN = rtp_hdr->SequenceNumber;
ch->num_sn_loops = 0;
}
if (ch->first_SR && !ch->SenderSSRC && rtp_hdr->SSRC) {
ch->SenderSSRC = rtp_hdr->SSRC;
GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTP] Assigning SSRC to %d for stream on port %u because none was specified through SDP/RTSP\n", ch->SenderSSRC, ch->net_info.client_port_first));
}
if (!ch->ntp_init && ch->SenderSSRC && (ch->SenderSSRC != rtp_hdr->SSRC) ) {
GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTP] SSRC mismatch: %d vs %d\n", rtp_hdr->SSRC, ch->SenderSSRC));
return GF_IP_NETWORK_EMPTY;
}
/*RTP specs annexe A.8*/
if (!ch->ntp_init) {
gf_net_get_ntp(&ch->ntp_init, &lost);
ch->last_pck_sn = (u32) rtp_hdr->SequenceNumber-1;
}
/*this is a loop in SN - add it*/
if ( (ch->last_pck_sn + 1 > rtp_hdr->SequenceNumber)
&& (rtp_hdr->SequenceNumber >= ch->last_pck_sn + MAX_RTP_SN/2)) {
ch->num_sn_loops += 1;
}
if (ch->last_SR_rtp_time) {
s64 diff_sec;
u32 sec = ch->last_SR_NTP_sec;
s64 frac;
frac = (s64) rtp_hdr->TimeStamp;
frac -= (s64) ch->last_SR_rtp_time;
if (frac<0) frac += 0xFFFFFFFF;
diff_sec = frac / ch->TimeScale;
frac -= diff_sec*ch->TimeScale;
frac *= 0xFFFFFFFF;
frac /= ch->TimeScale;
frac += ch->last_SR_NTP_frac;
if (frac>0xFFFFFFFF) {
sec += 1;
frac -= 0xFFFFFFFF;
}
rtp_hdr->recomputed_ntp_ts = sec + (s32) diff_sec;
rtp_hdr->recomputed_ntp_ts <<= 32;
rtp_hdr->recomputed_ntp_ts |= frac;
}
ntp = gf_rtp_channel_time(ch);
deviance = ntp - rtp_hdr->TimeStamp;
delta = deviance - ch->last_deviance;
ch->last_deviance = deviance;
if (delta < 0) delta = -delta;
ch->Jitter += delta - ( (ch->Jitter + 8) >> 4);
lost = 0;
LastSeq = ch->last_pck_sn;
CurrSeq = (u32) rtp_hdr->SequenceNumber;
ch->packet_loss = GF_FALSE;
/*next sequential pck*/
if ( ( (LastSeq + 1) & 0xffff ) == CurrSeq ) {
ch->last_num_pck_rcv += 1;
ch->last_num_pck_expected += 1;
}
/*repeated pck*/
else if ( (LastSeq & 0xffff ) == CurrSeq ) {
ch->last_num_pck_rcv += 1;
}
/*drop pck*/
else {
low16 = LastSeq & 0xffff;
if ( CurrSeq > low16 )
lost = CurrSeq - low16;
else
lost = 0xffff - low16 + CurrSeq + 1;
ch->last_num_pck_expected += lost;
ch->last_num_pck_rcv += 1;
ch->last_num_pck_loss += lost;
ch->packet_loss = GF_TRUE;
}
ch->last_pck_sn = CurrSeq;
#ifndef GPAC_DISABLE_LOG
if (gf_log_tool_level_on(GF_LOG_RTP, GF_LOG_DEBUG)) {
ch->total_bytes += pck_size-12;
GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP Packet]\t%d\t%d\t%u\t%d\t%d\t%d\t%d\t%d\t%d\n",
ch->SenderSSRC,
rtp_hdr->SequenceNumber,
rtp_hdr->TimeStamp,
ntp,
delta,
ch->Jitter >> 4,
lost,
ch->total_pck,
ch->total_bytes
));
}
#endif
//we work with no CSRC so payload offset is always 12
*PayloadStart = 12;
if (rtp_hdr->Extension) {
u16 ext_size;
char *payl = pck + *PayloadStart;
ext_size = payl[2];
ext_size <<= 8;
ext_size |= payl[3];
*PayloadStart += 4 + (ext_size * 4);
}
//store the time
ch->CurrentTime = rtp_hdr->TimeStamp;
return e;
}
GF_EXPORT
Double gf_rtp_get_current_time(GF_RTPChannel *ch)
{
Double ret;
if (!ch || !ch->rtp_time) return 0.0;
ret = (Double) ch->CurrentTime;
ret -= (Double) (ch->rtp_time-1);
ret /= ch->TimeScale;
return ret;
}
GF_EXPORT
GF_Err gf_rtp_send_packet(GF_RTPChannel *ch, GF_RTPHeader *rtp_hdr, u8 *pck, u32 pck_size, Bool fast_send)
{
GF_Err e;
u32 i, Start;
char *hdr = NULL;
if (!ch || !rtp_hdr
|| !ch->send_buffer
|| !pck
|| (rtp_hdr->CSRCCount > 15)) return GF_BAD_PARAM;
if (rtp_hdr->CSRCCount) fast_send = GF_FALSE;
if (12 + pck_size + 4*rtp_hdr->CSRCCount > ch->send_buffer_size)
return GF_IO_ERR;
if (fast_send) {
hdr = pck - 12;
gf_bs_reassign_buffer(ch->bs_w, hdr, 12);
} else {
gf_bs_reassign_buffer(ch->bs_w, ch->send_buffer, ch->send_buffer_size);
}
//write header
gf_bs_write_int(ch->bs_w, rtp_hdr->Version, 2);
gf_bs_write_int(ch->bs_w, rtp_hdr->Padding, 1);
gf_bs_write_int(ch->bs_w, rtp_hdr->Extension, 1);
gf_bs_write_int(ch->bs_w, rtp_hdr->CSRCCount, 4);
gf_bs_write_int(ch->bs_w, rtp_hdr->Marker, 1);
gf_bs_write_int(ch->bs_w, rtp_hdr->PayloadType, 7);
gf_bs_write_u16(ch->bs_w, rtp_hdr->SequenceNumber);
gf_bs_write_u32(ch->bs_w, rtp_hdr->TimeStamp);
gf_bs_write_u32(ch->bs_w, ch->SSRC);
for (i=0; i<rtp_hdr->CSRCCount; i++) {
gf_bs_write_u32(ch->bs_w, rtp_hdr->CSRC[i]);
}
//nb: RTP header is always aligned
Start = (u32) gf_bs_get_position(ch->bs_w);
if (ch->send_interleave) {
if (fast_send) {
e = ch->send_interleave(ch->interleave_cbk1, ch->interleave_cbk2, GF_FALSE, hdr, pck_size+12);
} else {
memcpy(ch->send_buffer + Start, pck, pck_size);
e = ch->send_interleave(ch->interleave_cbk1, ch->interleave_cbk2, GF_FALSE, ch->send_buffer, Start + pck_size);
}
}
//copy payload
else if (fast_send) {
e = gf_sk_send(ch->rtp, hdr, pck_size+12);
} else {
memcpy(ch->send_buffer + Start, pck, pck_size);
e = gf_sk_send(ch->rtp, ch->send_buffer, Start + pck_size);
}
if (e) return e;
//Update RTCP for sender reports
ch->pck_sent_since_last_sr += 1;
if (ch->first_SR) {
//get a new report time
gf_rtp_get_next_report_time(ch);
ch->num_payload_bytes = 0;
ch->num_pck_sent = 0;
ch->first_SR = 0;
}
ch->num_payload_bytes += pck_size;
ch->num_pck_sent += 1;
//store timing
ch->last_pck_ts = rtp_hdr->TimeStamp;
gf_net_get_ntp(&ch->last_pck_ntp_sec, &ch->last_pck_ntp_frac);
if (!ch->no_auto_rtcp) gf_rtp_send_rtcp_report(ch);
return GF_OK;
}
GF_EXPORT
u32 gf_rtp_is_unicast(GF_RTPChannel *ch)
{
if (!ch) return 0;
return ch->net_info.IsUnicast;
}
GF_EXPORT
u32 gf_rtp_is_interleaved(GF_RTPChannel *ch)
{
if (!ch || !ch->net_info.Profile) return 0;
return ch->net_info.IsInterleaved;
}
GF_EXPORT
u32 gf_rtp_get_clockrate(GF_RTPChannel *ch)
{
if (!ch || !ch->TimeScale) return 0;
return ch->TimeScale;
}
#if 0 //unused
u32 gf_rtp_is_active(GF_RTPChannel *ch)
{
if (!ch) return 0;
if (!ch->rtp_first_SN && !ch->rtp_time) return 0;
return 1;
}
#endif
GF_EXPORT
u8 gf_rtp_get_low_interleave_id(GF_RTPChannel *ch)
{
if (!ch || !ch->net_info.IsInterleaved) return 0;
return ch->net_info.rtpID;
}
GF_EXPORT
u8 gf_rtp_get_hight_interleave_id(GF_RTPChannel *ch)
{
if (!ch || !ch->net_info.IsInterleaved) return 0;
return ch->net_info.rtcpID;
}
#define RTP_DEFAULT_FIRSTPORT 7040
static u16 NextAvailablePort = 0;
GF_EXPORT
GF_Err gf_rtp_set_ports(GF_RTPChannel *ch, u16 first_port)
{
u16 p;
GF_Socket *sock;
if (!ch) return GF_BAD_PARAM;
if (!NextAvailablePort) {
NextAvailablePort = first_port ? first_port : RTP_DEFAULT_FIRSTPORT;
}
p = NextAvailablePort;
if (ch->net_info.client_port_first) return GF_OK;
sock = gf_sk_new_ex(GF_SOCK_TYPE_UDP, ch->netcap_id);
if (!sock) return GF_IO_ERR;
/*should be way enough (more than 100 rtp streams open on the machine)*/
while (1) {
/*try to bind without reuse. If fails this means the port is used on the machine, don't reuse it*/
GF_Err e = gf_sk_bind(sock, NULL, p, NULL, 0, 0);
if (e==GF_OK) break;
if (e!=GF_IP_CONNECTION_FAILURE) {
gf_sk_del(sock);
return GF_IP_NETWORK_FAILURE;
}
p+=2;
}
gf_sk_del(sock);
ch->net_info.client_port_first = p;
ch->net_info.client_port_last = p + 1;
NextAvailablePort = p + 2;
return GF_OK;
}
GF_EXPORT
GF_Err gf_rtp_setup_payload(GF_RTPChannel *ch, u32 PayloadType, u32 ClockRate)
{
if (!ch || !PayloadType || !ClockRate) return GF_BAD_PARAM;
ch->PayloadType = PayloadType;
ch->TimeScale = ClockRate;
return GF_OK;
}
GF_EXPORT
const GF_RTSPTransport *gf_rtp_get_transport(GF_RTPChannel *ch)
{
if (!ch) return NULL;
return &ch->net_info;
}
#if 0 //unused
u32 gf_rtp_get_local_ssrc(GF_RTPChannel *ch)
{
if (!ch) return 0;
return ch->SSRC;
}
#endif
#if 0
"#RTP log format:\n"
"#RTP SenderSSRC RTP_TimeStamp RTP_SeqNum NTP@Recv Deviance Jitter NbLost NbTotPck NbTotBytes\n"
"#RTCP Sender reports log format:\n"
"#RTCP-SR SenderSSRC RTP_TimeStamp@NTP NbTotPck NbTotBytes NTP\n"
"#RTCP Receiver reports log format:\n"
"#RTCP-RR StreamSSRC Jitter ExtendedSeqNum ExpectDiff LossDiff NTP\n"
#endif
GF_EXPORT
Float gf_rtp_get_loss(GF_RTPChannel *ch)
{
if (!ch->tot_num_pck_expected) return 0.0f;
return 100.0f - (100.0f * ch->tot_num_pck_rcv) / ch->tot_num_pck_expected;
}
GF_EXPORT
u32 gf_rtp_get_tcp_bytes_sent(GF_RTPChannel *ch)
{
return ch->rtcp_bytes_sent;
}
GF_EXPORT
void gf_rtp_get_ports(GF_RTPChannel *ch, u16 *rtp_port, u16 *rtcp_port)
{
if (ch->net_info.client_port_first) {
if (rtp_port) *rtp_port = ch->net_info.client_port_first;
if (rtcp_port) *rtcp_port = ch->net_info.client_port_last;
} else {
if (rtp_port) *rtp_port = ch->net_info.port_first;
if (rtcp_port) *rtcp_port = ch->net_info.port_last;
}
}
/*
RTP packet reorderer
*/
#define SN_CHECK_OFFSET 0x0A
GF_EXPORT
GF_RTPReorder *gf_rtp_reorderer_new(u32 MaxCount, u32 MaxDelay, u32 timescale)
{
GF_RTPReorder *tmp;
if (MaxCount <= 1 || !MaxDelay) return NULL;
GF_SAFEALLOC(tmp , GF_RTPReorder);
if (!tmp) return NULL;
tmp->MaxCount = MaxCount;
tmp->MaxDelay = MaxDelay;
tmp->TimeScale = timescale;
return tmp;
}
GF_EXPORT
void gf_rtp_reorderer_del(GF_RTPReorder *po)
{
gf_rtp_reorderer_reset(po);
gf_free(po);
}
GF_EXPORT
void gf_rtp_reorderer_reset(GF_RTPReorder *po)
{
GF_POItem *item;
if (!po) return;
item = po->in;
while (item) {
GF_POItem *next = item->next;
gf_free(item->pck);
gf_free(item);
item = next;
}
po->head_seqnum = 0;
po->Count = 0;
po->IsInit = 0;
po->in = NULL;
}
GF_EXPORT
GF_Err gf_rtp_reorderer_add(GF_RTPReorder *po, const void * pck, u32 pck_size, u32 pck_seqnum)
{
GF_POItem *it, *cur;
u32 bounds;
if (!po) return GF_BAD_PARAM;
it = (GF_POItem *) gf_malloc(sizeof(GF_POItem));
it->pck_seq_num = pck_seqnum;
it->next = NULL;
it->size = pck_size;
it->pck = gf_malloc(pck_size);
memcpy(it->pck, pck, pck_size);
/*do NOT reset timeout when receiving a new packet, this would make the re-orderer wait forever to output a packet in case of losses*/
//no input, this packet will be the input
if (!po->in) {
//the seq num was not initialized
if (!po->head_seqnum) {
po->head_seqnum = pck_seqnum;
} else if (!po->IsInit) {
//this is not in our current range for init
if (ABSDIFF(po->head_seqnum, pck_seqnum) > SN_CHECK_OFFSET) goto discard;
po->IsInit = 1;
}
po->in = it;
po->Count += 1;
return GF_OK;
}
//this is 16 bitr seq num, as we work with RTP only for now
bounds = 0;
if ( (po->head_seqnum >= 0xf000 ) || (po->head_seqnum <= 0x1000) ) bounds = 0x2000;
//first check the head of the list
//same seq num, we drop
if (po->in->pck_seq_num == pck_seqnum) goto discard;
if ( ( (u16) (pck_seqnum + bounds) <= (u16) (po->in->pck_seq_num + bounds) ) ) {
u8 *buffer = (u8*)pck;
u32 ts_pck = GF_4CC(buffer[4], buffer[5], buffer[6], buffer[7]);
buffer = (u8*)po->in->pck;
u32 ts_in = GF_4CC(buffer[4], buffer[5], buffer[6], buffer[7]);
s32 diff = ts_in;
diff-=ts_pck;
if (diff<0) diff=-diff;
if (po->TimeScale && ((u32) diff > 2*po->TimeScale)) {
cur = po->in;
while (cur->next) cur = cur->next;
cur->next = it;
po->Count += 1;
po->disc = it;
GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[rtp] Packet Reorderer: discontinuity detected last ts %u vs new %u\n", ts_in, ts_pck));
} else {
it->next = po->in;
po->in = it;
po->Count += 1;
GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[rtp] Packet Reorderer: inserting packet %d at head\n", pck_seqnum));
}
return GF_OK;
}
//no, insert at the right place
cur = po->in;
while (1) {
//same seq num, we drop
if (cur->pck_seq_num == pck_seqnum) goto discard;
//end of list
if (!cur->next) {
cur->next = it;
po->Count += 1;
if (pck_seqnum != 1+cur->pck_seq_num) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[rtp] Packet Reorderer: Appending packet %d (last %d)\n", pck_seqnum, cur->pck_seq_num));
}
return GF_OK;
}
//are we in the bounds ??
if ( ( (u16) (cur->pck_seq_num + bounds) < (u16) (pck_seqnum + bounds) )
&& ( (u16) (pck_seqnum + bounds) < (u16) (cur->next->pck_seq_num + bounds)) ) {
//insert
it->next = cur->next;
cur->next = it;
po->Count += 1;
GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[rtp] Packet Reorderer: Inserting packet %d\n", pck_seqnum));
//done
return GF_OK;
}
cur = cur->next;
}
discard:
gf_free(it->pck);
gf_free(it);
GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[rtp] Packet Reorderer: Dropping packet %d\n", pck_seqnum));
return GF_OK;
}
//retrieve the first available packet. Note that the behavior will be undefined if the first
//ever received packet if its SeqNum was unknown
//the BUFFER is yours, you must delete it
GF_EXPORT
void *gf_rtp_reorderer_get(GF_RTPReorder *po, u32 *pck_size, Bool force_flush, Bool *is_disc)
{
GF_POItem *t;
u32 bounds;
void *ret;
if (is_disc) *is_disc = GF_FALSE;
if (!po || !pck_size) return NULL;
*pck_size = 0;
//empty queue
if (!po->in) return NULL;
if (po->disc) {
gf_assert(po->in);
goto send_it;
}
//check we have received the first packet
if ( po->head_seqnum && po->MaxCount
&& (po->MaxCount > po->Count)
&& (po->in->pck_seq_num != po->head_seqnum))
return NULL;
//no entry
if (!po->in->next) goto check_timeout;
bounds = 0;
if ( (po->head_seqnum >= 0xf000 ) || (po->head_seqnum <= 0x1000) ) bounds = 0x2000;
//release the output if SN in order or maxCount reached
if (( (u16) (po->in->pck_seq_num + bounds + 1) == (u16) (po->in->next->pck_seq_num + bounds))
|| (po->MaxCount && (po->Count >= po->MaxCount)) ) {
#ifndef GPAC_DISABLE_LOG
if (po->in->pck_seq_num + 1 != po->in->next->pck_seq_num)
GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[rtp] WARNING Packet Loss: Sending %d out of the queue but next is %d\n", po->in->pck_seq_num, po->in->next->pck_seq_num ));
#endif
goto send_it;
}
//update timing
else {
check_timeout:
if (force_flush) goto send_it;
if (!po->LastTime) {
po->LastTime = gf_sys_clock();
GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[rtp] Packet Reorderer: starting timeout at %d\n", po->LastTime));
return NULL;
}
//if exceeding the delay send the head
if (gf_sys_clock() - po->LastTime >= po->MaxDelay) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[rtp] Packet Reorderer: Forcing output after %d ms wait (max allowed %d)\n", gf_sys_clock() - po->LastTime, po->MaxDelay));
goto send_it;
}
}
return NULL;
send_it:
if (!po->in->next || (po->in->pck_seq_num+1 != po->in->next->pck_seq_num)) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[rtp] Packet Reorderer: Fetching %d\n", po->in->pck_seq_num));
}
*pck_size = po->in->size;
t = po->in;
po->in = po->in->next;
//no other output. reset the head seqnum
po->head_seqnum = po->in ? po->in->pck_seq_num : 0;
po->Count -= 1;
if (po->in == po->disc) {
if (is_disc) *is_disc = GF_TRUE;
po->disc = NULL;
}
//release the item
ret = t->pck;
gf_free(t);
return ret;
}
GF_Err gf_rtp_set_interleave_callbacks(GF_RTPChannel *ch, gf_rtp_tcp_callback RTP_TCPCallback, void *cbk1, void *cbk2)
{
if (!ch) return GF_BAD_PARAM;
ch->send_interleave = RTP_TCPCallback;
ch->interleave_cbk1 = cbk1;
ch->interleave_cbk2 = cbk2;
return GF_OK;
}
Bool gf_rtp_is_disc(GF_RTPChannel *ch)
{
if (ch->disc_state == 2) {
ch->disc_state = 0;
return GF_TRUE;
}
return GF_FALSE;
}
#endif /*GPAC_DISABLE_STREAMING*/