/*filter setup if no session (rtp only)*/ M4Err RP_SetupChannel(RTPStream *ch, ChannelDescribe *ch_desc) { M4Err resp; /*assign ES_ID of the channel*/ if (ch_desc && !ch->ES_ID && ch_desc->ES_ID) ch->ES_ID = ch_desc->ES_ID; ch->status = RTP_Setup; /*assign channel handle if not done*/ if (ch_desc && ch->channel) { assert(ch->channel == ch_desc->channel); } else if (!ch->channel) { assert(ch_desc); assert(ch_desc->channel); ch->channel = ch_desc->channel; } /*no session , setup for pure rtp*/ if (!ch->rtsp) { ch->connected = 1; /*init rtp*/ resp = RP_InitStream(ch, 0), /*send confirmation to user*/ RP_ConfirmChannelConnect(ch, resp); } else { RP_Setup(ch); } return M4OK; }
void RP_ProcessSetup(RTPSession *sess, RTSPCommand *com, M4Err e) { RTPStream *ch; u32 i; RTSPTransport *trans; ch = com->user_data; if (e) goto exit; switch (sess->rtsp_rsp->ResponseCode) { case NC_RTSP_OK: break; case NC_RTSP_Not_Found: e = M4ChannelNotFound; goto exit; default: e = M4ServiceError; goto exit; } e = M4ServiceError; if (!ch) goto exit; /*transport setup: break at the first correct transport */ for (i=0; i<ChainGetCount(sess->rtsp_rsp->Transports); i++) { trans = ChainGetEntry(sess->rtsp_rsp->Transports, 0); e = RTP_SetupTransport(ch->rtp_ch, trans, RTSP_GetServerName(sess->session)); if (!e) break; } if (e) goto exit; e = RP_InitStream(ch, 0); if (e) goto exit; ch->status = RTP_Connected; //in case this is TCP channel, setup callbacks ch->is_interleaved = RTP_IsInterleaved(ch->rtp_ch); if (ch->is_interleaved) { RTSP_SetCallbackOnInterleaving(sess->session, RP_DataOnTCP); } exit: /*confirm only on first connect, otherwise this is a re-SETUP of the rtsp session, not the channel*/ if (! ch->connected) { ch->connected = 1; RP_ConfirmChannelConnect(ch, e); } com->user_data = NULL; }
static GF_Err RP_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) { RTPStream *ch; RTPClient *priv = (RTPClient *)plug->priv; if (com->command_type==GF_NET_SERVICE_HAS_AUDIO) { u32 i; for (i=0; i<gf_list_count(priv->channels); i++) { ch = gf_list_get(priv->channels, i); if (ch->depacketizer->sl_map.StreamType==GF_STREAM_AUDIO) return GF_OK; } return GF_NOT_SUPPORTED; } if (com->command_type==GF_NET_SERVICE_MIGRATION_INFO) { RP_SaveSessionState(priv); priv->session_migration=1; if (priv->session_state_data) { com->migrate.data = priv->session_state_data; com->migrate.data_len = strlen(priv->session_state_data); return GF_OK; } return GF_NOT_SUPPORTED; } /*ignore commands other than channels one*/ if (!com->base.on_channel) { if (com->command_type==GF_NET_IS_CACHABLE) return GF_OK; return GF_NOT_SUPPORTED; } ch = RP_FindChannel(priv, com->base.on_channel, 0, NULL, 0); if (!ch) return GF_STREAM_NOT_FOUND; switch (com->command_type) { case GF_NET_CHAN_SET_PULL: if (ch->rtp_ch || ch->rtsp || !ch->control) return GF_NOT_SUPPORTED; /*embedded channels work in pull mode*/ if (strstr(ch->control, "data:application/")) return GF_OK; return GF_NOT_SUPPORTED; case GF_NET_CHAN_INTERACTIVE: /*looks like pure RTP / multicast etc, not interactive*/ if (!ch->control) return GF_NOT_SUPPORTED; /*emulated broadcast mode*/ else if (ch->flags & RTP_FORCE_BROADCAST) return GF_NOT_SUPPORTED; /*regular rtsp mode*/ else if (ch->flags & RTP_HAS_RANGE) return GF_OK; /*embedded data*/ else if (strstr(ch->control, "application")) return GF_OK; return GF_NOT_SUPPORTED; case GF_NET_CHAN_BUFFER: if (!(ch->rtp_ch || ch->rtsp || !ch->control)) { com->buffer.max = com->buffer.min = 0; } else { const char *opt; /*amount of buffering in ms*/ opt = gf_modules_get_option((GF_BaseInterface *)plug, "Network", "BufferLength"); com->buffer.max = opt ? atoi(opt) : 1000; /*rebuffer low limit in ms - if the amount of buffering is less than this, rebuffering will never occur*/ opt = gf_modules_get_option((GF_BaseInterface *)plug, "Network", "RebufferLength"); if (opt) com->buffer.min = atoi(opt); else com->buffer.min = 500; if (com->buffer.min >= com->buffer.max ) com->buffer.min = 0; } return GF_OK; case GF_NET_CHAN_DURATION: com->duration.duration = (ch->flags & RTP_HAS_RANGE) ? (ch->range_end - ch->range_start) : 0; return GF_OK; /*RTP channel config is done upon connection, once the complete SL mapping is known however we must store some info not carried in SDP*/ case GF_NET_CHAN_CONFIG: if (com->cfg.frame_duration) ch->depacketizer->sl_hdr.au_duration = com->cfg.frame_duration; ch->ts_res = com->cfg.sl_config.timestampResolution; return GF_OK; case GF_NET_CHAN_PLAY: GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Processing play on channel @%08x - %s\n", ch, ch->rtsp ? "RTSP control" : "No control (RTP)" )); /*is this RTSP or direct RTP?*/ ch->flags &= ~RTP_EOS; if (ch->rtsp) { if (ch->status==RTP_SessionResume) { const char *opt = gf_modules_get_option((GF_BaseInterface *) plug, "Streaming", "SessionMigrationPause"); if (opt && !strcmp(opt, "yes")) { ch->status = RTP_Connected; com->play.start_range = ch->current_start; } else { ch->status = RTP_Running; return GF_OK; } } RP_UserCommand(ch->rtsp, ch, com); } else { ch->status = RTP_Running; if (ch->rtp_ch) { /*technically we shouldn't attempt to synchronize streams based on RTP, we should use RTCP/ However it may happen that the RTCP traffic is absent ...*/ ch->check_rtp_time = RTP_SET_TIME_RTP; ch->rtcp_init = 0; gf_mx_p(priv->mx); RP_InitStream(ch, (ch->flags & RTP_CONNECTED) ? 1 : 0); gf_mx_v(priv->mx); gf_rtp_set_info_rtp(ch->rtp_ch, 0, 0, 0); } else { /*direct channel, store current start*/ ch->current_start = com->play.start_range; ch->flags |= GF_RTP_NEW_AU; gf_rtp_depacketizer_reset(ch->depacketizer, 0); } } return GF_OK; case GF_NET_CHAN_STOP: /*is this RTSP or direct RTP?*/ if (ch->rtsp) { if (! ch->owner->session_migration) { RP_UserCommand(ch->rtsp, ch, com); } } else { ch->status = RTP_Connected; ch->owner->last_ntp = 0; } ch->rtcp_init = 0; return GF_OK; case GF_NET_CHAN_SET_SPEED: case GF_NET_CHAN_PAUSE: case GF_NET_CHAN_RESUME: assert(ch->rtsp); RP_UserCommand(ch->rtsp, ch, com); return GF_OK; case GF_NET_CHAN_GET_DSI: if (ch->depacketizer && ch->depacketizer->sl_map.configSize) { com->get_dsi.dsi_len = ch->depacketizer->sl_map.configSize; com->get_dsi.dsi = (char*)gf_malloc(sizeof(char)*com->get_dsi.dsi_len); memcpy(com->get_dsi.dsi, ch->depacketizer->sl_map.config, sizeof(char)*com->get_dsi.dsi_len); } else { com->get_dsi.dsi = NULL; com->get_dsi.dsi_len = 0; } return GF_OK; case GF_NET_GET_STATS: memset(&com->net_stats, 0, sizeof(GF_NetComStats)); if (ch->rtp_ch) { u32 time; Float bps; com->net_stats.pck_loss_percentage = gf_rtp_get_loss(ch->rtp_ch); if (ch->flags & RTP_INTERLEAVED) { com->net_stats.multiplex_port = gf_rtsp_get_session_port(ch->rtsp->session); com->net_stats.port = gf_rtp_get_low_interleave_id(ch->rtp_ch); com->net_stats.ctrl_port = gf_rtp_get_hight_interleave_id(ch->rtp_ch); } else { com->net_stats.multiplex_port = 0; gf_rtp_get_ports(ch->rtp_ch, &com->net_stats.port, &com->net_stats.ctrl_port); } if (ch->stat_stop_time) { time = ch->stat_stop_time - ch->stat_start_time; } else { time = gf_sys_clock() - ch->stat_start_time; } bps = 8.0f * ch->rtp_bytes; bps *= 1000; bps /= time; com->net_stats.bw_down = (u32) bps; bps = 8.0f * ch->rtcp_bytes; bps *= 1000; bps /= time; com->net_stats.ctrl_bw_down = (u32) bps; bps = 8.0f * gf_rtp_get_tcp_bytes_sent(ch->rtp_ch); bps *= 1000; bps /= time; com->net_stats.ctrl_bw_up = (u32) bps; } return GF_OK; } return GF_NOT_SUPPORTED; }
static GF_Err RP_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) { u32 ESID; RTPStream *ch; RTSPSession *sess; char *es_url; RTPClient *priv = (RTPClient *)plug->priv; if (upstream) return GF_NOT_SUPPORTED; GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Connecting channel @%08x - %s\n", channel, url)); ch = RP_FindChannel(priv, channel, 0, (char *) url, 0); if (ch && (ch->status != RTP_Disconnected) ) return GF_SERVICE_ERROR; es_url = NULL; sess = NULL; if (strstr(url, "ES_ID=")) { sscanf(url, "ES_ID=%ud", &ESID); /*first case: simple URL (same namespace)*/ ch = RP_FindChannel(priv, NULL, ESID, NULL, 0); /*this should not happen, the sdp must describe all streams in the service*/ if (!ch) return GF_STREAM_NOT_FOUND; /*assign app channel*/ ch->channel = channel; sess = ch->rtsp; } /*rtsp url - create a session if needed*/ else if (!strnicmp(url, "rtsp://", 7) || !strnicmp(url, "rtspu://", 8)) { sess = RP_CheckSession(priv, (char *) url); if (!sess) sess = RP_NewSession(priv, (char *) url); es_url = (char *) url; } /*data: url*/ else if (strstr(url, "data:application/mpeg4-od-au;base64") || strstr(url, "data:application/mpeg4-bifs-au;base64") || strstr(url, "data:application/mpeg4-es-au;base64") ) { GF_SAFEALLOC(ch, RTPStream); ch->control = gf_strdup(url); ch->owner = priv; ch->channel = channel; ch->status = RTP_Connected; /*register*/ gf_list_add(priv->channels, ch); RP_ConfirmChannelConnect(ch, GF_OK); return GF_OK; } /*session migration resume - don't send data to the server*/ if (ch->status==RTP_SessionResume) { ch->flags |= RTP_CONNECTED; RP_InitStream(ch, 0); RP_ConfirmChannelConnect(ch, GF_OK); return GF_OK; } /*send a DESCRIBE (not a setup) on the channel. If the channel is already created then the describe is skipped and a SETUP is sent directly, otherwise the channel is first described then setup*/ if (sess) RP_Describe(sess, es_url, channel); /*otherwise confirm channel connection*/ else RP_ConfirmChannelConnect(ch, GF_OK); return GF_OK; }
static void gf_rtp_switch_quality(RTPClient *rtp, Bool switch_up) { u32 i,count; RTPStream *ch, *cur_ch; count = gf_list_count(rtp->channels); /*find the current stream*/ ch = cur_ch = NULL; for (i = 0; i < count; i++) { cur_ch = (RTPStream *) gf_list_get(rtp->channels, i); if (cur_ch->mid != rtp->cur_mid) { cur_ch=NULL; continue; } break; } if (!cur_ch) return; if (switch_up) { /*this is the highest stream*/ if (!cur_ch->next_stream) { cur_ch->status = RTP_Running; return; } else { for (i = 0; i < count; i++) { ch = (RTPStream *) gf_list_get(rtp->channels, i); if (ch->mid == cur_ch->next_stream) { /*resume streaming next channel*/ gf_mx_p(rtp->mx); RP_InitStream(ch, 0); gf_mx_v(rtp->mx); ch->status = RTP_Running; rtp->cur_mid = ch->mid; break; } } } } else { /*this is the lowest stream i.e base layer*/ if (!cur_ch->prev_stream) { cur_ch->status = RTP_Running; return; } else { for (i = 0; i < count; i++) { ch = (RTPStream *) gf_list_get(rtp->channels, i); if (ch->mid == cur_ch->prev_stream) { /*stop streaming current channel*/ gf_rtp_stop(cur_ch->rtp_ch); cur_ch->status = RTP_Connected; rtp->cur_mid = ch->mid; break; } } } } GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("Switch from ES%d to ES %d\n", cur_ch->mid, ch->mid)); return; }
void RP_ProcessUserCommand(RTPSession *sess, RTSPCommand *com, M4Err e) { ChannelControl *ch_ctrl; RTPStream *ch, *agg_ch; u32 i; RTP_Info *info; ch_ctrl = com->user_data; ch = ch_ctrl->ch; if (!channel_is_valid(sess->owner, ch)) { free(ch_ctrl); com->user_data = NULL; return; } assert(ch->channel==ch_ctrl->com.base.on_channel); /*some consistency checking: on interleaved sessions, some servers do NOT reply to the teardown. If our command is STOP just skip the error notif*/ if (e) { if (!strcmp(com->method, RTSP_TEARDOWN)) { goto process_reply; } else { goto err_exit; } } switch (sess->rtsp_rsp->ResponseCode) { //handle all 3xx codes (redirections) case NC_RTSP_Method_Not_Allowed: e = M4NotSupported; goto err_exit; case NC_RTSP_OK: break; default: //we should have a basic error code mapping here e = M4ServiceError; goto err_exit; } process_reply: NM_OnCommand(sess->owner->service, &ch_ctrl->com, M4OK); if ( (ch_ctrl->com.command_type==CHAN_PLAY) || (ch_ctrl->com.command_type==CHAN_SET_SPEED) || (ch_ctrl->com.command_type==CHAN_RESUME) ) { //auto-detect any aggregated control if not done yet if (ChainGetCount(sess->rtsp_rsp->RTP_Infos) > 1) { sess->has_aggregated_control = 1; } //process all RTP infos for (i=0;i<ChainGetCount(sess->rtsp_rsp->RTP_Infos); i++) { info = ChainGetEntry(sess->rtsp_rsp->RTP_Infos, i); agg_ch = RP_FindChannel(sess->owner, NULL, 0, info->url, 0); if (!agg_ch || (agg_ch->rtsp != sess) ) continue; /*if play/seeking we must send update RTP/NPT link*/ if (ch_ctrl->com.command_type != CHAN_RESUME) { agg_ch->check_rtp_time = 1; } /*this is used to discard RTP packets re-sent on resume*/ else { agg_ch->check_rtp_time = 2; } /* reset the buffers */ RP_InitStream(agg_ch, 1); RTP_SetInfo(agg_ch->rtp_ch, info->seq, info->rtp_time, info->ssrc); agg_ch->status = RTP_Running; /*skip next play command on this channel if aggregated control*/ if (ch!=agg_ch && ch->rtsp->has_aggregated_control) agg_ch->skip_next_command = 1; if (RTP_IsInterleaved(agg_ch->rtp_ch)) { RTSP_RegisterTCPChannel(sess->session, agg_ch, RTP_GetLowInterleavedID(agg_ch->rtp_ch), RTP_GetHighInterleavedID(agg_ch->rtp_ch)); } } /*no rtp info (just in case), no time mapped - set to 0 and specify we're not interactive*/ if (!i) { ch->current_start = 0.0; ch->check_rtp_time = 1; RP_InitStream(ch, 1); ch->status = RTP_Running; if (RTP_IsInterleaved(ch->rtp_ch)) { RTSP_RegisterTCPChannel(sess->session, ch, RTP_GetLowInterleavedID(ch->rtp_ch), RTP_GetHighInterleavedID(ch->rtp_ch)); } } ch->skip_next_command = 0; } else if (ch_ctrl->com.command_type == CHAN_PAUSE) { SkipCommandOnSession(ch); ch->skip_next_command = 0; } else if (ch_ctrl->com.command_type == CHAN_STOP) { assert(0); } free(ch_ctrl); com->user_data = NULL; return; err_exit: ch->status = RTP_Disconnected; NM_OnCommand(sess->owner->service, &ch_ctrl->com, e); RTSP_ResetAggregation(ch->rtsp->session); ch->check_rtp_time = 0; free(ch_ctrl); com->user_data = NULL; }