Commit 443f7c8f authored by Mickaël Turnel's avatar Mickaël Turnel

Added a NACK context object allowing to resend missing packets

parent 40c6a2cc
......@@ -24,6 +24,7 @@ set(HEADER_FILES
b64.h
event.h
logging.h
nack.h
ortp.h
payloadtype.h
port.h
......
ortp_includedir=$(includedir)/ortp
ortp_include_HEADERS=str_utils.h rtpsession.h rtp.h port.h logging.h \
ortp_include_HEADERS=str_utils.h rtpsession.h rtp.h port.h logging.h nack.h \
ortp.h telephonyevents.h sessionset.h payloadtype.h rtpprofile.h rtpsignaltable.h \
rtcp.h event.h utils.h \
b64.h
......
......@@ -54,6 +54,7 @@ struct _OrtpEventData{
uint32_t received_rtt_character;
bool_t congestion_detected;
float video_bandwidth_available;
int jitter_min_size_for_nack;
} info;
};
......@@ -86,6 +87,7 @@ ORTP_PUBLIC OrtpEventType ortp_event_get_type(const OrtpEvent *ev);
#define ORTP_EVENT_ZRTP_PEER_VERSION_OBSOLETE 18
#define ORTP_EVENT_NEW_VIDEO_BANDWIDTH_ESTIMATION_AVAILABLE 19
#define ORTP_EVENT_ICE_DEACTIVATION_NEEDED 20
#define ORTP_EVENT_JITTER_UPDATE_FOR_NACK 21
ORTP_PUBLIC OrtpEventData * ortp_event_get_data(OrtpEvent *ev);
ORTP_PUBLIC void ortp_event_destroy(OrtpEvent *ev);
......
/*
* The oRTP library is an RTP (Realtime Transport Protocol - rfc3550) implementation with additional features.
* Copyright (C) 2017 Belledonne Communications SARL
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef NACK_H
#define NACK_H
#include <bctoolbox/list.h>
#include <bctoolbox/port.h>
#include <ortp/port.h>
#include <ortp/rtpsession.h>
#ifdef __cplusplus
extern "C"{
#endif
struct _OrtpNackContext {
RtpSession *session;
OrtpEvDispatcher *ev_dispatcher;
RtpTransportModifier *rtp_modifier;
queue_t sent_packets;
bctbx_mutex_t sent_packets_mutex;
int max_packets;
int min_jitter_before_nack;
bool_t decrease_jitter_timer_running;
uint64_t decrease_jitter_timer_start;
};
typedef struct _OrtpNackContext OrtpNackContext;
ORTP_PUBLIC OrtpNackContext *ortp_nack_context_new(OrtpEvDispatcher *evt);
ORTP_PUBLIC void ortp_nack_context_destroy(OrtpNackContext *ctx);
ORTP_PUBLIC void ortp_nack_context_set_max_packet(OrtpNackContext *ctx, int max);
ORTP_PUBLIC void ortp_nack_context_process_timer(OrtpNackContext *ctx);
#ifdef __cplusplus
}
#endif
#endif
......@@ -47,6 +47,7 @@
#define ORTP_AVPF_FEATURE_NONE 0
#define ORTP_AVPF_FEATURE_TMMBR (1 << 0)
#define ORTP_AVPF_FEATURE_GENERIC_NACK (1 << 1)
#define ORTP_AVPF_FEATURE_IMMEDIATE_NACK (1 << 2)
typedef enum {
......@@ -104,7 +105,8 @@ typedef struct _JitterControl
uint32_t remote_ts_start;
uint32_t diverged_start_ts;
bool_t is_diverging;
bool_t pad[3];
bool_t jb_size_updated;
bool_t pad[2];
} JitterControl;
typedef struct _WaitPoint
......@@ -212,6 +214,7 @@ typedef struct OrtpRtcpSendAlgorithm {
typedef struct OrtpRtcpFbConfiguration {
bool_t generic_nack_enabled;
bool_t immediate_nack_enabled;
bool_t tmmbr_enabled;
} OrtpRtcpFbConfiguration;
......@@ -332,7 +335,8 @@ typedef struct _RtpStream
uint32_t snd_time_offset;/*the scheduler time when the application send its first timestamp*/
uint32_t snd_ts_offset; /* the first application timestamp sent by the application */
uint32_t snd_rand_offset; /* a random number added to the user offset to make the stream timestamp*/
uint32_t snd_last_ts; /* the last stream timestamp sended */
uint32_t snd_last_ts; /* the last stream timestamp sent */
uint16_t snd_last_nack; /* the last nack sent when in immediate mode */
uint32_t rcv_time_offset; /*the scheduler time when the application ask for its first timestamp*/
uint32_t rcv_ts_offset; /* the first stream timestamp */
uint32_t rcv_query_ts_offset; /* the first user timestamp asked by the application */
......@@ -768,6 +772,7 @@ ORTP_PUBLIC void meta_rtp_transport_set_endpoint(RtpTransport *transport,RtpTran
ORTP_PUBLIC void meta_rtp_transport_destroy(RtpTransport *tp);
ORTP_PUBLIC void meta_rtp_transport_append_modifier(RtpTransport *tp,RtpTransportModifier *tpm);
ORTP_PUBLIC void meta_rtp_transport_prepend_modifier(RtpTransport *tp,RtpTransportModifier *tpm);
ORTP_PUBLIC int rtp_session_splice(RtpSession *session, RtpSession *to_session);
ORTP_PUBLIC int rtp_session_unsplice(RtpSession *session, RtpSession *to_session);
......
......@@ -40,6 +40,7 @@ set(ORTP_SOURCE_FILES_C
jitterctl.c
kalmanrls.c
logging.c
nack.c
netsim.c
ortp.c
payloadtype.c
......
......@@ -27,6 +27,7 @@ libortp_la_SOURCES= \
kalmanrls.c \
jitterctl.c jitterctl.h \
logging.c \
nack.c \
netsim.c \
ortp.c \
payloadtype.c \
......
......@@ -154,7 +154,8 @@ bool_t rtp_session_jitter_buffer_enabled(const RtpSession *session){
void rtp_session_set_jitter_buffer_params(RtpSession *session, const JBParameters *par){
if (par == &session->rtp.jittctl.params) return;
memcpy(&session->rtp.jittctl.params, par, sizeof (JBParameters));
rtp_session_init_jitter_buffer(session);
//rtp_session_init_jitter_buffer(session);
session->rtp.jittctl.jb_size_updated = TRUE;
}
void rtp_session_get_jitter_buffer_params(RtpSession *session, JBParameters *par){
......@@ -238,7 +239,6 @@ static uint32_t jitter_control_local_ts_to_remote_ts_rls(JitterControl *ctl, uin
void jitter_control_new_packet_rls(JitterControl *ctl, uint32_t packet_ts, uint32_t cur_str_ts){
int32_t diff = packet_ts - cur_str_ts;
int deviation;
bool_t jb_size_updated = FALSE;
if (ctl->is_diverging){
int32_t elapsed = (int32_t)(cur_str_ts - ctl->diverged_start_ts);
......@@ -297,20 +297,20 @@ void jitter_control_new_packet_rls(JitterControl *ctl, uint32_t packet_ts, uint3
jitter_control_update_interarrival_jitter(ctl, diff);
cur_str_ts -= ctl->local_ts_start;
if (ctl->params.adaptive){
if (ctl->params.adaptive || ctl->jb_size_updated){
bool_t max_updated = ortp_extremum_record_max(&ctl->max_ts_deviation, cur_str_ts, (float)deviation);
float max_deviation = MAX(ortp_extremum_get_previous(&ctl->max_ts_deviation), ortp_extremum_get_current(&ctl->max_ts_deviation));
if (max_updated && max_deviation > ctl->adapt_jitt_comp_ts){
ctl->adapt_jitt_comp_ts=(int)max_deviation;
jb_size_updated = TRUE;
ctl->jb_size_updated = TRUE;
}else if (max_deviation < ctl->params.ramp_threshold/100.f*ctl->adapt_jitt_comp_ts){
/*Jitter is decreasing. Make a smooth descent to avoid dropping lot of packets*/
if ( (int32_t)(cur_str_ts - ctl->adapt_refresh_prev_ts) > ((ctl->params.ramp_refresh_ms*ctl->clock_rate)/1000)) {
ctl->adapt_jitt_comp_ts -= (ctl->params.ramp_step_ms * ctl->clock_rate) / 1000;
jb_size_updated = TRUE;
ctl->jb_size_updated = TRUE;
}
}
if (jb_size_updated){
if (ctl->jb_size_updated){
int min_size_ts = (ctl->params.min_size * ctl->clock_rate) / 1000;
int max_size_ts = (ctl->params.max_size * ctl->clock_rate) / 1000;
if (ctl->adapt_jitt_comp_ts < min_size_ts){
......@@ -319,11 +319,11 @@ void jitter_control_new_packet_rls(JitterControl *ctl, uint32_t packet_ts, uint3
ctl->adapt_jitt_comp_ts = max_size_ts;
}
ctl->adapt_refresh_prev_ts = cur_str_ts;
jb_size_updated = TRUE;
ctl->jb_size_updated = FALSE;
}
}
if (time_for_log(ctl, cur_str_ts)){
ortp_message("jitter buffer %s: target-size: %f ms, effective-size: %f (min: %i nom: %i, max: %i)",jb_size_updated ? "updated" : "stable",
ortp_message("jitter buffer %s: target-size: %f ms, effective-size: %f (min: %i nom: %i, max: %i)",ctl->jb_size_updated ? "updated" : "stable",
((float)ctl->adapt_jitt_comp_ts/(float)ctl->clock_rate)*1000.0,
ctl->jitter_buffer_mean_size,
ctl->params.min_size, ctl->params.nom_size, ctl->params.max_size);
......
/*
* The oRTP library is an RTP (Realtime Transport Protocol - rfc3550) implementation with additional features.
* Copyright (C) 2017 Belledonne Communications SARL
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "ortp/logging.h"
#include "ortp/nack.h"
#define DECREASE_JITTER_DELAY 5000
static mblk_t *find_packet_with_sequence_number(const queue_t *q, const uint16_t seq_number) {
mblk_t *tmp;
for (tmp = qbegin(q); !qend(q, tmp); tmp = qnext(q, tmp)) {
if (ntohs(rtp_get_seqnumber(tmp)) == seq_number) {
return tmp;
}
}
return NULL;
}
static void generic_nack_received(const OrtpEventData *evd, OrtpNackContext *ctx) {
if (rtcp_is_RTPFB(evd->packet) && rtcp_RTPFB_get_type(evd->packet) == RTCP_RTPFB_NACK) {
RtpTransport *rtpt = NULL;
rtcp_fb_generic_nack_fci_t *fci;
uint16_t pid, blp, seq;
mblk_t *lost_msg;
/* get RTP transport from session */
rtp_session_get_transports(ctx->session, &rtpt, NULL);
fci = rtcp_RTPFB_generic_nack_get_fci(evd->packet);
pid = rtcp_fb_generic_nack_fci_get_pid(fci);
blp = rtcp_fb_generic_nack_fci_get_blp(fci);
bctbx_mutex_lock(&ctx->sent_packets_mutex);
lost_msg = find_packet_with_sequence_number(&ctx->sent_packets, pid);
if (lost_msg != NULL) {
meta_rtp_transport_modifier_inject_packet_to_send(rtpt, ctx->rtp_modifier, lost_msg, 0);
ortp_message("OrtpNackContext [%p]: Resending missing packet with seq=%hu", ctx, pid);
} else {
ortp_warning("OrtpNackContext [%p]: Cannot find missing packet with seq=%hu", ctx, pid);
}
++pid;
for (seq = blp; seq != 0; seq >>= 1, ++pid) {
if (seq & 1) {
lost_msg = find_packet_with_sequence_number(&ctx->sent_packets, pid);
if (lost_msg != NULL) {
meta_rtp_transport_modifier_inject_packet_to_send(rtpt, ctx->rtp_modifier, lost_msg, 0);
ortp_message("OrtpNackContext [%p]: Resending missing packet with seq=%hu", ctx, pid);
} else {
ortp_warning("OrtpNackContext [%p]: Cannot find missing packet with seq=%hu", ctx, pid);
}
}
}
bctbx_mutex_unlock(&ctx->sent_packets_mutex);
}
}
static int ortp_nack_rtp_process_on_send(RtpTransportModifier *t, mblk_t *msg) {
OrtpNackContext *userData = (OrtpNackContext *) t->data;
if (rtp_get_version(msg) == 2) {
bctbx_mutex_lock(&userData->sent_packets_mutex);
// Remove the oldest packet if the cache is full
if (userData->sent_packets.q_mcount >= userData->max_packets) {
mblk_t *erase = qbegin(&userData->sent_packets);
remq(&userData->sent_packets, erase);
if (erase != NULL) freemsg(erase);
}
// Stock the packet before sending it
putq(&userData->sent_packets, dupmsg(msg));
//ortp_message("OrtpNackContext [%p]: Stocking packet with pid=%hu (seq=%hu)", userData, ntohs(rtp_get_seqnumber(msg)), userData->session->rtp.snd_seq);
bctbx_mutex_unlock(&userData->sent_packets_mutex);
}
return (int) msgdsize(msg);
}
static int ortp_nack_rtp_process_on_receive(RtpTransportModifier *t, mblk_t *msg) {
return (int) msgdsize(msg);
}
static int ortp_nack_rtcp_process_on_send(RtpTransportModifier *t, mblk_t *msg) {
mblk_t *pullmsg = dupmsg(msg);
msgpullup(pullmsg, (size_t)-1);
do {
if (rtcp_is_RTPFB(pullmsg) && rtcp_RTPFB_get_type(pullmsg) == RTCP_RTPFB_NACK) {
OrtpNackContext *userData = (OrtpNackContext *) t->data;
OrtpEvent *ev;
OrtpEventData *evd;
JBParameters jitter_params;
int rtt = (int) userData->session->rtt;
if (rtt == 0) rtt = 200;
rtp_session_get_jitter_buffer_params(userData->session, &jitter_params);
if (userData->min_jitter_before_nack == 0) {
/* We keep the min_size at the first sent NACK to know at which value it has to come back */
userData->min_jitter_before_nack = jitter_params.min_size;
}
if (userData->min_jitter_before_nack + rtt != jitter_params.min_size) {
if (userData->min_jitter_before_nack + rtt >= jitter_params.max_size) {
jitter_params.min_size = jitter_params.max_size - 20;
} else {
jitter_params.min_size = userData->min_jitter_before_nack + rtt;
}
rtp_session_set_jitter_buffer_params(userData->session, &jitter_params);
ortp_message("OrtpNackContext [%p]: Sending NACK... increasing jitter min size to %dms", userData, jitter_params.min_size);
// Send an event that the video jitter has been updated so that we can update the audio too
ev = ortp_event_new(ORTP_EVENT_JITTER_UPDATE_FOR_NACK);
evd = ortp_event_get_data(ev);
evd->info.jitter_min_size_for_nack = jitter_params.min_size;
rtp_session_dispatch_event(userData->session, ev);
}
// Start the timer that will decrase the min jitter if no NACK is sent
userData->decrease_jitter_timer_running = TRUE;
userData->decrease_jitter_timer_start = ortp_get_cur_time_ms();
break;
}
} while (rtcp_next_packet(pullmsg));
freemsg(pullmsg);
return (int) msgdsize(msg);
}
static int ortp_nack_rtcp_process_on_receive(RtpTransportModifier *t, mblk_t *msg) {
return (int) msgdsize(msg);
}
static void ortp_nack_transport_modifier_destroy(RtpTransportModifier *tp) {
ortp_free(tp);
}
static void ortp_nack_transport_modifier_new(OrtpNackContext* ctx, RtpTransportModifier **rtpt, RtpTransportModifier **rtcpt ) {
if (rtpt) {
*rtpt = ortp_new0(RtpTransportModifier, 1);
(*rtpt)->data = ctx; /* back link to get access to the other fields of the OrtpNackContext from the RtpTransportModifier structure */
(*rtpt)->t_process_on_send = ortp_nack_rtp_process_on_send;
(*rtpt)->t_process_on_receive = ortp_nack_rtp_process_on_receive;
(*rtpt)->t_destroy = ortp_nack_transport_modifier_destroy;
}
if (rtcpt) {
*rtcpt = ortp_new0(RtpTransportModifier, 1);
(*rtcpt)->data = ctx; /* back link to get access to the other fields of the OrtpNackContext from the RtpTransportModifier structure */
(*rtcpt)->t_process_on_send = ortp_nack_rtcp_process_on_send;
(*rtcpt)->t_process_on_receive = ortp_nack_rtcp_process_on_receive;
(*rtcpt)->t_destroy = ortp_nack_transport_modifier_destroy;
}
}
static OrtpNackContext *ortp_nack_configure_context(OrtpNackContext *userData) {
RtpTransport *rtpt = NULL, *rtcpt = NULL;
RtpTransportModifier *rtp_modifier, *rtcp_modifier;
rtp_session_get_transports(userData->session, &rtpt, &rtcpt);
ortp_nack_transport_modifier_new(userData, &rtp_modifier, &rtcp_modifier);
meta_rtp_transport_append_modifier(rtpt, rtp_modifier);
meta_rtp_transport_prepend_modifier(rtcpt, rtcp_modifier);
userData->rtp_modifier = rtp_modifier;
return userData;
}
OrtpNackContext *ortp_nack_context_new(OrtpEvDispatcher *evt) {
OrtpNackContext *userData;
userData = ortp_new0(OrtpNackContext, 1);
userData->session = evt->session;
userData->ev_dispatcher = evt;
userData->max_packets = 100;
qinit(&userData->sent_packets);
bctbx_mutex_init(&userData->sent_packets_mutex, NULL);
ortp_ev_dispatcher_connect(userData->ev_dispatcher
, ORTP_EVENT_RTCP_PACKET_RECEIVED
, RTCP_RTPFB
, (OrtpEvDispatcherCb)generic_nack_received
, userData);
return ortp_nack_configure_context(userData);
}
void ortp_nack_context_destroy(OrtpNackContext *ctx) {
ortp_ev_dispatcher_disconnect(ctx->ev_dispatcher
, ORTP_EVENT_RTCP_PACKET_RECEIVED
, RTCP_RTPFB
, (OrtpEvDispatcherCb)generic_nack_received);
bctbx_mutex_lock(&ctx->sent_packets_mutex);
flushq(&ctx->sent_packets, FLUSHALL);
bctbx_mutex_unlock(&ctx->sent_packets_mutex);
bctbx_mutex_destroy(&ctx->sent_packets_mutex);
ortp_free(ctx);
}
void ortp_nack_context_set_max_packet(OrtpNackContext *ctx, int max) {
ctx->max_packets = max;
}
void ortp_nack_context_process_timer(OrtpNackContext *ctx) {
if (ctx->decrease_jitter_timer_running) {
uint64_t current_time = ortp_get_cur_time_ms();
if (current_time - ctx->decrease_jitter_timer_start >= DECREASE_JITTER_DELAY) {
OrtpEvent *ev;
OrtpEventData *evd;
JBParameters jitter_params;
ortp_message("OrtpNackContext [%p]: No NACK sent in the last %d seconds, decreasing jitter min size to %dms...", ctx, DECREASE_JITTER_DELAY / 1000, ctx->min_jitter_before_nack);
rtp_session_get_jitter_buffer_params(ctx->session, &jitter_params);
jitter_params.min_size = ctx->min_jitter_before_nack;
rtp_session_set_jitter_buffer_params(ctx->session, &jitter_params);
// Send an event that the video jitter has been updated so that we can update the audio too
ev = ortp_event_new(ORTP_EVENT_JITTER_UPDATE_FOR_NACK);
evd = ortp_event_get_data(ev);
evd->info.jitter_min_size_for_nack = jitter_params.min_size;
rtp_session_dispatch_event(ctx->session, ev);
ctx->decrease_jitter_timer_running = FALSE;
}
}
}
......@@ -124,6 +124,42 @@ static void update_rtcp_xr_stat_summary(RtpSession *session, mblk_t *mp, uint32_
session->rtcp_xr_stats.last_jitter_diff_since_last_stat_summary = diff;
}
static void check_for_seq_number_gap(RtpSession *session, rtp_header_t *rtp) {
uint16_t pid;
uint16_t i;
/*don't check anything before first packet delivered*/
if (session->flags & RTP_SESSION_FIRST_PACKET_DELIVERED
&& RTP_SEQ_IS_STRICTLY_GREATER_THAN(rtp->seq_number, session->rtp.rcv_last_seq + 1)
&& RTP_SEQ_IS_STRICTLY_GREATER_THAN(rtp->seq_number, session->rtp.snd_last_nack + 1)
) {
uint16_t first_missed_seq = session->rtp.rcv_last_seq + 1;
uint16_t diff;
if (first_missed_seq <= session->rtp.snd_last_nack) {
first_missed_seq = session->rtp.snd_last_nack + 1;
}
diff = rtp->seq_number - first_missed_seq;
pid = first_missed_seq;
for (i = 0; i <= (diff / 16); i++) {
uint16_t seq;
uint16_t blp = 0;
for (seq = pid + 1; (seq < rtp->seq_number) && ((seq - pid) < 16); seq++) {
blp |= (1 << (seq - pid - 1));
}
rtp_session_send_rtcp_fb_generic_nack(session, pid, blp);
pid = seq;
}
}
if (session->rtp.snd_last_nack < rtp->seq_number) {
/* We update the last_nack since we received this packet we don't need a nack for it */
session->rtp.snd_last_nack = rtp->seq_number;
}
}
void rtp_session_rtp_parse(RtpSession *session, mblk_t *mp, uint32_t local_str_ts, struct sockaddr *addr, socklen_t addrlen)
{
int i;
......@@ -329,6 +365,16 @@ void rtp_session_rtp_parse(RtpSession *session, mblk_t *mp, uint32_t local_str_t
}
}
if ((rtp_session_avpf_enabled(session) == TRUE)
&& (rtp_session_avpf_feature_enabled(session, ORTP_AVPF_FEATURE_GENERIC_NACK) == TRUE)
&& (rtp_session_avpf_feature_enabled(session, ORTP_AVPF_FEATURE_IMMEDIATE_NACK) == TRUE)) {
/*
* If immediate nack is enabled then we check for missing packets here instead of
* rtp_session_recvm_with_ts
*/
check_for_seq_number_gap(session, rtp);
}
if (queue_packet(&session->rtp.rq,session->rtp.jittctl.params.max_packets,mp,rtp,&discarded,&duplicate))
jitter_control_update_size(&session->rtp.jittctl,&session->rtp.rq);
stats->discarded+=discarded;
......
......@@ -1285,7 +1285,17 @@ rtp_session_recvm_with_ts (RtpSession * session, uint32_t user_ts)
{
payload_type_changed_notify(session, rtp->paytype);
}
if ((rtp_session_avpf_enabled(session) == TRUE)
&& (rtp_session_avpf_feature_enabled(session, ORTP_AVPF_FEATURE_GENERIC_NACK) == TRUE)
&& (rtp_session_avpf_feature_enabled(session, ORTP_AVPF_FEATURE_IMMEDIATE_NACK) == FALSE)) {
/*
* If immediate nack is disabled then we check for missing packets here instead of
* rtp_session_rtp_parse
*/
check_for_seq_number_gap(session, rtp);
}
/* update the packet's timestamp so that it corrected by the
adaptive jitter buffer mechanism */
if (session->rtp.jittctl.params.adaptive){
......@@ -2475,6 +2485,16 @@ void meta_rtp_transport_append_modifier(RtpTransport *tp,RtpTransportModifier *t
tpm->session = tp->session;
}
}
void meta_rtp_transport_prepend_modifier(RtpTransport *tp,RtpTransportModifier *tpm) {
MetaRtpTransportImpl *m = (MetaRtpTransportImpl*)tp->data;
tpm->transport = tp;
m->modifiers=o_list_prepend(m->modifiers, tpm);
if(m->has_set_session) {
tpm->session = tp->session;
}
}
bool_t rtp_session_get_symmetric_rtp(const RtpSession *session) {
return session->symmetric_rtp;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment