diff options
-rw-r--r-- | pjmedia/include/pjmedia/sdp_neg.h | 45 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/sdp_neg.c | 20 | ||||
-rw-r--r-- | pjsip-apps/src/samples/invtester.c | 294 | ||||
-rw-r--r-- | pjsip/include/pjsip-ua/sip_inv.h | 21 | ||||
-rw-r--r-- | pjsip/src/pjsip-ua/sip_inv.c | 58 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_call.c | 53 |
6 files changed, 478 insertions, 13 deletions
diff --git a/pjmedia/include/pjmedia/sdp_neg.h b/pjmedia/include/pjmedia/sdp_neg.h index 3f8481da..bf75a3ac 100644 --- a/pjmedia/include/pjmedia/sdp_neg.h +++ b/pjmedia/include/pjmedia/sdp_neg.h @@ -50,11 +50,11 @@ * modify_local_offer() * create_w_local_offer() +-------------+ send_local_offer() * ----------------------->| LOCAL_OFFER |<----------------------- - * | +-------------+ | - * | | | - * | set_remote_answer() | | - * | V | - * +--+---+ +-----------+ negotiate() +------+ + * | +-------------+______ | + * | | \______ cancel() | + * | set_remote_answer() | \______ | + * | V \ | + * +--+---+ +-----------+ negotiate() +-~----+ * | NULL | | WAIT_NEGO |-------------------->| DONE | * +------+ +-----------+ +------+ * | A | @@ -173,6 +173,29 @@ * the negotiator state will move to PJMEDIA_SDP_NEG_STATE_DONE. * * + * \subsection sdpneg_cancel_offer Cancelling an Offer + * + * In other case, after an offer is generated (negotiator state is in + * PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER), the answer may not be received, and + * application wants the negotiator to reset itself to its previous state. + * Consider this example: + * + * - media has been established, and negotiator state is + * PJMEDIA_SDP_NEG_STATE_DONE. + * - application generates a new offer for re-INVITE, so in this case + * it would either call #pjmedia_sdp_neg_send_local_offer() or + * #pjmedia_sdp_neg_modify_local_offer() + * - the negotiator state moves to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER + * - the re-INVITE was rejected with an error + * + * Since an answer is not received, it is necessary to reset the negotiator + * state back to PJMEDIA_SDP_NEG_STATE_DONE so that the negotiator can + * create or receive new offer. + * + * This can be accomplished by calling #pjmedia_sdp_neg_cancel_offer(), + * to reset the negotiator state back to PJMEDIA_SDP_NEG_STATE_DONE. In + * this case, both active local and active remote will not be modified. + * * \subsection sdpneg_create_answer Generating SDP Answer * * After remote offer has been set in the negotiator, application can @@ -598,6 +621,18 @@ PJ_DECL(pj_bool_t) pjmedia_sdp_neg_has_local_answer(pjmedia_sdp_neg *neg); /** + * Cancel previously sent offer, and move negotiator state back to + * previous stable state (PJMEDIA_SDP_NEG_STATE_DONE). The negotiator + * must be in PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER state. + * + * @param neg The negotiator. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_neg_cancel_offer(pjmedia_sdp_neg *neg); + + +/** * Negotiate local and remote answer. Before calling this function, the * SDP negotiator must be in PJMEDIA_SDP_NEG_STATE_WAIT_NEGO state. * After calling this function, the negotiator state will move to diff --git a/pjmedia/src/pjmedia/sdp_neg.c b/pjmedia/src/pjmedia/sdp_neg.c index ecbba9eb..f24dc41e 100644 --- a/pjmedia/src/pjmedia/sdp_neg.c +++ b/pjmedia/src/pjmedia/sdp_neg.c @@ -985,6 +985,26 @@ static pj_status_t create_answer( pj_pool_t *pool, return has_active ? PJ_SUCCESS : status; } +/* Cancel offer */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_cancel_offer(pjmedia_sdp_neg *neg) +{ + PJ_ASSERT_RETURN(neg, PJ_EINVAL); + + /* Must be in LOCAL_OFFER state. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + PJMEDIA_SDPNEG_EINSTATE); + + /* Reset state to done */ + neg->state = PJMEDIA_SDP_NEG_STATE_DONE; + + /* Clear temporary SDP */ + neg->neg_local_sdp = neg->neg_remote_sdp = NULL; + neg->has_remote_answer = PJ_FALSE; + + return PJ_SUCCESS; +} + + /* The best bit: SDP negotiation function! */ PJ_DEF(pj_status_t) pjmedia_sdp_neg_negotiate( pj_pool_t *pool, pjmedia_sdp_neg *neg, diff --git a/pjsip-apps/src/samples/invtester.c b/pjsip-apps/src/samples/invtester.c new file mode 100644 index 00000000..734e8e9b --- /dev/null +++ b/pjsip-apps/src/samples/invtester.c @@ -0,0 +1,294 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org> + * + * 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 + */ + +/** + * invtester.c + * + * Send INVITE/re-INVITE without SDP. + */ + + +/* Include all headers. */ +#include <pjsip.h> +#include <pjlib-util.h> +#include <pjlib.h> + +#define THIS_FILE "invtester.c" + +#define PORT 50060 +#define PORT_STR ":50060" +#define SAME_BRANCH 0 +#define ACK_HAS_SDP 1 + +static pjsip_endpoint *sip_endpt; +static pj_bool_t quit_flag; +static pjsip_dialog *dlg; + + +/* Callback to handle incoming requests. */ +static void on_tsx_state(pjsip_transaction *tsx, pjsip_event *event); + +static pjsip_module mod_app = +{ + NULL, NULL, /* prev, next. */ + { "mod-app", 7 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + NULL, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + &on_tsx_state /* on_tsx_state() */ +}; + + +/* Worker thread */ +static int worker_thread(void *arg) +{ + PJ_UNUSED_ARG(arg); + + while (!quit_flag) { + pj_time_val timeout = {0, 500}; + pjsip_endpt_handle_events(sip_endpt, &timeout); + } + + return 0; +} + +/* Send request */ +static void send_request(const pjsip_method *method, + int cseq, + const pj_str_t *branch, + pj_bool_t with_offer) +{ + pjsip_tx_data *tdata; + pj_str_t dummy_sdp_str = + { + "v=0\r\n" + "o=- 3360842071 3360842071 IN IP4 192.168.0.68\r\n" + "s=pjmedia\r\n" + "c=IN IP4 192.168.0.68\r\n" + "t=0 0\r\n" + "m=audio 4000 RTP/AVP 0 101\r\n" + "a=rtcp:4001 IN IP4 192.168.0.68\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=sendrecv\r\n" + "a=rtpmap:101 telephone-event/8000\r\n" + "a=fmtp:101 0-15\r\n", + 0 + }; + pj_status_t status; + + status = pjsip_dlg_create_request(dlg, method, cseq, &tdata); + pj_assert(status == PJ_SUCCESS); + + if (branch) { + pjsip_via_hdr *via; + + via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); + pj_strdup(tdata->pool, &via->branch_param, branch); + } + + if (with_offer) { + pjsip_msg_body *body; + pj_str_t mime_application = { "application", 11}; + pj_str_t mime_sdp = {"sdp", 3}; + + + dummy_sdp_str.slen = pj_ansi_strlen(dummy_sdp_str.ptr); + body = pjsip_msg_body_create(tdata->pool, + &mime_application, &mime_sdp, + &dummy_sdp_str); + tdata->msg->body = body; + } + + status = pjsip_dlg_send_request(dlg, tdata, -1, NULL); + pj_assert(status == PJ_SUCCESS); +} + +/* Callback to handle incoming requests. */ +static void on_tsx_state(pjsip_transaction *tsx, pjsip_event *event) +{ + if (tsx->role == PJSIP_ROLE_UAC) { + if (tsx->method.id == PJSIP_INVITE_METHOD && tsx->state == PJSIP_TSX_STATE_TERMINATED) { +#if SAME_BRANCH + send_request(&pjsip_ack_method, tsx->cseq, &tsx->branch, ACK_HAS_SDP); +#else + send_request(&pjsip_ack_method, tsx->cseq, NULL, ACK_HAS_SDP); +#endif + } + + } else { + if (event->type == PJSIP_EVENT_RX_MSG && tsx->state == PJSIP_TSX_STATE_TRYING) { + pjsip_tx_data *tdata; + + pjsip_dlg_create_response(dlg, event->body.tsx_state.src.rdata, + 200, NULL, &tdata); + pjsip_dlg_send_response(dlg, tsx, tdata); + } + } +} + +/* make call */ +void make_call(char *uri, pj_bool_t with_offer) +{ + pj_str_t local = pj_str("sip:localhost" PORT_STR); + pj_str_t remote = pj_str(uri); + pj_status_t status; + + status = pjsip_dlg_create_uac(pjsip_ua_instance(), + &local, &local, &remote, &remote, &dlg); + pj_assert(status == PJ_SUCCESS); + + pjsip_dlg_inc_lock(dlg); + + status = pjsip_dlg_add_usage(dlg, &mod_app, NULL); + pj_assert(status == PJ_SUCCESS); + + pjsip_dlg_inc_session(dlg, &mod_app); + + send_request(&pjsip_invite_method, -1, NULL, with_offer); + + pjsip_dlg_dec_lock(dlg); +} + +/* reinvite */ +void reinvite(pj_bool_t with_offer) +{ + send_request(&pjsip_invite_method, -1, NULL, with_offer); +} + +/* hangup call */ +void hangup(void) +{ + send_request(&pjsip_bye_method, -1, NULL, PJ_FALSE); + pjsip_dlg_dec_session(dlg, &mod_app); +} + +/* + * main() + * + */ +int main(int argc, char *argv[]) +{ + pj_caching_pool cp; + pj_thread_t *thread; + pj_pool_t *pool; + pj_status_t status; + + if (argc != 2) { + puts("Error: destination URL needed"); + return 0; + } + + /* Must init PJLIB first: */ + status = pj_init(); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + + /* Then init PJLIB-UTIL: */ + status = pjlib_util_init(); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + /* Must create a pool factory before we can allocate any memory. */ + pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); + + + /* Create the endpoint: */ + status = pjsip_endpt_create(&cp.factory, "sipstateless", + &sip_endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + /* + * Add UDP transport, with hard-coded port + */ + { + pj_sockaddr_in addr; + + addr.sin_family = PJ_AF_INET; + addr.sin_addr.s_addr = 0; + addr.sin_port = pj_htons(PORT); + + status = pjsip_udp_transport_start( sip_endpt, &addr, NULL, 1, NULL); + if (status != PJ_SUCCESS) { + PJ_LOG(3,(THIS_FILE, + "Error starting UDP transport (port in use?)")); + return 1; + } + } + + status = pjsip_tsx_layer_init_module(sip_endpt); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_ua_init_module(sip_endpt, NULL); + pj_assert(status == PJ_SUCCESS); + + /* + * Register our module to receive incoming requests. + */ + status = pjsip_endpt_register_module( sip_endpt, &mod_app); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + pool = pjsip_endpt_create_pool(sip_endpt, "", 1000, 1000); + + status = pj_thread_create(pool, "", &worker_thread, NULL, 0, 0, &thread); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + printf("Destination URL: %s\n", argv[1]); + + for (;;) { + char line[10]; + + fgets(line, sizeof(line), stdin); + + switch (line[0]) { + case 'm': + make_call(argv[1], PJ_FALSE); + break; + case 'M': + make_call(argv[1], PJ_TRUE); + break; + case 'r': + reinvite(PJ_FALSE); + break; + case 'R': + reinvite(PJ_TRUE); + break; + case 'h': + hangup(); + break; + case 'q': + goto on_quit; + } + } + +on_quit: + quit_flag = 1; + pj_thread_join(thread); + + pjsip_endpt_destroy(sip_endpt); + pj_caching_pool_destroy(&cp); + pj_shutdown(); + return 0; +} + diff --git a/pjsip/include/pjsip-ua/sip_inv.h b/pjsip/include/pjsip-ua/sip_inv.h index 3a144f80..e74abfac 100644 --- a/pjsip/include/pjsip-ua/sip_inv.h +++ b/pjsip/include/pjsip-ua/sip_inv.h @@ -149,7 +149,10 @@ typedef struct pjsip_inv_callback /** * This callback is called when the invite session has received * new offer from peer. Application can inspect the remote offer - * in "offer". + * in "offer", and set the SDP answer with #pjsip_inv_set_sdp_answer(). + * When the application sends a SIP message to send the answer, + * this SDP answer will be negotiated with the offer, and the result + * will be sent with the SIP message. * * @param inv The invite session. * @param offer Remote offer. @@ -158,6 +161,22 @@ typedef struct pjsip_inv_callback const pjmedia_sdp_session *offer); /** + * This callback is optional, and it is used to ask the application + * to create a fresh offer, when the invite session has received + * re-INVITE without offer. This offer then will be sent in the + * 200/OK response to the re-INVITE request. + * + * If application doesn't implement this callback, the invite session + * will send the currently active SDP as the offer. + * + * @param inv The invite session. + * @param p_offer Pointer to receive the SDP offer created by + * application. + */ + void (*on_create_offer)(pjsip_inv_session *inv, + pjmedia_sdp_session **p_offer); + + /** * This callback is called after SDP offer/answer session has completed. * The status argument specifies the status of the offer/answer, * as returned by pjmedia_sdp_neg_negotiate(). diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c index 984501f6..c26c8bce 100644 --- a/pjsip/src/pjsip-ua/sip_inv.c +++ b/pjsip/src/pjsip-ua/sip_inv.c @@ -1225,8 +1225,13 @@ static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, /* MUST NOT do multiple SDP offer/answer in a single transaction. */ - if (tsx_inv_data->sdp_done) + if (tsx_inv_data->sdp_done) { + if (rdata->msg_info.msg->body) { + PJ_LOG(4,(inv->obj_name, "SDP negotiation done, message " + "body is ignored")); + } return PJ_SUCCESS; + } /* Check if SDP is present in the message. */ @@ -1383,11 +1388,19 @@ static pj_status_t process_answer( pjsip_inv_session *inv, } else if (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO && pjmedia_sdp_neg_has_local_answer(inv->neg) ) { + struct tsx_inv_data *tsx_inv_data; + + /* Get invite session's transaction data */ + tsx_inv_data = (struct tsx_inv_data*) + inv->invite_tsx->mod_data[mod_inv.mod.id]; status = inv_negotiate_sdp(inv); if (status != PJ_SUCCESS) return status; + /* Mark this transaction has having SDP offer/answer done. */ + tsx_inv_data->sdp_done = 1; + status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp); } } @@ -2503,12 +2516,33 @@ static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e) if (rdata->msg_info.msg->body != NULL) { status = process_answer(inv, 200, tdata, NULL); } else { - const pjmedia_sdp_session *active_sdp; - status = pjmedia_sdp_neg_send_local_offer(dlg->pool, - inv->neg, - &active_sdp); - if (status == PJ_SUCCESS) { - tdata->msg->body = create_sdp_body(tdata->pool, active_sdp); + /* INVITE does not have SDP. + * If on_create_offer() callback is implemented, ask app. + * to generate an offer, otherwise just send active local + * SDP to signal that nothing gets modified. + */ + pjmedia_sdp_session *sdp = NULL; + + if (mod_inv.cb.on_create_offer) { + (*mod_inv.cb.on_create_offer)(inv, &sdp); + if (sdp) { + status = pjmedia_sdp_neg_modify_local_offer(dlg->pool, + inv->neg, + sdp); + } + } + + if (sdp == NULL) { + const pjmedia_sdp_session *active_sdp = NULL; + status = pjmedia_sdp_neg_send_local_offer(dlg->pool, + inv->neg, + &active_sdp); + if (status == PJ_SUCCESS) + sdp = (pjmedia_sdp_session*) active_sdp; + } + + if (sdp) { + tdata->msg->body = create_sdp_body(tdata->pool, sdp); } } @@ -2604,6 +2638,16 @@ static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e) */ inv_set_cause(inv, tsx->status_code, &tsx->status_text); inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + + } else if (tsx->status_code >= 300 && tsx->status_code < 700) { + + pjmedia_sdp_neg_state neg_state; + + /* Outgoing INVITE transaction has failed, cancel SDP nego */ + neg_state = pjmedia_sdp_neg_get_state(inv->neg); + if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) { + pjmedia_sdp_neg_cancel_offer(inv->neg); + } } } } diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index 247c3085..e362679d 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -50,6 +50,12 @@ static void pjsua_call_on_rx_offer(pjsip_inv_session *inv, const pjmedia_sdp_session *offer); /* + * Called to generate new offer. + */ +static void pjsua_call_on_create_offer(pjsip_inv_session *inv, + pjmedia_sdp_session **offer); + +/* * This callback is called when transaction state has changed in INVITE * session. We use this to trap: * - incoming REFER request. @@ -118,6 +124,7 @@ pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg) inv_cb.on_new_session = &pjsua_call_on_forked; inv_cb.on_media_update = &pjsua_call_on_media_update; inv_cb.on_rx_offer = &pjsua_call_on_rx_offer; + inv_cb.on_create_offer = &pjsua_call_on_create_offer; inv_cb.on_tsx_state_changed = &pjsua_call_on_tsx_state_changed; @@ -2295,6 +2302,52 @@ static void pjsua_call_on_rx_offer(pjsip_inv_session *inv, /* + * Called to generate new offer. + */ +static void pjsua_call_on_create_offer(pjsip_inv_session *inv, + pjmedia_sdp_session **offer) +{ + pjsua_call *call; + pj_status_t status; + + PJSUA_LOCK(); + + call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id]; + + /* See if we've put call on hold. */ + if (call->media_st == PJSUA_CALL_MEDIA_LOCAL_HOLD) { + PJ_LOG(4,(THIS_FILE, + "Call %d: call is on-hold locally, creating inactive SDP ", + call->index)); + status = create_inactive_sdp( call, offer ); + } else { + + PJ_LOG(4,(THIS_FILE, "Call %d: asked to send a new offer", + call->index)); + + /* Init media channel */ + status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing media channel", status); + PJSUA_UNLOCK(); + return; + } + + status = pjsua_media_channel_create_sdp(call->index, call->inv->pool, offer); + } + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create local SDP", status); + PJSUA_UNLOCK(); + return; + } + + + PJSUA_UNLOCK(); +} + + +/* * Callback called by event framework when the xfer subscription state * has changed. */ |