From f8793f52a83dd232eddf2bb61f48cca6dd759ffd Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Fri, 17 Mar 2006 19:41:19 +0000 Subject: Added comments in simpleua.c git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@329 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip-apps/src/samples/simpleua.c | 418 +++++++++++++++++++++++++------------- 1 file changed, 272 insertions(+), 146 deletions(-) diff --git a/pjsip-apps/src/samples/simpleua.c b/pjsip-apps/src/samples/simpleua.c index d8b551fe..ae90cd8d 100644 --- a/pjsip-apps/src/samples/simpleua.c +++ b/pjsip-apps/src/samples/simpleua.c @@ -1,63 +1,111 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * 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 all PJSIP core headers. */ -#include -/* Include all PJMEDIA headers. */ -#include +/** + * simpleua.c + * + * This is a very simple SIP user agent complete with media. The user + * agent should do a proper SDP negotiation and start RTP media once + * SDP negotiation has completed. + * + * This program does not register to SIP server. + * + * Capabilities to be demonstrated here: + * - Basic call + * - UDP transport at port 5060 (hard coded) + * - RTP socket at port 4000 (hard coded) + * - proper SDP negotiation + * - PCMA/PCMU codec only. + * - Audio/media to sound device. + * + * + * Usage: + * - To make outgoing call, start simpleua with the URL of remote + * destination to contact. + * E.g.: + * simpleua sip:user@remote + * + * - Incoming calls will automatically be answered with 180, then 200. + * + * This program does not disconnect call. + * + * This program will quit once it has completed a single call. + */ -/* Include all PJMEDIA-CODEC headers. */ +/* Include all headers. */ +#include +#include #include - -/* Include all PJSIP-UA headers */ #include - -/* Include all PJSIP-SIMPLE headers */ #include - -/* Include all PJLIB-UTIL headers. */ #include - -/* Include all PJLIB headers. */ #include - +/* For logging purpose. */ #define THIS_FILE "simpleua.c" /* * Static variables. */ -static pj_bool_t g_complete; -/* Global endpoint instance. */ -static pjsip_endpoint *g_endpt; +static pj_bool_t g_complete; /* Quit flag. */ +static pjsip_endpoint *g_endpt; /* SIP endpoint. */ +static pj_caching_pool cp; /* Global pool factory. */ -/* Global caching pool factory. */ -static pj_caching_pool cp; +static pjmedia_endpt *g_med_endpt; /* Media endpoint. */ +static pjmedia_sock_info g_med_skinfo; /* Socket info for media */ -/* Global media endpoint. */ -static pjmedia_endpt *g_med_endpt; -static pjmedia_sock_info g_med_skinfo; - -/* Call variables. */ -static pjsip_inv_session *g_inv; -static pjmedia_session *g_med_session; -static pjmedia_snd_port *g_snd_player; -static pjmedia_snd_port *g_snd_rec; +/* Call variables: */ +static pjsip_inv_session *g_inv; /* Current invite session. */ +static pjmedia_session *g_med_session; /* Call's media session. */ +static pjmedia_snd_port *g_snd_player; /* Call's sound player */ +static pjmedia_snd_port *g_snd_rec; /* Call's sound recorder. */ /* - * Prototypes. + * Prototypes: */ + +/* Callback to be called when SDP negotiation is done in the call: */ static void call_on_media_update( pjsip_inv_session *inv, pj_status_t status); + +/* Callback to be called when invite session's state has changed: */ static void call_on_state_changed( pjsip_inv_session *inv, pjsip_event *e); + +/* Callback to be called when dialog has forked: */ static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e); + +/* Callback to be called to handle incoming requests outside dialogs: */ static pj_bool_t on_rx_request( pjsip_rx_data *rdata ); -/* Module to receive incoming requests (e.g. INVITE). */ + + +/* This is a PJSIP module to be registered by application to handle + * incoming requests outside any dialogs/transactions. The main purpose + * here is to handle incoming INVITE request message, where we will + * create a dialog and INVITE session for it. + */ static pjsip_module mod_simpleua = { NULL, NULL, /* prev, next. */ @@ -77,7 +125,7 @@ static pjsip_module mod_simpleua = /* - * Show error. + * Util to display the error message for the specified error code */ static int app_perror( const char *sender, const char *title, pj_status_t status) @@ -93,25 +141,28 @@ static int app_perror( const char *sender, const char *title, /* * main() + * + * If called with argument, treat argument as SIP URL to be called. + * Otherwise wait for incoming calls. */ int main(int argc, char *argv[]) { pj_status_t status; - /* Init PJLIB */ + /* Must init PJLIB first: */ status = pj_init(); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); - /* Init PJLIB-UTIL: */ + + /* Then init PJLIB-UTIL: */ status = pjlib_util_init(); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); - /* Init memory pool: */ - - /* Init caching pool. */ + /* Must create a pool factory before we can allocate any memory. */ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); + /* Create global endpoint: */ { const pj_str_t *hostname; @@ -132,8 +183,12 @@ int main(int argc, char *argv[]) PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); } + /* - * Add UDP transport. + * Add UDP transport, with hard-coded port + * Alternatively, application can use pjsip_udp_transport_attach() to + * start UDP transport, if it already has an UDP socket (e.g. after it + * resolves the address with STUN). */ { pj_sockaddr_in addr; @@ -151,19 +206,32 @@ int main(int argc, char *argv[]) /* - * Init transaction layer. + * Init transaction layer. + * This will create/initialize transaction hash tables etc. */ status = pjsip_tsx_layer_init_module(g_endpt); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + /* - * Initialize UA layer module: + * Initialize UA layer module. + * This will create/initialize dialog hash tables etc. */ status = pjsip_ua_init_module( g_endpt, NULL ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); - /* + + /* * Init invite session module. + * The invite session module initialization takes additional argument, + * i.e. a structure containing callbacks to be called on specific + * occurence of events. + * + * The on_state_changed and on_new_session callbacks are mandatory. + * Application must supply the callback function. + * + * We use on_media_update() callback in this application to start + * media transmission. */ { pjsip_inv_callback inv_cb; @@ -181,14 +249,15 @@ int main(int argc, char *argv[]) /* - * Register module to receive incoming requests. + * Register our module to receive incoming requests. */ status = pjsip_endpt_register_module( g_endpt, &mod_simpleua); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* - * Init media endpoint: + * Initialize media endpoint. + * This will implicitly initialize PJMEDIA too. */ status = pjmedia_endpt_create(&cp.factory, &g_med_endpt); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); @@ -201,6 +270,7 @@ int main(int argc, char *argv[]) /* * Initialize RTP socket info for the media. + * The RTP socket is hard-codec to port 4000. */ status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &g_med_skinfo.rtp_sock); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); @@ -249,26 +319,49 @@ int main(int argc, char *argv[]) return 1; } - /* Get media capability from media endpoint: */ - status = pjmedia_endpt_create_sdp( g_med_endpt, dlg->pool, 1, - &g_med_skinfo, &local_sdp); + /* Get the SDP body to be put in the outgoing INVITE, by asking + * media endpoint to create one for us. The SDP will contain all + * codecs that have been registered to it (in this case, only + * PCMA and PCMU), plus telephony event. + */ + status = pjmedia_endpt_create_sdp( g_med_endpt, /* the media endpt */ + dlg->pool, /* pool. */ + 1, /* # of streams */ + &g_med_skinfo, /* RTP sock info */ + &local_sdp); /* the SDP result */ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); - /* Create the INVITE session: */ + + /* Create the INVITE session, and pass the SDP returned earlier + * as the session's initial capability. + */ status = pjsip_inv_create_uac( dlg, local_sdp, 0, &g_inv); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); - /* Create initial INVITE request: */ + + /* Create initial INVITE request. + * This INVITE request will contain a perfectly good request and + * an SDP body as well. + */ status = pjsip_inv_invite(g_inv, &tdata); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); - /* Send initial INVITE request: */ + + + /* Send initial INVITE request. + * From now on, the invite session's state will be reported to us + * via the invite session callbacks. + */ status = pjsip_inv_send_msg(g_inv, tdata); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + } else { + + /* No URL to make call to */ + PJ_LOG(3,(THIS_FILE, "Ready to accept incoming calls...")); } @@ -286,6 +379,9 @@ int main(int argc, char *argv[]) /* * Callback when INVITE session state has changed. + * This callback is registered when the invite session module is initialized. + * We mostly want to know when the invite session has been disconnected, + * so that we can quit the application. */ static void call_on_state_changed( pjsip_inv_session *inv, pjsip_event *e) @@ -307,106 +403,18 @@ static void call_on_state_changed( pjsip_inv_session *inv, } } -/* - * Callback when media negotiation has completed. - */ -static void call_on_media_update( pjsip_inv_session *inv, - pj_status_t status) -{ - 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 */ - status = pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp); - - status = pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp); - - /* Create new media session. - * The media session is active immediately. - */ - status = pjmedia_session_create( g_med_endpt, 1, - &g_med_skinfo, - local_sdp, remote_sdp, - NULL, &g_med_session ); - if (status != PJ_SUCCESS) { - app_perror( THIS_FILE, "Unable to create media session", status); - return; - } - - /* Get the port interface of the first stream in the session. */ - 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.sample_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.sample_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.sample_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. */ -} - - -/* - * This callback is called when dialog has forked - */ +/* This callback is called when dialog has forked. */ static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e) { + /* To be done... */ } + /* * Callback when incoming requests outside any transactions and any - * dialogs are received + * dialogs are received. We're only interested to hande incoming INVITE + * request, and we'll reject any other requests with 500 response. */ static pj_bool_t on_rx_request( pjsip_rx_data *rdata ) { @@ -416,6 +424,7 @@ static pj_bool_t on_rx_request( pjsip_rx_data *rdata ) unsigned options = 0; pj_status_t status; + /* * Respond (statelessly) any non-INVITE requests with 500 */ @@ -429,6 +438,7 @@ static pj_bool_t on_rx_request( pjsip_rx_data *rdata ) return PJ_TRUE; } + /* * Reject INVITE if we already have an INVITE session in progress. */ @@ -478,28 +488,35 @@ static pj_bool_t on_rx_request( pjsip_rx_data *rdata ) &local_sdp); PJ_ASSERT_RETURN(status == PJ_SUCCESS, PJ_TRUE); + /* - * Create invite session: + * Create invite session, and pass both the UAS dialog and the SDP + * capability to the session. */ status = pjsip_inv_create_uas( dlg, rdata, local_sdp, 0, &g_inv); PJ_ASSERT_RETURN(status == PJ_SUCCESS, PJ_TRUE); + /* * Initially send 180 response. + * + * The very first response to an INVITE must be created with + * pjsip_inv_initial_answer(). Subsequent responses to the same + * transaction MUST use pjsip_inv_answer(). */ status = pjsip_inv_initial_answer(g_inv, rdata, 180, NULL, NULL, &tdata); PJ_ASSERT_RETURN(status == PJ_SUCCESS, PJ_TRUE); - /* - * Send the 180 response. - */ + + /* Send the 180 response. */ status = pjsip_inv_send_msg(g_inv, tdata); PJ_ASSERT_RETURN(status == PJ_SUCCESS, PJ_TRUE); + /* - * Now send 200 response. + * Now create 200 response. */ status = pjsip_inv_answer( g_inv, 200, NULL, /* st_code and st_text */ @@ -513,7 +530,116 @@ static pj_bool_t on_rx_request( pjsip_rx_data *rdata ) status = pjsip_inv_send_msg(g_inv, tdata); PJ_ASSERT_RETURN(status == PJ_SUCCESS, PJ_TRUE); + + /* Done. + * When the call is disconnected, it will be reported via the callback. + */ + return PJ_TRUE; } + +/* + * 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) +{ + 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 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, 1, + &g_med_skinfo, + local_sdp, remote_sdp, + 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.sample_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.sample_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.sample_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. */ +} + + -- cgit v1.2.3