/* v=0 o=mhandley 2890844526 2890842807 IN IP4 126.16.64.4 s=SDP Seminar i=A Seminar on the session description protocol u=http://www.cs.ucl.ac.uk/staff/M.Handley/sdp.03.ps [email protected] (Mark Handley) c=IN IP4 224.2.17.12/127 t=2873397496 2873404696 a=recvonly m=audio 3456 RTP/AVP 0 m=video 2232 RTP/AVP 31 m=whiteboard 32416 UDP WB a=orient:portrait */ int rtsp_client_sdp(struct rtsp_client_t* rtsp, const char* content) { int i, j, n, count; int formats[N_MEDIA_FORMAT]; struct rtsp_media_t* media; void* sdp; sdp = sdp_parse(content); if (!sdp) return -1; count = sdp_media_count(sdp); if(count > N_MEDIA) { rtsp->media_ptr = (struct rtsp_media_t*)malloc(sizeof(struct rtsp_media_t)*(count-N_MEDIA)); if(!rtsp->media_ptr) { sdp_destroy(sdp); return ENOMEM; } memset(rtsp->media_ptr, 0, sizeof(struct rtsp_media_t)*(count-N_MEDIA)); } rtsp->media_count = count; // rfc 2326 C.1.1 Control URL (p80) // If found at the session level, the attribute indicates the URL for aggregate control rtsp->aggregate = rtsp_media_aggregate_control_enable(sdp); rtsp_get_session_uri(sdp, rtsp->aggregate_uri, sizeof(rtsp->aggregate_uri), rtsp->uri, rtsp->baseuri, rtsp->location); for(i = 0; i < count; i++) { media = rtsp_get_media(rtsp, i); //media->cseq = rand(); // RTSP2326 C.1.1 Control URL rtsp_get_media_uri(sdp, i, media->uri, sizeof(media->uri), rtsp->aggregate_uri); n = sdp_media_formats(sdp, i, formats, N_MEDIA_FORMAT); media->avformat_count = n > N_MEDIA_FORMAT ? N_MEDIA_FORMAT : n; for(j = 0; j < media->avformat_count; j++) { media->avformats[j].fmt = formats[j]; } // update media encoding sdp_media_attribute_list(sdp, i, NULL, rtsp_media_onattr, media); } sdp_destroy(sdp); return 0; }
/* v=0 o=mhandley 2890844526 2890842807 IN IP4 126.16.64.4 s=SDP Seminar i=A Seminar on the session description protocol u=http://www.cs.ucl.ac.uk/staff/M.Handley/sdp.03.ps [email protected] (Mark Handley) c=IN IP4 224.2.17.12/127 t=2873397496 2873404696 a=recvonly m=audio 3456 RTP/AVP 0 m=video 2232 RTP/AVP 31 m=whiteboard 32416 UDP WB a=orient:portrait */ int rtsp_client_sdp(struct rtsp_client_context_t* ctx, void* sdp) { int i, count; int formats[N_MEDIA_FORMAT]; struct rtsp_media_t* media; assert(sdp); count = sdp_media_count(sdp); if(count > N_MEDIA) { ctx->media_ptr = (struct rtsp_media_t*)malloc(sizeof(struct rtsp_media_t)*(count-N_MEDIA)); if(!ctx->media_ptr) return -1; memset(ctx->media_ptr, 0, sizeof(struct rtsp_media_t)*(count-N_MEDIA)); } ctx->media_count = count; // rfc 2326 C.1.1 Control URL (p80) // If found at the session level, the attribute indicates the URL for aggregate control ctx->aggregate = rtsp_media_aggregate_control_enable(sdp); rtsp_get_session_uri(sdp, ctx->aggregate_uri, sizeof(ctx->aggregate_uri), ctx->uri, ctx->baseuri, ctx->location); for(i = 0; i < count; i++) { int j, n; media = rtsp_get_media(ctx, i); media->cseq = rand(); // RTSP2326 C.1.1 Control URL rtsp_get_media_uri(sdp, i, media->uri, sizeof(media->uri), ctx->aggregate_uri); n = sdp_media_formats(sdp, i, formats, N_MEDIA_FORMAT); media->avformat_count = n > N_MEDIA_FORMAT ? N_MEDIA_FORMAT : n; for(j = 0; j < media->avformat_count; j++) { media->avformats[j].pt = formats[j]; } // update media encoding sdp_media_attribute_list(sdp, i, NULL, rtsp_media_onattr, media); } return 0; }
/** Upgrade m= lines within session */ static int soa_sdp_upgrade(soa_session_t *ss, su_home_t *home, sdp_session_t *session, sdp_session_t const *user, sdp_session_t const *remote, int **return_u2s, int **return_s2u) { soa_static_session_t *sss = (soa_static_session_t *)ss; int Ns, Nu, Nr, Nmax, n, i, j; sdp_media_t *m, **mm, *um; sdp_media_t **s_media, **o_media, **u_media; sdp_media_t const *rm, **r_media; int *u2s = NULL, *s2u = NULL; if (session == NULL || user == NULL) return (errno = EFAULT), -1; Ns = sdp_media_count(session, sdp_media_any, (sdp_text_t)0, (sdp_proto_e)0, (sdp_text_t)0); Nu = sdp_media_count(user, sdp_media_any, (sdp_text_t)0, (sdp_proto_e)0, (sdp_text_t)0); Nr = sdp_media_count(remote, sdp_media_any, (sdp_text_t)0, (sdp_proto_e)0, (sdp_text_t)0); if (remote == NULL) Nmax = Ns + Nu; else if (Ns < Nr) Nmax = Nr; else Nmax = Ns; s_media = su_zalloc(home, (Nmax + 1) * (sizeof *s_media)); o_media = su_zalloc(home, (Ns + 1) * (sizeof *o_media)); u_media = su_zalloc(home, (Nu + 1) * (sizeof *u_media)); r_media = su_zalloc(home, (Nr + 1) * (sizeof *r_media)); if (!s_media || !o_media || !u_media || !r_media) return -1; um = sdp_media_dup_all(home, user->sdp_media, session); if (!um && user->sdp_media) return -1; u2s = su_alloc(home, (Nu + 1) * sizeof(*u2s)); s2u = su_alloc(home, (Nmax + 1) * sizeof(*s2u)); if (!u2s || !s2u) return -1; for (i = 0; i < Nu; i++) u2s[i] = U2S_NOT_USED; u2s[Nu] = U2S_SENTINEL; for (i = 0; i < Nmax; i++) s2u[i] = U2S_NOT_USED; s2u[Nmax] = U2S_SENTINEL; for (i = 0, m = session->sdp_media; m && i < Ns; m = m->m_next) o_media[i++] = m; assert(i == Ns); for (i = 0, m = um; m && i < Nu; m = m->m_next) u_media[i++] = m; assert(i == Nu); m = remote ? remote->sdp_media : NULL; for (i = 0; m && i < Nr; m = m->m_next) r_media[i++] = m; assert(i == Nr); if (sss->sss_ordered_user && sss->sss_u2s) { /* User SDP is ordered */ for (j = 0; sss->sss_u2s[j] != U2S_SENTINEL; j++) { i = sss->sss_u2s[j]; if (i == U2S_NOT_USED) continue; if (j >= Nu) /* lines removed from user SDP */ continue; if (i >= Ns) /* I should never be called but somehow i and Ns are 0 here sometimes */ continue; s_media[i] = u_media[j], u_media[j] = SDP_MEDIA_NONE; u2s[j] = i, s2u[i] = j; } } if (remote) { /* Update session according to remote */ for (i = 0; i < Nr; i++) { rm = r_media[i]; m = s_media[i]; if (!m) { int codec_mismatch = 0; if (!rm->m_rejected) j = soa_sdp_matching_mindex(ss, u_media, rm, &codec_mismatch); else j = -1; if (j == -1) { s_media[i] = soa_sdp_make_rejected_media(home, rm, session, 0); continue; } else if (codec_mismatch && !ss->ss_rtp_mismatch) { m = soa_sdp_make_rejected_media(home, u_media[j], session, 1); soa_sdp_set_rtpmap_pt(s_media[i] = m, rm); continue; } s_media[i] = m = u_media[j]; u_media[j] = SDP_MEDIA_NONE; u2s[j] = i, s2u[i] = j; } if (sdp_media_uses_rtp(rm)) soa_sdp_media_upgrade_rtpmaps(ss, m, rm); } } else { if (sss->sss_ordered_user) { /* Update session with unused media in u_media */ if (!sss->sss_reuse_rejected) { /* Mark previously used slots */ for (i = 0; i < Ns; i++) { if (s_media[i]) continue; s_media[i] = soa_sdp_make_rejected_media(home, o_media[i], session, 0); } } for (j = 0; j < Nu; j++) { if (u_media[j] == SDP_MEDIA_NONE) continue; for (i = 0; i < Nmax; i++) { if (s_media[i] == NULL) { s_media[i] = u_media[j], u_media[j] = SDP_MEDIA_NONE; u2s[j] = i, s2u[i] = j; break; } } assert(i != Nmax); } } /* Match unused user media by media types with the existing session */ for (i = 0; i < Ns; i++) { if (s_media[i]) continue; j = soa_sdp_matching_mindex(ss, u_media, o_media[i], NULL); if (j == -1) { s_media[i] = soa_sdp_make_rejected_media(home, o_media[i], session, 0); continue; } s_media[i] = u_media[j], u_media[j] = SDP_MEDIA_NONE; u2s[j] = i, s2u[i] = j; } /* Here we just append new media at the end */ for (j = 0; j < Nu; j++) { if (u_media[j] != SDP_MEDIA_NONE) { s_media[i] = u_media[j], u_media[j] = SDP_MEDIA_NONE; u2s[j] = i, s2u[i] = j; i++; } } assert(i <= Nmax); } mm = &session->sdp_media; for (i = 0; s_media[i]; i++) { m = s_media[i]; *mm = m; mm = &m->m_next; } *mm = NULL; s2u[n = i] = U2S_SENTINEL; *return_u2s = u2s; *return_s2u = s2u; #ifndef NDEBUG /* X check */ for (j = 0; j < Nu; j++) { i = u2s[j]; assert(i == U2S_NOT_USED || s2u[i] == j); } for (i = 0; i < n; i++) { j = s2u[i]; assert(j == U2S_NOT_USED || u2s[j] == i); } #endif return 0; }
/** * Updates the modified copy of local SDP based * on application provided local SDP and remote SDP. */ static int offer_answer_step(soa_session_t *ss, enum offer_answer_action action, char const *by) { soa_static_session_t *sss = (soa_static_session_t *)ss; sdp_session_t *local = ss->ss_local->ssd_sdp; sdp_session_t local0[1]; sdp_session_t *user = ss->ss_user->ssd_sdp; unsigned user_version = ss->ss_user_version; sdp_session_t *remote = ss->ss_remote->ssd_sdp; unsigned remote_version = ss->ss_remote_version; int fresh = 0; sdp_origin_t o[1] = {{ sizeof(o) }}; sdp_connection_t *c, c0[1] = {{ sizeof(c0) }}; char c0_buffer[64]; sdp_time_t t[1] = {{ sizeof(t) }}; int *u2s = NULL, *s2u = NULL, *tbf; sdp_session_t *latest = NULL, *previous = NULL; char const *phrase = "Internal Media Error"; su_home_t tmphome[SU_HOME_AUTO_SIZE(8192)]; su_home_auto(tmphome, sizeof tmphome); SU_DEBUG_7(("soa_static_offer_answer_action(%p, %s): called\n", (void *)ss, by)); if (user == NULL) return soa_set_status(ss, 500, "No session set by user"); if (action == generate_offer) remote = NULL; else if (remote == NULL) return soa_set_status(ss, 500, "No remote SDP"); /* Pre-negotiation Step: Expand truncated remote SDP */ if (local && remote) switch (action) { case generate_answer: case process_answer: if (sdp_media_count(remote, sdp_media_any, "*", (sdp_proto_e)0, (sdp_text_t)0) < sdp_media_count(local, sdp_media_any, "*", (sdp_proto_e)0, (sdp_text_t)0)) { SU_DEBUG_5(("%s: remote %s is truncated: expanding\n", by, action == generate_answer ? "offer" : "answer")); remote = soa_sdp_expand_media(tmphome, remote, local); if (remote == NULL) return soa_set_status(ss, 500, "Cannot expand remote session"); } default: break; } /* Step A: Create local SDP session (based on user-supplied SDP) */ if (local == NULL) switch (action) { case generate_offer: case generate_answer: SU_DEBUG_7(("soa_static(%p, %s): %s\n", (void *)ss, by, "generating local description")); fresh = 1; local = local0; *local = *user, local->sdp_media = NULL; o->o_username = "******"; o->o_address = c0; c0->c_address = c0_buffer; if (!local->sdp_origin) local->sdp_origin = o; break; case process_answer: default: goto internal_error; } /* Step B: upgrade local SDP (add m= lines to it) */ switch (action) { case generate_offer: /* Upgrade local SDP based on user SDP */ if (local != local0 && ss->ss_local_user_version == user_version) break; if (local != local0) *local0 = *local, local = local0; SU_DEBUG_7(("soa_static(%p, %s): %s\n", (void *)ss, by, "upgrade with local description")); if (soa_sdp_upgrade(ss, tmphome, local, user, NULL, &u2s, &s2u) < 0) goto internal_error; break; case generate_answer: /* Upgrade local SDP based on remote SDP */ if (ss->ss_local_user_version == user_version && ss->ss_local_remote_version == remote_version) break; if (1) { if (local != local0) *local0 = *local, local = local0; SU_DEBUG_7(("soa_static(%p, %s): %s\n", (void *)ss, by, "upgrade with remote description")); if (soa_sdp_upgrade(ss, tmphome, local, user, remote, &u2s, &s2u) < 0) goto internal_error; } break; case process_answer: default: break; } /* Step C: reject media */ switch (action) { case generate_offer: /* Local media is marked as rejected already in upgrade phase */ break; case generate_answer: case process_answer: if (ss->ss_local_remote_version == remote_version) break; if (soa_sdp_reject_is_needed(local, remote)) { if (local != local0) { *local0 = *local, local = local0; #define DUP_LOCAL(local) \ do { \ if (!local->sdp_media) break; \ local->sdp_media = \ sdp_media_dup_all(tmphome, local->sdp_media, local); \ if (!local->sdp_media) \ goto internal_error; \ } while (0) DUP_LOCAL(local); } SU_DEBUG_7(("soa_static(%p, %s): %s\n", (void *)ss, by, "marking rejected media")); soa_sdp_reject(tmphome, local, remote); } break; default: break; } /* Step D: Set media mode bits */ switch (action) { int const *s2u_; case generate_offer: case generate_answer: case process_answer: s2u_ = s2u; if (!s2u_) s2u_ = sss->sss_s2u; if (soa_sdp_mode_set(user, s2u_, local, remote, ss->ss_hold, 1)) { if (local != local0) { *local0 = *local, local = local0; DUP_LOCAL(local); } soa_sdp_mode_set(user, s2u_, local, remote, ss->ss_hold, 0); } break; default: break; } /* Step E: Upgrade codecs by answer. */ switch (action) { case process_answer: /* Upgrade local SDP based on remote SDP */ if (ss->ss_local_remote_version == remote_version) break; if (1 /* We don't have good test for codecs */) { SU_DEBUG_7(("soa_static(%p, %s): %s\n", (void *)ss, by, "upgrade codecs with remote description")); if (local != local0) { *local0 = *local, local = local0; DUP_LOCAL(local); } soa_sdp_session_upgrade_rtpmaps(ss, local, remote); } break; case generate_offer: case generate_answer: default: break; } /* Step F0: Initialize o= line */ if (fresh) { if (user->sdp_origin) { o->o_username = user->sdp_origin->o_username; if (user->sdp_origin->o_address) o->o_address = user->sdp_origin->o_address; if (user->sdp_origin->o_id) o->o_id = user->sdp_origin->o_id; if (user->sdp_origin->o_version && user->sdp_origin->o_version != o->o_version) { o->o_version = user->sdp_origin->o_version; o->o_version--; } } if (soa_init_sdp_origin_with_session(ss, o, c0_buffer, local) < 0) { phrase = "Cannot Get IP Address for Session Description"; goto internal_error; } local->sdp_origin = o; } /* Step F: Update c= line(s) */ switch (action) { sdp_connection_t *user_c, *local_c; case generate_offer: case generate_answer: user_c = user->sdp_connection; if (!soa_check_sdp_connection(user_c)) user_c = NULL; local_c = local->sdp_connection; if (!soa_check_sdp_connection(local_c)) local_c = NULL; if (ss->ss_local_user_version != user_version || local_c == NULL || (user_c != NULL && sdp_connection_cmp(local_c, user_c))) { sdp_media_t *m; if (user_c) c = user_c; else c = local->sdp_origin->o_address; /* Every m= line (even rejected one) must have a c= line * or there must be a c= line at session level */ for (m = local->sdp_media; m; m = m->m_next) if (m->m_connections == NULL) break; if (m) { if (local != local0) { *local0 = *local, local = local0; DUP_LOCAL(local); } local->sdp_connection = c; } } break; default: break; } soa_description_free(ss, ss->ss_previous); su_free(ss->ss_home, sss->sss_previous.u2s), sss->sss_previous.u2s = NULL; su_free(ss->ss_home, sss->sss_previous.s2u), sss->sss_previous.s2u = NULL; if (u2s) { u2s = u2s_alloc(ss->ss_home, u2s); s2u = u2s_alloc(ss->ss_home, s2u); if (!u2s || !s2u) goto internal_error; } if (ss->ss_local->ssd_sdp != local && sdp_session_cmp(ss->ss_local->ssd_sdp, local)) { int bump; switch (action) { case generate_offer: bump = sdp_session_cmp(local, sss->sss_latest); break; case generate_answer: bump = 1; break; case process_answer: default: bump = 0; break; } if (bump) { /* Upgrade the version number */ if (local->sdp_origin != o) *o = *local->sdp_origin, local->sdp_origin = o; o->o_version++; } /* Do sanity checks for the created SDP */ if (!local->sdp_subject) /* s= is mandatory */ local->sdp_subject = "-"; if (!local->sdp_time) /* t= is mandatory */ local->sdp_time = t; if (action == generate_offer) { /* Keep a copy of previous session state */ int *previous_u2s = u2s_alloc(ss->ss_home, sss->sss_u2s); int *previous_s2u = u2s_alloc(ss->ss_home, sss->sss_s2u); if ((sss->sss_u2s && !previous_u2s) || (sss->sss_s2u && !previous_s2u)) goto internal_error; *ss->ss_previous = *ss->ss_local; memset(ss->ss_local, 0, (sizeof *ss->ss_local)); ss->ss_previous_user_version = ss->ss_local_user_version; ss->ss_previous_remote_version = ss->ss_local_remote_version; sss->sss_previous.u2s = previous_u2s; sss->sss_previous.s2u = previous_s2u; } SU_DEBUG_7(("soa_static(%p, %s): %s\n", (void *)ss, by, "storing local description")); /* Update the unparsed and pretty-printed descriptions */ if (soa_description_set(ss, ss->ss_local, local, NULL, 0) < 0) { if (action == generate_offer) { /* Remove 2nd reference to local session state */ memset(ss->ss_previous, 0, (sizeof *ss->ss_previous)); ss->ss_previous_user_version = 0; ss->ss_previous_remote_version = 0; su_free(ss->ss_home, sss->sss_previous.u2s), sss->sss_previous.u2s = NULL; su_free(ss->ss_home, sss->sss_previous.s2u), sss->sss_previous.s2u = NULL; } su_free(ss->ss_home, u2s), su_free(ss->ss_home, s2u); goto internal_error; } if (bump) { latest = sdp_session_dup(ss->ss_home, ss->ss_local->ssd_sdp); previous = sss->sss_latest; } } if (u2s) { tbf = sss->sss_u2s, sss->sss_u2s = u2s, su_free(ss->ss_home, tbf); tbf = sss->sss_s2u, sss->sss_s2u = s2u, su_free(ss->ss_home, tbf); } /* Update version numbers */ switch (action) { case generate_offer: ss->ss_local_user_version = user_version; sss->sss_latest = latest; break; case generate_answer: ss->ss_local_user_version = user_version; ss->ss_local_remote_version = remote_version; sss->sss_latest = latest; break; case process_answer: ss->ss_local_remote_version = remote_version; default: break; } if (previous) su_free(ss->ss_home, previous); su_home_deinit(tmphome); return 0; internal_error: su_home_deinit(tmphome); return soa_set_status(ss, 500, phrase); }