void Sdp::startNegotiation() { if (negotiator_ == NULL) { ERROR("Can't start negotiation with invalid negotiator"); return; } const pjmedia_sdp_session *active_local; const pjmedia_sdp_session *active_remote; if (pjmedia_sdp_neg_get_state(negotiator_) != PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) WARN("Negotiator not in right state for negotiation"); if (pjmedia_sdp_neg_negotiate(memPool_, negotiator_, 0) != PJ_SUCCESS) return; if (pjmedia_sdp_neg_get_active_local(negotiator_, &active_local) != PJ_SUCCESS) ERROR("Could not retrieve local active session"); else setActiveLocalSdpSession(active_local); if (pjmedia_sdp_neg_get_active_remote(negotiator_, &active_remote) != PJ_SUCCESS) ERROR("Could not retrieve remote active session"); else setActiveRemoteSdpSession(active_remote); }
/* * Callback when SDP negotiation has completed. * We are interested with this callback because we want to start media * as soon as SDP negotiation is completed. */ static void call_on_media_update( pjsip_inv_session *inv, pj_status_t status) { pjmedia_stream_info stream_info; const pjmedia_sdp_session *local_sdp; const pjmedia_sdp_session *remote_sdp; pjmedia_port *media_port; if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "SDP negotiation has failed", status); /* Here we should disconnect call if we're not in the middle * of initializing an UAS dialog and if this is not a re-INVITE. */ return; } /* Get local and remote SDP. * We need both SDPs to create a media session. */ status = pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp); status = pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp); /* Create stream info based on the media audio SDP. */ status = pjmedia_stream_info_from_sdp(&stream_info, inv->dlg->pool, g_med_endpt, local_sdp, remote_sdp, 0); if (status != PJ_SUCCESS) { app_perror(THIS_FILE,"Unable to create audio stream info",status); return; } /* If required, we can also change some settings in the stream info, * (such as jitter buffer settings, codec settings, etc) before we * create the stream. */ /* Create new audio media stream, passing the stream info, and also the * media socket that we created earlier. */ status = pjmedia_stream_create(g_med_endpt, inv->dlg->pool, &stream_info, g_med_transport[0], NULL, &g_med_stream); if (status != PJ_SUCCESS) { app_perror( THIS_FILE, "Unable to create audio stream", status); return; } /* Start the audio stream */ status = pjmedia_stream_start(g_med_stream); if (status != PJ_SUCCESS) { app_perror( THIS_FILE, "Unable to start audio stream", status); return; } /* Get the media port interface of the audio stream. * Media port interface is basicly a struct containing get_frame() and * put_frame() function. With this media port interface, we can attach * the port interface to conference bridge, or directly to a sound * player/recorder device. */ pjmedia_stream_get_port(g_med_stream, &media_port); /* Create sound port */ pjmedia_snd_port_create(inv->pool, PJMEDIA_AUD_DEFAULT_CAPTURE_DEV, PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV, PJMEDIA_PIA_SRATE(&media_port->info),/* clock rate */ PJMEDIA_PIA_CCNT(&media_port->info),/* channel count */ PJMEDIA_PIA_SPF(&media_port->info), /* samples per frame*/ PJMEDIA_PIA_BITS(&media_port->info),/* bits per sample */ 0, &g_snd_port); if (status != PJ_SUCCESS) { app_perror( THIS_FILE, "Unable to create sound port", status); PJ_LOG(3,(THIS_FILE, "%d %d %d %d", PJMEDIA_PIA_SRATE(&media_port->info),/* clock rate */ PJMEDIA_PIA_CCNT(&media_port->info),/* channel count */ PJMEDIA_PIA_SPF(&media_port->info), /* samples per frame*/ PJMEDIA_PIA_BITS(&media_port->info) /* bits per sample */ )); return; } status = pjmedia_snd_port_connect(g_snd_port, media_port); /* Get the media port interface of the second stream in the session, * which is video stream. With this media port interface, we can attach * the port directly to a renderer/capture video device. */ #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) if (local_sdp->media_count > 1) { pjmedia_vid_stream_info vstream_info; pjmedia_vid_port_param vport_param; pjmedia_vid_port_param_default(&vport_param); /* Create stream info based on the media video SDP. */ status = pjmedia_vid_stream_info_from_sdp(&vstream_info, inv->dlg->pool, g_med_endpt, local_sdp, remote_sdp, 1); if (status != PJ_SUCCESS) { app_perror(THIS_FILE,"Unable to create video stream info",status); return; } /* If required, we can also change some settings in the stream info, * (such as jitter buffer settings, codec settings, etc) before we * create the video stream. */ /* Create new video media stream, passing the stream info, and also the * media socket that we created earlier. */ status = pjmedia_vid_stream_create(g_med_endpt, NULL, &vstream_info, g_med_transport[1], NULL, &g_med_vstream); if (status != PJ_SUCCESS) { app_perror( THIS_FILE, "Unable to create video stream", status); return; } /* Start the video stream */ status = pjmedia_vid_stream_start(g_med_vstream); if (status != PJ_SUCCESS) { app_perror( THIS_FILE, "Unable to start video stream", status); return; } if (vstream_info.dir & PJMEDIA_DIR_DECODING) { status = pjmedia_vid_dev_default_param( inv->pool, PJMEDIA_VID_DEFAULT_RENDER_DEV, &vport_param.vidparam); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to get default param of video " "renderer device", status); return; } /* Get video stream port for decoding direction */ pjmedia_vid_stream_get_port(g_med_vstream, PJMEDIA_DIR_DECODING, &media_port); /* Set format */ pjmedia_format_copy(&vport_param.vidparam.fmt, &media_port->info.fmt); vport_param.vidparam.dir = PJMEDIA_DIR_RENDER; vport_param.active = PJ_TRUE; /* Create renderer */ status = pjmedia_vid_port_create(inv->pool, &vport_param, &g_vid_renderer); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to create video renderer device", status); return; } /* Connect renderer to media_port */ status = pjmedia_vid_port_connect(g_vid_renderer, media_port, PJ_FALSE); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to connect renderer to stream", status); return; } } /* Create capturer */ if (vstream_info.dir & PJMEDIA_DIR_ENCODING) { status = pjmedia_vid_dev_default_param( inv->pool, PJMEDIA_VID_DEFAULT_CAPTURE_DEV, &vport_param.vidparam); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to get default param of video " "capture device", status); return; } /* Get video stream port for decoding direction */ pjmedia_vid_stream_get_port(g_med_vstream, PJMEDIA_DIR_ENCODING, &media_port); /* Get capturer format from stream info */ pjmedia_format_copy(&vport_param.vidparam.fmt, &media_port->info.fmt); vport_param.vidparam.dir = PJMEDIA_DIR_CAPTURE; vport_param.active = PJ_TRUE; /* Create capturer */ status = pjmedia_vid_port_create(inv->pool, &vport_param, &g_vid_capturer); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to create video capture device", status); return; } /* Connect capturer to media_port */ status = pjmedia_vid_port_connect(g_vid_capturer, media_port, PJ_FALSE); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to connect capturer to stream", status); return; } } /* Start streaming */ if (g_vid_renderer) { status = pjmedia_vid_port_start(g_vid_renderer); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to start video renderer", status); return; } } if (g_vid_capturer) { status = pjmedia_vid_port_start(g_vid_capturer); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to start video capturer", status); return; } } } #endif /* PJMEDIA_HAS_VIDEO */ /* Done with media. */ }
static int offer_answer_test(pj_pool_t *pool, pjmedia_sdp_neg **p_neg, struct offer_answer *oa) { pjmedia_sdp_session *sdp1; pjmedia_sdp_neg *neg; pj_status_t status; status = pjmedia_sdp_parse(pool, oa->sdp1, pj_ansi_strlen(oa->sdp1), &sdp1); if (status != PJ_SUCCESS) { app_perror(status, " error: unexpected parse status for sdp1"); return -10; } status = pjmedia_sdp_validate(sdp1); if (status != PJ_SUCCESS) { app_perror(status, " error: sdp1 validation failed"); return -15; } neg = *p_neg; if (oa->type == LOCAL_OFFER) { /* * Local creates offer first. */ pjmedia_sdp_session *sdp2, *sdp3; const pjmedia_sdp_session *active; if (neg == NULL) { /* Create negotiator with local offer. */ status = pjmedia_sdp_neg_create_w_local_offer(pool, sdp1, &neg); if (status != PJ_SUCCESS) { app_perror(status, " error: pjmedia_sdp_neg_create_w_local_offer"); return -20; } *p_neg = neg; } else { /* Modify local offer */ status = pjmedia_sdp_neg_modify_local_offer(pool, neg, sdp1); if (status != PJ_SUCCESS) { app_perror(status, " error: pjmedia_sdp_neg_modify_local_offer"); return -30; } } /* Parse and validate remote answer */ status = pjmedia_sdp_parse(pool, oa->sdp2, pj_ansi_strlen(oa->sdp2), &sdp2); if (status != PJ_SUCCESS) { app_perror(status, " error: parsing sdp2"); return -40; } status = pjmedia_sdp_validate(sdp2); if (status != PJ_SUCCESS) { app_perror(status, " error: sdp2 validation failed"); return -50; } /* Give the answer to negotiator. */ status = pjmedia_sdp_neg_set_remote_answer(pool, neg, sdp2); if (status != PJ_SUCCESS) { app_perror(status, " error: pjmedia_sdp_neg_rx_remote_answer"); return -60; } /* Negotiate remote answer with local answer */ status = pjmedia_sdp_neg_negotiate(pool, neg, 0); if (status != PJ_SUCCESS) { app_perror(status, " error: pjmedia_sdp_neg_negotiate"); return -70; } /* Get the local active media. */ status = pjmedia_sdp_neg_get_active_local(neg, &active); if (status != PJ_SUCCESS) { app_perror(status, " error: pjmedia_sdp_neg_get_local"); return -80; } /* Parse and validate the correct active media. */ status = pjmedia_sdp_parse(pool, oa->sdp3, pj_ansi_strlen(oa->sdp3), &sdp3); if (status != PJ_SUCCESS) { app_perror(status, " error: parsing sdp3"); return -90; } status = pjmedia_sdp_validate(sdp3); if (status != PJ_SUCCESS) { app_perror(status, " error: sdp3 validation failed"); return -100; } /* Compare active with sdp3 */ status = pjmedia_sdp_session_cmp(active, sdp3, 0); if (status != PJ_SUCCESS) { app_perror(status, " error: active local comparison mismatch"); compare_sdp_string("Logical cmp after negotiatin remote answer", "Active local sdp from negotiator", active, "The correct active local sdp", sdp3, status); return -110; } /* Compare the string representation oa both sdps */ status = compare_sdp_string("String cmp after negotiatin remote answer", "Active local sdp from negotiator", active, "The correct active local sdp", sdp3, PJ_SUCCESS); if (status != 0) return -120; } else { /* * Remote creates offer first. */ pjmedia_sdp_session *sdp2 = NULL, *sdp3; const pjmedia_sdp_session *answer; if (oa->sdp2) { /* Parse and validate initial local capability */ status = pjmedia_sdp_parse(pool, oa->sdp2, pj_ansi_strlen(oa->sdp2), &sdp2); if (status != PJ_SUCCESS) { app_perror(status, " error: parsing sdp2"); return -200; } status = pjmedia_sdp_validate(sdp2); if (status != PJ_SUCCESS) { app_perror(status, " error: sdp2 validation failed"); return -210; } } else if (neg) { status = pjmedia_sdp_neg_get_active_local(neg, &sdp2); if (status != PJ_SUCCESS) { app_perror(status, " error: pjmedia_sdp_neg_get_active_local"); return -215; } } if (neg == NULL) { /* Create negotiator with remote offer. */ status = pjmedia_sdp_neg_create_w_remote_offer(pool, sdp2, sdp1, &neg); if (status != PJ_SUCCESS) { app_perror(status, " error: pjmedia_sdp_neg_create_w_remote_offer"); return -220; } *p_neg = neg; } else { /* Received subsequent offer from remote. */ status = pjmedia_sdp_neg_set_remote_offer(pool, neg, sdp1); if (status != PJ_SUCCESS) { app_perror(status, " error: pjmedia_sdp_neg_rx_remote_offer"); return -230; } status = pjmedia_sdp_neg_set_local_answer(pool, neg, sdp2); if (status != PJ_SUCCESS) { app_perror(status, " error: pjmedia_sdp_neg_set_local_answer"); return -235; } } /* Negotiate. */ status = pjmedia_sdp_neg_negotiate(pool, neg, 0); if (status != PJ_SUCCESS) { app_perror(status, " error: pjmedia_sdp_neg_negotiate"); return -240; } /* Get our answer. */ status = pjmedia_sdp_neg_get_active_local(neg, &answer); if (status != PJ_SUCCESS) { app_perror(status, " error: pjmedia_sdp_neg_get_local"); return -250; } /* Parse the correct answer. */ status = pjmedia_sdp_parse(pool, oa->sdp3, pj_ansi_strlen(oa->sdp3), &sdp3); if (status != PJ_SUCCESS) { app_perror(status, " error: parsing sdp3"); return -260; } /* Validate the correct answer. */ status = pjmedia_sdp_validate(sdp3); if (status != PJ_SUCCESS) { app_perror(status, " error: sdp3 validation failed"); return -270; } /* Compare answer from negotiator and the correct answer */ status = pjmedia_sdp_session_cmp(sdp3, answer, 0); if (status != PJ_SUCCESS) { compare_sdp_string("Logical cmp after negotiating remote offer", "Local answer from negotiator", answer, "The correct local answer", sdp3, status); return -280; } /* Compare the string representation oa both answers */ status = compare_sdp_string("String cmp after negotiating remote offer", "Local answer from negotiator", answer, "The correct local answer", sdp3, PJ_SUCCESS); if (status != 0) return -290; } return 0; }
/* * Callback when SDP negotiation has completed. * We are interested with this callback because we want to start media * as soon as SDP negotiation is completed. */ static void call_on_media_update( pjsip_inv_session *inv, pj_status_t status) { pjmedia_session_info sess_info; const pjmedia_sdp_session *local_sdp; const pjmedia_sdp_session *remote_sdp; pjmedia_port *media_port; if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "SDP negotiation has failed", status); /* Here we should disconnect call if we're not in the middle * of initializing an UAS dialog and if this is not a re-INVITE. */ return; } /* Get local and remote SDP. * We need both SDPs to create a media session. */ status = pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp); status = pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp); /* Create session info based on the two SDPs. * We only support one stream per session for now. */ status = pjmedia_session_info_from_sdp(inv->dlg->pool, g_med_endpt, 1, &sess_info, local_sdp, remote_sdp); if (status != PJ_SUCCESS) { app_perror( THIS_FILE, "Unable to create media session", status); return; } /* If required, we can also change some settings in the session info, * (such as jitter buffer settings, codec settings, etc) before we * create the session. */ /* Create new media session, passing the two SDPs, and also the * media socket that we created earlier. * The media session is active immediately. */ status = pjmedia_session_create( g_med_endpt, &sess_info, &g_med_transport, NULL, &g_med_session ); if (status != PJ_SUCCESS) { app_perror( THIS_FILE, "Unable to create media session", status); return; } /* Get the media port interface of the first stream in the session. * Media port interface is basicly a struct containing get_frame() and * put_frame() function. With this media port interface, we can attach * the port interface to conference bridge, or directly to a sound * player/recorder device. */ pjmedia_session_get_port(g_med_session, 0, &media_port); /* Create a sound Player device and connect the media port to the * sound device. */ status = pjmedia_snd_port_create_player( inv->pool, /* pool */ -1, /* sound dev id */ media_port->info.clock_rate, /* clock rate */ media_port->info.channel_count, /* channel count */ media_port->info.samples_per_frame, /* samples per frame*/ media_port->info.bits_per_sample, /* bits per sample */ 0, /* options */ &g_snd_player); if (status != PJ_SUCCESS) { app_perror( THIS_FILE, "Unable to create sound player", status); PJ_LOG(3,(THIS_FILE, "%d %d %d %d", media_port->info.clock_rate, /* clock rate */ media_port->info.channel_count, /* channel count */ media_port->info.samples_per_frame, /* samples per frame*/ media_port->info.bits_per_sample /* bits per sample */ )); return; } status = pjmedia_snd_port_connect(g_snd_player, media_port); /* Create a sound recorder device and connect the media port to the * sound device. */ status = pjmedia_snd_port_create_rec( inv->pool, /* pool */ -1, /* sound dev id */ media_port->info.clock_rate, /* clock rate */ media_port->info.channel_count, /* channel count */ media_port->info.samples_per_frame, /* samples per frame*/ media_port->info.bits_per_sample, /* bits per sample */ 0, /* options */ &g_snd_rec); if (status != PJ_SUCCESS) { app_perror( THIS_FILE, "Unable to create sound recorder", status); return; } status = pjmedia_snd_port_connect(g_snd_rec, media_port); /* Done with media. */ }
/* Timer callback. When the timer is fired, it can be time to refresh * the session if UA is the refresher, otherwise it is time to end * the session. */ static void timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) { pjsip_inv_session *inv = (pjsip_inv_session*) entry->user_data; pjsip_tx_data *tdata = NULL; pj_status_t status; pj_bool_t as_refresher; pj_assert(inv); PJ_UNUSED_ARG(timer_heap); /* Lock dialog. */ pjsip_dlg_inc_lock(inv->dlg); /* Check our role */ as_refresher = (inv->timer->refresher == TR_UAC && inv->timer->role == PJSIP_ROLE_UAC) || (inv->timer->refresher == TR_UAS && inv->timer->role == PJSIP_ROLE_UAS); /* Do action based on role(refresher or refreshee). * As refresher: * - send refresh, or * - end session if there is no response to the refresh request. * As refreshee: * - end session if there is no refresh request received. */ if (as_refresher && (entry->id != REFRESHER_EXPIRE_TIMER_ID)) { pj_time_val now; /* As refresher, reshedule the refresh request on the following: * - must not send re-INVITE if another INVITE or SDP negotiation * is in progress. * - must not send UPDATE with SDP if SDP negotiation is in progress */ pjmedia_sdp_neg_state neg_state = pjmedia_sdp_neg_get_state(inv->neg); inv->timer->timer.id = 0; if ( (!inv->timer->use_update && ( inv->invite_tsx != NULL || neg_state != PJMEDIA_SDP_NEG_STATE_DONE) ) || (inv->timer->use_update && inv->timer->with_sdp && neg_state != PJMEDIA_SDP_NEG_STATE_DONE ) ) { pj_time_val delay = {1, 0}; inv->timer->timer.id = 1; pjsip_endpt_schedule_timer(inv->dlg->endpt, &inv->timer->timer, &delay); pjsip_dlg_dec_lock(inv->dlg); return; } /* Refresher, refresh the session */ if (inv->timer->use_update) { const pjmedia_sdp_session *offer = NULL; if (inv->timer->with_sdp) { pjmedia_sdp_neg_get_active_local(inv->neg, &offer); } status = pjsip_inv_update(inv, NULL, offer, &tdata); } else { /* Create re-INVITE without modifying session */ pjsip_msg_body *body; const pjmedia_sdp_session *offer = NULL; pj_assert(pjmedia_sdp_neg_get_state(inv->neg) == PJMEDIA_SDP_NEG_STATE_DONE); status = pjsip_inv_invite(inv, &tdata); if (status == PJ_SUCCESS) status = pjmedia_sdp_neg_send_local_offer(inv->pool_prov, inv->neg, &offer); if (status == PJ_SUCCESS) status = pjmedia_sdp_neg_get_neg_local(inv->neg, &offer); if (status == PJ_SUCCESS) { status = pjsip_create_sdp_body(tdata->pool, (pjmedia_sdp_session*)offer, &body); tdata->msg->body = body; } } pj_gettimeofday(&now); PJ_LOG(4, (inv->pool->obj_name, "Refreshing session after %ds (expiration period=%ds)", (now.sec-inv->timer->last_refresh.sec), inv->timer->setting.sess_expires)); } else { pj_time_val now; if (as_refresher) inv->timer->expire_timer.id = 0; else inv->timer->timer.id = 0; /* Terminate the session */ status = pjsip_inv_end_session(inv, PJSIP_SC_REQUEST_TIMEOUT, NULL, &tdata); pj_gettimeofday(&now); PJ_LOG(3, (inv->pool->obj_name, "No session %s received after %ds " "(expiration period=%ds), stopping session now!", (as_refresher?"refresh response":"refresh"), (now.sec-inv->timer->last_refresh.sec), inv->timer->setting.sess_expires)); } /* Unlock dialog. */ pjsip_dlg_dec_lock(inv->dlg); /* Send message, if any */ if (tdata && status == PJ_SUCCESS) { inv->timer->refresh_tdata = tdata; status = pjsip_inv_send_msg(inv, tdata); } /* Print error message, if any */ if (status != PJ_SUCCESS) { PJ_PERROR(2, (inv->pool->obj_name, status, "Error in %s session timer", ((as_refresher && entry->id != REFRESHER_EXPIRE_TIMER_ID)? "refreshing" : "terminating"))); } }