summaryrefslogtreecommitdiff
path: root/pjsip-apps/src/samples
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip-apps/src/samples')
-rw-r--r--pjsip-apps/src/samples/aectest.c304
-rw-r--r--pjsip-apps/src/samples/auddemo.c582
-rw-r--r--pjsip-apps/src/samples/aviplay.c560
-rw-r--r--pjsip-apps/src/samples/confbench.c347
-rw-r--r--pjsip-apps/src/samples/confsample.c609
-rw-r--r--pjsip-apps/src/samples/debug.c32
-rw-r--r--pjsip-apps/src/samples/encdec.c263
-rw-r--r--pjsip-apps/src/samples/footprint.c654
-rw-r--r--pjsip-apps/src/samples/httpdemo.c183
-rw-r--r--pjsip-apps/src/samples/icedemo.c1276
-rw-r--r--pjsip-apps/src/samples/invtester.c295
-rw-r--r--pjsip-apps/src/samples/jbsim.c1141
-rw-r--r--pjsip-apps/src/samples/latency.c202
-rw-r--r--pjsip-apps/src/samples/level.c179
-rw-r--r--pjsip-apps/src/samples/main_rtems.c12
-rw-r--r--pjsip-apps/src/samples/mix.c237
-rw-r--r--pjsip-apps/src/samples/pcaputil.c540
-rw-r--r--pjsip-apps/src/samples/pjsip-perf.c1853
-rw-r--r--pjsip-apps/src/samples/playfile.c217
-rw-r--r--pjsip-apps/src/samples/playsine.c317
-rw-r--r--pjsip-apps/src/samples/proxy.h585
-rw-r--r--pjsip-apps/src/samples/recfile.c202
-rw-r--r--pjsip-apps/src/samples/resampleplay.c232
-rw-r--r--pjsip-apps/src/samples/simple_pjsua.c201
-rw-r--r--pjsip-apps/src/samples/simpleua.c1030
-rw-r--r--pjsip-apps/src/samples/sipecho.c688
-rw-r--r--pjsip-apps/src/samples/siprtp.c2189
-rw-r--r--pjsip-apps/src/samples/siprtp_report.c231
-rw-r--r--pjsip-apps/src/samples/sipstateless.c243
-rw-r--r--pjsip-apps/src/samples/stateful_proxy.c587
-rw-r--r--pjsip-apps/src/samples/stateless_proxy.c255
-rw-r--r--pjsip-apps/src/samples/stereotest.c336
-rw-r--r--pjsip-apps/src/samples/streamutil.c1174
-rw-r--r--pjsip-apps/src/samples/strerror.c71
-rw-r--r--pjsip-apps/src/samples/tonegen.c159
-rw-r--r--pjsip-apps/src/samples/util.h173
-rw-r--r--pjsip-apps/src/samples/vid_streamutil.c967
37 files changed, 19126 insertions, 0 deletions
diff --git a/pjsip-apps/src/samples/aectest.c b/pjsip-apps/src/samples/aectest.c
new file mode 100644
index 0000000..ff95db4
--- /dev/null
+++ b/pjsip-apps/src/samples/aectest.c
@@ -0,0 +1,304 @@
+/* $Id: aectest.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+
+/**
+ * \page page_pjmedia_samples_aectest_c Samples: AEC Test (aectest.c)
+ *
+ * Play a file to speaker, run AEC, and record the microphone input
+ * to see if echo is coming.
+ *
+ * This file is pjsip-apps/src/samples/aectest.c
+ *
+ * \includelineno aectest.c
+ */
+#include <pjmedia.h>
+#include <pjlib-util.h> /* pj_getopt */
+#include <pjlib.h>
+
+#define THIS_FILE "aectest.c"
+#define PTIME 20
+#define TAIL_LENGTH 200
+
+static const char *desc =
+" FILE \n"
+" \n"
+" aectest.c \n"
+" \n"
+" PURPOSE \n"
+" \n"
+" Test the AEC effectiveness. \n"
+" \n"
+" USAGE \n"
+" \n"
+" aectest [options] <PLAY.WAV> <REC.WAV> <OUTPUT.WAV> \n"
+" \n"
+" <PLAY.WAV> is the signal played to the speaker. \n"
+" <REC.WAV> is the signal captured from the microphone. \n"
+" <OUTPUT.WAV> is the output file to store the test result \n"
+"\n"
+" options:\n"
+" -d The delay between playback and capture in ms, at least 25 ms.\n"
+" Default is 25 ms. See note below. \n"
+" -l Set the echo tail length in ms. Default is 200 ms \n"
+" -r Set repeat count (default=1) \n"
+" -a Algorithm: 0=default, 1=speex, 3=echo suppress \n"
+" -i Interactive \n"
+"\n"
+" Note that for the AEC internal buffering mechanism, it is required\n"
+" that the echoed signal (in REC.WAV) is delayed from the \n"
+" corresponding reference signal (in PLAY.WAV) at least as much as \n"
+" frame time + PJMEDIA_WSOLA_DELAY_MSEC. In this application, frame \n"
+" time is 20 ms and default PJMEDIA_WSOLA_DELAY_MSEC is 5 ms, hence \n"
+" 25 ms delay is the minimum value. \n";
+
+/*
+ * Sample session:
+ *
+ * -d 100 -a 1 ../bin/orig8.wav ../bin/echo8.wav ../bin/result8.wav
+ */
+
+static void app_perror(const char *sender, const char *title, pj_status_t st)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(st, errmsg, sizeof(errmsg));
+ PJ_LOG(3,(sender, "%s: %s", title, errmsg));
+}
+
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+ pj_caching_pool cp;
+ pjmedia_endpt *med_endpt;
+ pj_pool_t *pool;
+ pjmedia_port *wav_play;
+ pjmedia_port *wav_rec;
+ pjmedia_port *wav_out;
+ pj_status_t status;
+ pjmedia_echo_state *ec;
+ pjmedia_frame play_frame, rec_frame;
+ unsigned opt = 0;
+ unsigned latency_ms = 25;
+ unsigned tail_ms = TAIL_LENGTH;
+ pj_timestamp t0, t1;
+ int i, repeat=1, interactive=0, c;
+
+ pj_optind = 0;
+ while ((c=pj_getopt(argc, argv, "d:l:a:r:i")) !=-1) {
+ switch (c) {
+ case 'd':
+ latency_ms = atoi(pj_optarg);
+ if (latency_ms < 25) {
+ puts("Invalid delay");
+ puts(desc);
+ }
+ break;
+ case 'l':
+ tail_ms = atoi(pj_optarg);
+ break;
+ case 'a':
+ {
+ int alg = atoi(pj_optarg);
+ switch (alg) {
+ case 0:
+ opt = 0;
+ case 1:
+ opt = PJMEDIA_ECHO_SPEEX;
+ break;
+ case 3:
+ opt = PJMEDIA_ECHO_SIMPLE;
+ break;
+ default:
+ puts("Invalid algorithm");
+ puts(desc);
+ return 1;
+ }
+ }
+ break;
+ case 'r':
+ repeat = atoi(pj_optarg);
+ if (repeat < 1) {
+ puts("Invalid repeat count");
+ puts(desc);
+ return 1;
+ }
+ break;
+ case 'i':
+ interactive = 1;
+ break;
+ }
+ }
+
+ if (argc - pj_optind != 3) {
+ puts("Error: missing argument(s)");
+ puts(desc);
+ return 1;
+ }
+
+ /* Must init PJLIB first: */
+ status = pj_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);
+
+ /*
+ * Initialize media endpoint.
+ * This will implicitly initialize PJMEDIA too.
+ */
+ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Create memory pool for our file player */
+ pool = pj_pool_create( &cp.factory, /* pool factory */
+ "wav", /* pool name. */
+ 4000, /* init size */
+ 4000, /* increment size */
+ NULL /* callback on error */
+ );
+
+ /* Open wav_play */
+ status = pjmedia_wav_player_port_create(pool, argv[pj_optind], PTIME,
+ PJMEDIA_FILE_NO_LOOP, 0,
+ &wav_play);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error opening playback WAV file", status);
+ return 1;
+ }
+
+ /* Open recorded wav */
+ status = pjmedia_wav_player_port_create(pool, argv[pj_optind+1], PTIME,
+ PJMEDIA_FILE_NO_LOOP, 0,
+ &wav_rec);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error opening recorded WAV file", status);
+ return 1;
+ }
+
+ /* play and rec WAVs must have the same clock rate */
+ if (PJMEDIA_PIA_SRATE(&wav_play->info) != PJMEDIA_PIA_SRATE(&wav_rec->info)) {
+ puts("Error: clock rate mismatch in the WAV files");
+ return 1;
+ }
+
+ /* .. and channel count */
+ if (PJMEDIA_PIA_CCNT(&wav_play->info) != PJMEDIA_PIA_CCNT(&wav_rec->info)) {
+ puts("Error: clock rate mismatch in the WAV files");
+ return 1;
+ }
+
+ /* Create output wav */
+ status = pjmedia_wav_writer_port_create(pool, argv[pj_optind+2],
+ PJMEDIA_PIA_SRATE(&wav_play->info),
+ PJMEDIA_PIA_CCNT(&wav_play->info),
+ PJMEDIA_PIA_SPF(&wav_play->info),
+ PJMEDIA_PIA_BITS(&wav_play->info),
+ 0, 0, &wav_out);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error opening output WAV file", status);
+ return 1;
+ }
+
+ /* Create echo canceller */
+ status = pjmedia_echo_create2(pool, PJMEDIA_PIA_SRATE(&wav_play->info),
+ PJMEDIA_PIA_CCNT(&wav_play->info),
+ PJMEDIA_PIA_SPF(&wav_play->info),
+ tail_ms, latency_ms,
+ opt, &ec);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error creating EC", status);
+ return 1;
+ }
+
+
+ /* Processing loop */
+ play_frame.buf = pj_pool_alloc(pool, PJMEDIA_PIA_SPF(&wav_play->info)<<1);
+ rec_frame.buf = pj_pool_alloc(pool, PJMEDIA_PIA_SPF(&wav_play->info)<<1);
+ pj_get_timestamp(&t0);
+ for (i=0; i < repeat; ++i) {
+ for (;;) {
+ play_frame.size = PJMEDIA_PIA_SPF(&wav_play->info) << 1;
+ status = pjmedia_port_get_frame(wav_play, &play_frame);
+ if (status != PJ_SUCCESS)
+ break;
+
+ status = pjmedia_echo_playback(ec, (short*)play_frame.buf);
+
+ rec_frame.size = PJMEDIA_PIA_SPF(&wav_play->info) << 1;
+ status = pjmedia_port_get_frame(wav_rec, &rec_frame);
+ if (status != PJ_SUCCESS)
+ break;
+
+ status = pjmedia_echo_capture(ec, (short*)rec_frame.buf, 0);
+
+ //status = pjmedia_echo_cancel(ec, (short*)rec_frame.buf,
+ // (short*)play_frame.buf, 0, NULL);
+
+ pjmedia_port_put_frame(wav_out, &rec_frame);
+ }
+
+ pjmedia_wav_player_port_set_pos(wav_play, 0);
+ pjmedia_wav_player_port_set_pos(wav_rec, 0);
+ }
+ pj_get_timestamp(&t1);
+
+ i = pjmedia_wav_writer_port_get_pos(wav_out) / sizeof(pj_int16_t) * 1000 /
+ (PJMEDIA_PIA_SRATE(&wav_out->info) * PJMEDIA_PIA_CCNT(&wav_out->info));
+ PJ_LOG(3,(THIS_FILE, "Processed %3d.%03ds audio",
+ i / 1000, i % 1000));
+ PJ_LOG(3,(THIS_FILE, "Completed in %u msec\n", pj_elapsed_msec(&t0, &t1)));
+
+ /* Destroy file port(s) */
+ status = pjmedia_port_destroy( wav_play );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ status = pjmedia_port_destroy( wav_rec );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ status = pjmedia_port_destroy( wav_out );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Destroy ec */
+ pjmedia_echo_destroy(ec);
+
+ /* Release application pool */
+ pj_pool_release( pool );
+
+ /* Destroy media endpoint. */
+ pjmedia_endpt_destroy( med_endpt );
+
+ /* Destroy pool factory */
+ pj_caching_pool_destroy( &cp );
+
+ /* Shutdown PJLIB */
+ pj_shutdown();
+
+ if (interactive) {
+ char s[10], *dummy;
+ puts("ENTER to quit");
+ dummy = fgets(s, sizeof(s), stdin);
+ }
+
+ /* Done. */
+ return 0;
+}
+
diff --git a/pjsip-apps/src/samples/auddemo.c b/pjsip-apps/src/samples/auddemo.c
new file mode 100644
index 0000000..c4e3562
--- /dev/null
+++ b/pjsip-apps/src/samples/auddemo.c
@@ -0,0 +1,582 @@
+/* $Id: auddemo.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+#include <pjmedia-audiodev/audiodev.h>
+#include <pjmedia-audiodev/audiotest.h>
+#include <pjmedia.h>
+#include <pjlib.h>
+#include <pjlib-util.h>
+
+#define THIS_FILE "auddemo.c"
+#define MAX_DEVICES 64
+#define WAV_FILE "auddemo.wav"
+
+
+static unsigned dev_count;
+static unsigned playback_lat = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+static unsigned capture_lat = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+
+static void app_perror(const char *title, pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ printf( "%s: %s (err=%d)\n",
+ title, errmsg, status);
+}
+
+static void list_devices(void)
+{
+ unsigned i;
+ pj_status_t status;
+
+ dev_count = pjmedia_aud_dev_count();
+ if (dev_count == 0) {
+ PJ_LOG(3,(THIS_FILE, "No devices found"));
+ return;
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Found %d devices:", dev_count));
+
+ for (i=0; i<dev_count; ++i) {
+ pjmedia_aud_dev_info info;
+
+ status = pjmedia_aud_dev_get_info(i, &info);
+ if (status != PJ_SUCCESS)
+ continue;
+
+ PJ_LOG(3,(THIS_FILE," %2d: %s [%s] (%d/%d)",
+ i, info.driver, info.name, info.input_count, info.output_count));
+ }
+}
+
+static const char *decode_caps(unsigned caps)
+{
+ static char text[200];
+ unsigned i;
+
+ text[0] = '\0';
+
+ for (i=0; i<31; ++i) {
+ if ((1 << i) & caps) {
+ const char *capname;
+ capname = pjmedia_aud_dev_cap_name((pjmedia_aud_dev_cap)(1 << i),
+ NULL);
+ strcat(text, capname);
+ strcat(text, " ");
+ }
+ }
+
+ return text;
+}
+
+static void show_dev_info(unsigned index)
+{
+#define H "%-20s"
+ pjmedia_aud_dev_info info;
+ char formats[200];
+ pj_status_t status;
+
+ if (index >= dev_count) {
+ PJ_LOG(1,(THIS_FILE, "Error: invalid index %u", index));
+ return;
+ }
+
+ status = pjmedia_aud_dev_get_info(index, &info);
+ if (status != PJ_SUCCESS) {
+ app_perror("pjmedia_aud_dev_get_info() error", status);
+ return;
+ }
+
+ PJ_LOG(3, (THIS_FILE, "Device at index %u:", index));
+ PJ_LOG(3, (THIS_FILE, "-------------------------"));
+
+ PJ_LOG(3, (THIS_FILE, H": %u (0x%x)", "ID", index, index));
+ PJ_LOG(3, (THIS_FILE, H": %s", "Name", info.name));
+ PJ_LOG(3, (THIS_FILE, H": %s", "Driver", info.driver));
+ PJ_LOG(3, (THIS_FILE, H": %u", "Input channels", info.input_count));
+ PJ_LOG(3, (THIS_FILE, H": %u", "Output channels", info.output_count));
+ PJ_LOG(3, (THIS_FILE, H": %s", "Capabilities", decode_caps(info.caps)));
+
+ formats[0] = '\0';
+ if (info.caps & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) {
+ unsigned i;
+
+ for (i=0; i<info.ext_fmt_cnt; ++i) {
+ char bitrate[32];
+
+ switch (info.ext_fmt[i].id) {
+ case PJMEDIA_FORMAT_L16:
+ strcat(formats, "L16/");
+ break;
+ case PJMEDIA_FORMAT_PCMA:
+ strcat(formats, "PCMA/");
+ break;
+ case PJMEDIA_FORMAT_PCMU:
+ strcat(formats, "PCMU/");
+ break;
+ case PJMEDIA_FORMAT_AMR:
+ strcat(formats, "AMR/");
+ break;
+ case PJMEDIA_FORMAT_G729:
+ strcat(formats, "G729/");
+ break;
+ case PJMEDIA_FORMAT_ILBC:
+ strcat(formats, "ILBC/");
+ break;
+ default:
+ strcat(formats, "unknown/");
+ break;
+ }
+ sprintf(bitrate, "%u", info.ext_fmt[i].det.aud.avg_bps);
+ strcat(formats, bitrate);
+ strcat(formats, " ");
+ }
+ }
+ PJ_LOG(3, (THIS_FILE, H": %s", "Extended formats", formats));
+
+#undef H
+}
+
+static void test_device(pjmedia_dir dir, unsigned rec_id, unsigned play_id,
+ unsigned clock_rate, unsigned ptime,
+ unsigned chnum)
+{
+ pjmedia_aud_param param;
+ pjmedia_aud_test_results result;
+ pj_status_t status;
+
+ if (dir & PJMEDIA_DIR_CAPTURE) {
+ status = pjmedia_aud_dev_default_param(rec_id, &param);
+ } else {
+ status = pjmedia_aud_dev_default_param(play_id, &param);
+ }
+
+ if (status != PJ_SUCCESS) {
+ app_perror("pjmedia_aud_dev_default_param()", status);
+ return;
+ }
+
+ param.dir = dir;
+ param.rec_id = rec_id;
+ param.play_id = play_id;
+ param.clock_rate = clock_rate;
+ param.channel_count = chnum;
+ param.samples_per_frame = clock_rate * chnum * ptime / 1000;
+
+ /* Latency settings */
+ param.flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
+ param.input_latency_ms = capture_lat;
+ param.output_latency_ms = playback_lat;
+
+ PJ_LOG(3,(THIS_FILE, "Performing test.."));
+
+ status = pjmedia_aud_test(&param, &result);
+ if (status != PJ_SUCCESS) {
+ app_perror("Test has completed with error", status);
+ return;
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Done. Result:"));
+
+ if (dir & PJMEDIA_DIR_CAPTURE) {
+ if (result.rec.frame_cnt==0) {
+ PJ_LOG(1,(THIS_FILE, "Error: no frames captured!"));
+ } else {
+ PJ_LOG(3,(THIS_FILE, " %-20s: interval (min/max/avg/dev)=%u/%u/%u/%u, burst=%u",
+ "Recording result",
+ result.rec.min_interval,
+ result.rec.max_interval,
+ result.rec.avg_interval,
+ result.rec.dev_interval,
+ result.rec.max_burst));
+ }
+ }
+
+ if (dir & PJMEDIA_DIR_PLAYBACK) {
+ if (result.play.frame_cnt==0) {
+ PJ_LOG(1,(THIS_FILE, "Error: no playback!"));
+ } else {
+ PJ_LOG(3,(THIS_FILE, " %-20s: interval (min/max/avg/dev)=%u/%u/%u/%u, burst=%u",
+ "Playback result",
+ result.play.min_interval,
+ result.play.max_interval,
+ result.play.avg_interval,
+ result.play.dev_interval,
+ result.play.max_burst));
+ }
+ }
+
+ if (dir==PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+ if (result.rec_drift_per_sec == 0) {
+ PJ_LOG(3,(THIS_FILE, " No clock drift detected"));
+ } else {
+ const char *which = result.rec_drift_per_sec>=0 ? "faster" : "slower";
+ unsigned drift = result.rec_drift_per_sec>=0 ?
+ result.rec_drift_per_sec :
+ -result.rec_drift_per_sec;
+
+ PJ_LOG(3,(THIS_FILE, " Clock drifts detected. Capture device "
+ "is running %d samples per second %s "
+ "than the playback device",
+ drift, which));
+ }
+ }
+}
+
+
+static pj_status_t wav_rec_cb(void *user_data, pjmedia_frame *frame)
+{
+ return pjmedia_port_put_frame((pjmedia_port*)user_data, frame);
+}
+
+static void record(unsigned rec_index, const char *filename)
+{
+ pj_pool_t *pool = NULL;
+ pjmedia_port *wav = NULL;
+ pjmedia_aud_param param;
+ pjmedia_aud_stream *strm = NULL;
+ char line[10], *dummy;
+ pj_status_t status;
+
+ if (filename == NULL)
+ filename = WAV_FILE;
+
+ pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(), "wav",
+ 1000, 1000, NULL);
+
+ status = pjmedia_wav_writer_port_create(pool, filename, 16000,
+ 1, 320, 16, 0, 0, &wav);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error creating WAV file", status);
+ goto on_return;
+ }
+
+ status = pjmedia_aud_dev_default_param(rec_index, &param);
+ if (status != PJ_SUCCESS) {
+ app_perror("pjmedia_aud_dev_default_param()", status);
+ goto on_return;
+ }
+
+ param.dir = PJMEDIA_DIR_CAPTURE;
+ param.clock_rate = PJMEDIA_PIA_SRATE(&wav->info);
+ param.samples_per_frame = PJMEDIA_PIA_SPF(&wav->info);
+ param.channel_count = PJMEDIA_PIA_CCNT(&wav->info);
+ param.bits_per_sample = PJMEDIA_PIA_BITS(&wav->info);
+
+ status = pjmedia_aud_stream_create(&param, &wav_rec_cb, NULL, wav,
+ &strm);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error opening the sound device", status);
+ goto on_return;
+ }
+
+ status = pjmedia_aud_stream_start(strm);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error starting the sound device", status);
+ goto on_return;
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Recording started, press ENTER to stop"));
+ dummy = fgets(line, sizeof(line), stdin);
+
+on_return:
+ if (strm) {
+ pjmedia_aud_stream_stop(strm);
+ pjmedia_aud_stream_destroy(strm);
+ }
+ if (wav)
+ pjmedia_port_destroy(wav);
+ if (pool)
+ pj_pool_release(pool);
+}
+
+
+static pj_status_t wav_play_cb(void *user_data, pjmedia_frame *frame)
+{
+ return pjmedia_port_get_frame((pjmedia_port*)user_data, frame);
+}
+
+
+static void play_file(unsigned play_index, const char *filename)
+{
+ pj_pool_t *pool = NULL;
+ pjmedia_port *wav = NULL;
+ pjmedia_aud_param param;
+ pjmedia_aud_stream *strm = NULL;
+ char line[10], *dummy;
+ pj_status_t status;
+
+ if (filename == NULL)
+ filename = WAV_FILE;
+
+ pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(), "wav",
+ 1000, 1000, NULL);
+
+ status = pjmedia_wav_player_port_create(pool, filename, 20, 0, 0, &wav);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error opening WAV file", status);
+ goto on_return;
+ }
+
+ status = pjmedia_aud_dev_default_param(play_index, &param);
+ if (status != PJ_SUCCESS) {
+ app_perror("pjmedia_aud_dev_default_param()", status);
+ goto on_return;
+ }
+
+ param.dir = PJMEDIA_DIR_PLAYBACK;
+ param.clock_rate = PJMEDIA_PIA_SRATE(&wav->info);
+ param.samples_per_frame = PJMEDIA_PIA_SPF(&wav->info);
+ param.channel_count = PJMEDIA_PIA_CCNT(&wav->info);
+ param.bits_per_sample = PJMEDIA_PIA_BITS(&wav->info);
+
+ status = pjmedia_aud_stream_create(&param, NULL, &wav_play_cb, wav,
+ &strm);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error opening the sound device", status);
+ goto on_return;
+ }
+
+ status = pjmedia_aud_stream_start(strm);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error starting the sound device", status);
+ goto on_return;
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Playback started, press ENTER to stop"));
+ dummy = fgets(line, sizeof(line), stdin);
+
+on_return:
+ if (strm) {
+ pjmedia_aud_stream_stop(strm);
+ pjmedia_aud_stream_destroy(strm);
+ }
+ if (wav)
+ pjmedia_port_destroy(wav);
+ if (pool)
+ pj_pool_release(pool);
+}
+
+
+static void print_menu(void)
+{
+ puts("");
+ puts("Audio demo menu:");
+ puts("-------------------------------");
+ puts(" l List devices");
+ puts(" R Refresh devices");
+ puts(" i ID Show device info for device ID");
+ puts(" t RID PID CR PTIM [CH] Perform test on the device:");
+ puts(" RID: record device ID (-1 for no)");
+ puts(" PID: playback device ID (-1 for no)");
+ puts(" CR: clock rate");
+ puts(" PTIM: ptime in ms");
+ puts(" CH: # of channels");
+ puts(" r RID [FILE] Record capture device RID to WAV file");
+ puts(" p PID [FILE] Playback WAV file to device ID PID");
+ puts(" d [RLAT PLAT] Get/set sound device latencies (in ms):");
+ puts(" Specify no param to get current latencies setting");
+ puts(" RLAT: record latency (-1 for default)");
+ puts(" PLAT: playback latency (-1 for default)");
+ puts(" v Toggle log verbosity");
+ puts(" q Quit");
+ puts("");
+ printf("Enter selection: ");
+ fflush(stdout);
+}
+
+int main()
+{
+ pj_caching_pool cp;
+ pj_bool_t done = PJ_FALSE;
+ pj_status_t status;
+
+ /* Init pjlib */
+ status = pj_init();
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
+
+ pj_log_set_decor(PJ_LOG_HAS_NEWLINE);
+
+ /* Must create a pool factory before we can allocate any memory. */
+ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+
+ status = pjmedia_aud_subsys_init(&cp.factory);
+ if (status != PJ_SUCCESS) {
+ app_perror("pjmedia_aud_subsys_init()", status);
+ pj_caching_pool_destroy(&cp);
+ pj_shutdown();
+ return 1;
+ }
+
+ list_devices();
+
+ while (!done) {
+ char line[80];
+
+ print_menu();
+
+ if (fgets(line, sizeof(line), stdin)==NULL)
+ break;
+
+ switch (line[0]) {
+ case 'l':
+ list_devices();
+ break;
+
+ case 'R':
+ pjmedia_aud_dev_refresh();
+ puts("Audio device list refreshed.");
+ break;
+
+ case 'i':
+ {
+ unsigned dev_index;
+ if (sscanf(line+2, "%u", &dev_index) != 1) {
+ puts("error: device ID required");
+ break;
+ }
+ show_dev_info(dev_index);
+ }
+ break;
+
+ case 't':
+ {
+ pjmedia_dir dir;
+ int rec_id, play_id;
+ unsigned clock_rate, ptime, chnum;
+ int cnt;
+
+ cnt = sscanf(line+2, "%d %d %u %u %u", &rec_id, &play_id,
+ &clock_rate, &ptime, &chnum);
+ if (cnt < 4) {
+ puts("error: not enough parameters");
+ break;
+ }
+ if (clock_rate < 8000 || clock_rate > 128000) {
+ puts("error: invalid clock rate");
+ break;
+ }
+ if (ptime < 10 || ptime > 500) {
+ puts("error: invalid ptime");
+ break;
+ }
+ if (cnt==5) {
+ if (chnum < 1 || chnum > 4) {
+ puts("error: invalid number of channels");
+ break;
+ }
+ } else {
+ chnum = 1;
+ }
+
+ if (rec_id >= 0 && rec_id < (int)dev_count) {
+ if (play_id >= 0 && play_id < (int)dev_count)
+ dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ else
+ dir = PJMEDIA_DIR_CAPTURE;
+ } else if (play_id >= 0 && play_id < (int)dev_count) {
+ dir = PJMEDIA_DIR_PLAYBACK;
+ } else {
+ puts("error: at least one valid device index required");
+ break;
+ }
+
+ test_device(dir, rec_id, play_id, clock_rate, ptime, chnum);
+
+ }
+ break;
+
+ case 'r':
+ /* record */
+ {
+ int index;
+ char filename[80];
+ int count;
+
+ count = sscanf(line+2, "%d %s", &index, filename);
+ if (count==1)
+ record(index, NULL);
+ else if (count==2)
+ record(index, filename);
+ else
+ puts("error: invalid command syntax");
+ }
+ break;
+
+ case 'p':
+ /* playback */
+ {
+ int index;
+ char filename[80];
+ int count;
+
+ count = sscanf(line+2, "%d %s", &index, filename);
+ if (count==1)
+ play_file(index, NULL);
+ else if (count==2)
+ play_file(index, filename);
+ else
+ puts("error: invalid command syntax");
+ }
+ break;
+
+ case 'd':
+ /* latencies */
+ {
+ int rec_lat, play_lat;
+
+ if (sscanf(line+2, "%d %d", &rec_lat, &play_lat) == 2) {
+ capture_lat = (unsigned)
+ (rec_lat>=0? rec_lat:PJMEDIA_SND_DEFAULT_REC_LATENCY);
+ playback_lat = (unsigned)
+ (play_lat >= 0? play_lat : PJMEDIA_SND_DEFAULT_PLAY_LATENCY);
+ printf("Recording latency=%ums, playback latency=%ums",
+ capture_lat, playback_lat);
+ } else {
+ printf("Current latencies: record=%ums, playback=%ums",
+ capture_lat, playback_lat);
+ }
+ puts("");
+ }
+ break;
+
+ case 'v':
+ if (pj_log_get_level() <= 3) {
+ pj_log_set_level(5);
+ puts("Logging set to detail");
+ } else {
+ pj_log_set_level(3);
+ puts("Logging set to quiet");
+ }
+ break;
+
+ case 'q':
+ done = PJ_TRUE;
+ break;
+ }
+ }
+
+ pj_caching_pool_destroy(&cp);
+ pj_shutdown();
+ return 0;
+}
+
+
diff --git a/pjsip-apps/src/samples/aviplay.c b/pjsip-apps/src/samples/aviplay.c
new file mode 100644
index 0000000..2ac904e
--- /dev/null
+++ b/pjsip-apps/src/samples/aviplay.c
@@ -0,0 +1,560 @@
+/* $Id: aviplay.c 4051 2012-04-13 08:16:30Z ming $ */
+/*
+ * Copyright (C) 2010-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjmedia.h>
+#include <pjmedia/converter.h>
+#include <pjmedia-codec.h>
+#include <pjlib-util.h>
+#include <pjlib.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "util.h"
+
+/**
+ * \page page_pjmedia_samples_aviplay_c Samples: Playing AVI File to
+ * Video and Sound Devices
+ *
+ * This is a very simple example to use the @ref PJMEDIA_FILE_PLAY,
+ * @ref PJMED_SND_PORT, and @ref PJMEDIA_VID_PORT. In this example, we
+ * open the file, video, and sound devices, then connect the file to both
+ * video and sound devices to play the contents of the file.
+ *
+ *
+ * This file is pjsip-apps/src/samples/aviplay.c
+ *
+ * \includelineno aviplay.c
+ */
+
+
+/*
+ * aviplay.c
+ *
+ * PURPOSE:
+ * Play a AVI file to video and sound devices.
+ *
+ * USAGE:
+ * aviplay FILE.AVI
+ */
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+/* For logging purpose. */
+#define THIS_FILE "aviplay.c"
+
+static const char *desc =
+" FILE \n"
+" \n"
+" aviplay.c \n"
+" \n"
+" PURPOSE \n"
+" \n"
+" Demonstrate how to play a AVI file. \n"
+" \n"
+" USAGE \n"
+" \n"
+" aviplay FILE.AVI \n";
+
+struct codec_fmt {
+ pj_uint32_t pjmedia_id;
+ const char *codec_id;
+ /* Do we need to convert the decoded frame? */
+ pj_bool_t need_conversion;
+ /* If conversion is needed, dst_fmt indicates the destination format */
+ pjmedia_format_id dst_fmt;
+} codec_fmts[] = {{PJMEDIA_FORMAT_MJPEG, "mjpeg",
+ PJ_TRUE , PJMEDIA_FORMAT_I420},
+ {PJMEDIA_FORMAT_H263 , "h263" ,
+ PJ_FALSE, 0},
+ {PJMEDIA_FORMAT_MPEG4, "mp4v"},
+ {PJMEDIA_FORMAT_H264 , "h264"}
+ };
+
+typedef struct avi_port_t
+{
+ pjmedia_vid_port *vid_port;
+ pjmedia_snd_port *snd_port;
+ pj_bool_t is_running;
+ pj_bool_t is_quitting;
+} avi_port_t;
+
+typedef struct codec_port_data_t
+{
+ pjmedia_vid_codec *codec;
+ pjmedia_port *src_port;
+ pj_uint8_t *enc_buf;
+ pj_size_t enc_buf_size;
+
+ pjmedia_converter *conv;
+} codec_port_data_t;
+
+static pj_status_t avi_event_cb(pjmedia_event *event,
+ void *user_data)
+{
+ avi_port_t *ap = (avi_port_t *)user_data;
+
+ switch (event->type) {
+ case PJMEDIA_EVENT_WND_CLOSED:
+ ap->is_quitting = PJ_TRUE;
+ break;
+ case PJMEDIA_EVENT_MOUSE_BTN_DOWN:
+ if (ap->is_running) {
+ pjmedia_vid_port_stop(ap->vid_port);
+ if (ap->snd_port)
+ pjmedia_aud_stream_stop(
+ pjmedia_snd_port_get_snd_stream(ap->snd_port));
+ } else {
+ pjmedia_vid_port_start(ap->vid_port);
+ if (ap->snd_port)
+ pjmedia_aud_stream_start(
+ pjmedia_snd_port_get_snd_stream(ap->snd_port));
+ }
+ ap->is_running = !ap->is_running;
+ break;
+ default:
+ return PJ_SUCCESS;
+ }
+
+ /* We handled the event on our own, so return non-PJ_SUCCESS here */
+ return -1;
+}
+
+static pj_status_t codec_get_frame(pjmedia_port *port,
+ pjmedia_frame *frame)
+{
+ codec_port_data_t *port_data = (codec_port_data_t*)port->port_data.pdata;
+ pjmedia_vid_codec *codec = port_data->codec;
+ pjmedia_frame enc_frame;
+ pj_status_t status;
+
+ enc_frame.buf = port_data->enc_buf;
+ enc_frame.size = port_data->enc_buf_size;
+
+ if (port_data->conv) {
+ pj_size_t frame_size = frame->size;
+
+ status = pjmedia_port_get_frame(port_data->src_port, frame);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ status = pjmedia_vid_codec_decode(codec, 1, frame,
+ frame->size, &enc_frame);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ frame->size = frame_size;
+ status = pjmedia_converter_convert(port_data->conv, &enc_frame, frame);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ return PJ_SUCCESS;
+ }
+
+ status = pjmedia_port_get_frame(port_data->src_port, &enc_frame);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ status = pjmedia_vid_codec_decode(codec, 1, &enc_frame,
+ frame->size, frame);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ return PJ_SUCCESS;
+
+on_error:
+ pj_perror(3, THIS_FILE, status, "codec_get_frame() error");
+ return status;
+}
+
+static int aviplay(pj_pool_t *pool, const char *fname)
+{
+ pjmedia_vid_port *renderer=NULL;
+ pjmedia_vid_port_param param;
+ const pjmedia_video_format_info *vfi;
+ pjmedia_video_format_detail *vfd;
+ pjmedia_snd_port *snd_port = NULL;
+ pj_status_t status;
+ int rc = 0;
+ pjmedia_avi_streams *avi_streams;
+ pjmedia_avi_stream *vid_stream, *aud_stream;
+ pjmedia_port *vid_port = NULL, *aud_port = NULL;
+ pjmedia_vid_codec *codec=NULL;
+ avi_port_t avi_port;
+
+ pj_bzero(&avi_port, sizeof(avi_port));
+
+ status = pjmedia_avi_player_create_streams(pool, fname, 0, &avi_streams);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(2,("", status, " Error playing %s", fname));
+ rc = 210; goto on_return;
+ }
+
+ vid_stream = pjmedia_avi_streams_get_stream_by_media(avi_streams,
+ 0,
+ PJMEDIA_TYPE_VIDEO);
+ vid_port = pjmedia_avi_stream_get_port(vid_stream);
+
+ if (vid_port) {
+ pjmedia_vid_port_param_default(&param);
+
+ status = pjmedia_vid_dev_default_param(pool,
+ PJMEDIA_VID_DEFAULT_RENDER_DEV,
+ &param.vidparam);
+ if (status != PJ_SUCCESS) {
+ rc = 220; goto on_return;
+ }
+
+ /* Create renderer, set it to active */
+ param.active = PJ_TRUE;
+ param.vidparam.dir = PJMEDIA_DIR_RENDER;
+ vfd = pjmedia_format_get_video_format_detail(&vid_port->info.fmt,
+ PJ_TRUE);
+ pjmedia_format_init_video(&param.vidparam.fmt,
+ vid_port->info.fmt.id,
+ vfd->size.w, vfd->size.h,
+ vfd->fps.num, vfd->fps.denum);
+
+ vfi = pjmedia_get_video_format_info(
+ pjmedia_video_format_mgr_instance(),
+ vid_port->info.fmt.id);
+ /* Check whether the frame is encoded */
+ if (!vfi || vfi->bpp == 0) {
+ /* Yes, prepare codec */
+ pj_str_t codec_id_st;
+ unsigned info_cnt = 1, i, k;
+ const pjmedia_vid_codec_info *codec_info;
+ pj_str_t port_name = {"codec", 5};
+ pj_uint8_t *enc_buf = NULL;
+ pj_size_t enc_buf_size = 0;
+ pjmedia_vid_dev_info rdr_info;
+ pjmedia_port codec_port;
+ codec_port_data_t codec_port_data;
+ pjmedia_vid_codec_param codec_param;
+ struct codec_fmt *codecp = NULL;
+
+ /* Lookup codec */
+ for (i = 0; i < sizeof(codec_fmts)/sizeof(codec_fmts[0]); i++) {
+ if (vid_port->info.fmt.id == codec_fmts[i].pjmedia_id) {
+ codecp = &codec_fmts[i];
+ break;
+ }
+ }
+ if (!codecp) {
+ rc = 242; goto on_return;
+ }
+ pj_cstr(&codec_id_st, codecp->codec_id);
+ status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL,
+ &codec_id_st,
+ &info_cnt,
+ &codec_info,
+ NULL);
+ if (status != PJ_SUCCESS) {
+ rc = 245; goto on_return;
+ }
+ status = pjmedia_vid_codec_mgr_get_default_param(NULL, codec_info,
+ &codec_param);
+ if (status != PJ_SUCCESS) {
+ rc = 246; goto on_return;
+ }
+
+ pjmedia_format_copy(&codec_param.enc_fmt, &param.vidparam.fmt);
+
+ pjmedia_vid_dev_get_info(param.vidparam.rend_id, &rdr_info);
+ for (i=0; i<codec_info->dec_fmt_id_cnt; ++i) {
+ for (k=0; k<rdr_info.fmt_cnt; ++k) {
+ if (codec_info->dec_fmt_id[i]==(int)rdr_info.fmt[k].id)
+ {
+ param.vidparam.fmt.id = codec_info->dec_fmt_id[i];
+ i = codec_info->dec_fmt_id_cnt;
+ break;
+ }
+ }
+ }
+
+ /* Open codec */
+ status = pjmedia_vid_codec_mgr_alloc_codec(NULL, codec_info,
+ &codec);
+ if (status != PJ_SUCCESS) {
+ rc = 250; goto on_return;
+ }
+
+ status = pjmedia_vid_codec_init(codec, pool);
+ if (status != PJ_SUCCESS) {
+ rc = 251; goto on_return;
+ }
+
+ pjmedia_format_copy(&codec_param.dec_fmt, &param.vidparam.fmt);
+ codec_param.dir = PJMEDIA_DIR_DECODING;
+ codec_param.packing = PJMEDIA_VID_PACKING_WHOLE;
+ status = pjmedia_vid_codec_open(codec, &codec_param);
+ if (status != PJ_SUCCESS) {
+ rc = 252; goto on_return;
+ }
+
+ /* Alloc encoding buffer */
+ enc_buf_size = codec_param.dec_fmt.det.vid.size.w *
+ codec_param.dec_fmt.det.vid.size.h * 4
+ + 16; /*< padding, just in case */
+ enc_buf = pj_pool_alloc(pool,enc_buf_size);
+
+ /* Init codec port */
+ pj_bzero(&codec_port, sizeof(codec_port));
+ status = pjmedia_port_info_init2(&codec_port.info, &port_name,
+ 0x1234,
+ PJMEDIA_DIR_ENCODING,
+ &codec_param.dec_fmt);
+ if (status != PJ_SUCCESS) {
+ rc = 260; goto on_return;
+ }
+ pj_bzero(&codec_port_data, sizeof(codec_port_data));
+ codec_port_data.codec = codec;
+ codec_port_data.src_port = vid_port;
+ codec_port_data.enc_buf = enc_buf;
+ codec_port_data.enc_buf_size = enc_buf_size;
+
+ codec_port.get_frame = &codec_get_frame;
+ codec_port.port_data.pdata = &codec_port_data;
+
+ /* Check whether we need to convert the decoded frame */
+ if (codecp->need_conversion) {
+ pjmedia_conversion_param conv_param;
+
+ pjmedia_format_copy(&conv_param.src, &param.vidparam.fmt);
+ pjmedia_format_copy(&conv_param.dst, &param.vidparam.fmt);
+ conv_param.dst.id = codecp->dst_fmt;
+ param.vidparam.fmt.id = conv_param.dst.id;
+
+ status = pjmedia_converter_create(NULL, pool, &conv_param,
+ &codec_port_data.conv);
+ if (status != PJ_SUCCESS) {
+ rc = 270; goto on_return;
+ }
+ }
+
+ status = pjmedia_vid_port_create(pool, &param, &renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 230; goto on_return;
+ }
+
+ status = pjmedia_vid_port_connect(renderer, &codec_port,
+ PJ_FALSE);
+ } else {
+ status = pjmedia_vid_port_create(pool, &param, &renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 230; goto on_return;
+ }
+
+ /* Connect avi port to renderer */
+ status = pjmedia_vid_port_connect(renderer, vid_port,
+ PJ_FALSE);
+ }
+
+ if (status != PJ_SUCCESS) {
+ rc = 240; goto on_return;
+ }
+ }
+
+ aud_stream = pjmedia_avi_streams_get_stream_by_media(avi_streams,
+ 0,
+ PJMEDIA_TYPE_AUDIO);
+ aud_port = pjmedia_avi_stream_get_port(aud_stream);
+
+ if (aud_port) {
+ /* Create sound player port. */
+ status = pjmedia_snd_port_create_player(
+ pool, /* pool */
+ -1, /* use default dev. */
+ PJMEDIA_PIA_SRATE(&aud_port->info),/* clock rate. */
+ PJMEDIA_PIA_CCNT(&aud_port->info), /* # of channels. */
+ PJMEDIA_PIA_SPF(&aud_port->info), /* samples per frame. */
+ PJMEDIA_PIA_BITS(&aud_port->info), /* bits per sample. */
+ 0, /* options */
+ &snd_port /* returned port */
+ );
+ if (status != PJ_SUCCESS) {
+ rc = 310; goto on_return;
+ }
+
+ /* Connect file port to the sound player.
+ * Stream playing will commence immediately.
+ */
+ status = pjmedia_snd_port_connect(snd_port, aud_port);
+ if (status != PJ_SUCCESS) {
+ rc = 330; goto on_return;
+ }
+ }
+
+ if (vid_port) {
+ pjmedia_vid_dev_cb cb;
+
+ pj_bzero(&cb, sizeof(cb));
+ avi_port.snd_port = snd_port;
+ avi_port.vid_port = renderer;
+ avi_port.is_running = PJ_TRUE;
+ pjmedia_vid_port_set_cb(renderer, &cb, &avi_port);
+
+ /* subscribe events */
+ pjmedia_event_subscribe(NULL, &avi_event_cb, &avi_port,
+ renderer);
+
+ if (snd_port) {
+ /* Synchronize video rendering and audio playback */
+ pjmedia_vid_port_set_clock_src(
+ renderer,
+ pjmedia_snd_port_get_clock_src(
+ snd_port, PJMEDIA_DIR_PLAYBACK));
+ }
+
+
+ /* Start video streaming.. */
+ status = pjmedia_vid_port_start(renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 270; goto on_return;
+ }
+ }
+
+ while (!avi_port.is_quitting) {
+ pj_thread_sleep(100);
+ }
+
+on_return:
+ if (snd_port) {
+ pjmedia_snd_port_disconnect(snd_port);
+ /* Without this sleep, Windows/DirectSound will repeteadly
+ * play the last frame during destroy.
+ */
+ pj_thread_sleep(100);
+ pjmedia_snd_port_destroy(snd_port);
+ }
+ if (renderer) {
+ pjmedia_event_unsubscribe(NULL, &avi_event_cb, &avi_port,
+ renderer);
+ pjmedia_vid_port_destroy(renderer);
+ }
+ if (aud_port)
+ pjmedia_port_destroy(aud_port);
+ if (vid_port)
+ pjmedia_port_destroy(vid_port);
+ if (codec) {
+ pjmedia_vid_codec_close(codec);
+ pjmedia_vid_codec_mgr_dealloc_codec(NULL, codec);
+ }
+
+ return rc;
+}
+
+
+static int main_func(int argc, char *argv[])
+{
+ pj_caching_pool cp;
+ pj_pool_t *pool;
+ int rc = 0;
+ pj_status_t status = PJ_SUCCESS;
+
+ if (argc != 2) {
+ puts("Error: filename required");
+ puts(desc);
+ return 1;
+ }
+
+
+ /* Must init PJLIB first: */
+ status = pj_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 memory pool for our file player */
+ pool = pj_pool_create( &cp.factory, /* pool factory */
+ "AVI", /* pool name. */
+ 4000, /* init size */
+ 4000, /* increment size */
+ NULL /* callback on error */
+ );
+
+ pjmedia_video_format_mgr_create(pool, 64, 0, NULL);
+ pjmedia_converter_mgr_create(pool, NULL);
+ pjmedia_event_mgr_create(pool, 0, NULL);
+ pjmedia_vid_codec_mgr_create(pool, NULL);
+
+ status = pjmedia_vid_dev_subsys_init(&cp.factory);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ status = pjmedia_aud_subsys_init(&cp.factory);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+#if PJMEDIA_HAS_FFMPEG_VID_CODEC
+ status = pjmedia_codec_ffmpeg_vid_init(NULL, &cp.factory);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+#endif
+
+ rc = aviplay(pool, argv[1]);
+
+ /*
+ * File should be playing and looping now
+ */
+
+ /* Without this sleep, Windows/DirectSound will repeteadly
+ * play the last frame during destroy.
+ */
+ pj_thread_sleep(100);
+
+on_return:
+#if PJMEDIA_HAS_FFMPEG_VID_CODEC
+ pjmedia_codec_ffmpeg_vid_deinit();
+#endif
+ pjmedia_aud_subsys_shutdown();
+ pjmedia_vid_dev_subsys_shutdown();
+
+ pjmedia_video_format_mgr_destroy(pjmedia_video_format_mgr_instance());
+ pjmedia_converter_mgr_destroy(pjmedia_converter_mgr_instance());
+ pjmedia_event_mgr_destroy(pjmedia_event_mgr_instance());
+ pjmedia_vid_codec_mgr_destroy(pjmedia_vid_codec_mgr_instance());
+
+ /* Release application pool */
+ pj_pool_release( pool );
+
+ /* Destroy pool factory */
+ pj_caching_pool_destroy( &cp );
+
+ /* Shutdown PJLIB */
+ pj_shutdown();
+
+ /* Done. */
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ return pj_run_app(&main_func, argc, argv, 0);
+}
+
+#else
+
+int main(int argc, char *argv[])
+{
+ PJ_UNUSED_ARG(argc);
+ PJ_UNUSED_ARG(argv);
+ puts("Error: this sample requires video capability (PJMEDIA_HAS_VIDEO == 1)");
+ return -1;
+}
+
+#endif /* PJMEDIA_HAS_VIDEO */
diff --git a/pjsip-apps/src/samples/confbench.c b/pjsip-apps/src/samples/confbench.c
new file mode 100644
index 0000000..4fe155d
--- /dev/null
+++ b/pjsip-apps/src/samples/confbench.c
@@ -0,0 +1,347 @@
+/* $Id: confbench.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+
+/**
+ * \page page_pjmedia_samples_confbench_c Samples: Benchmarking Conference Bridge
+ *
+ * Benchmarking pjmedia (conference bridge+resample). For my use only,
+ * and it only works in Win32.
+ *
+ * This file is pjsip-apps/src/samples/confbench.c
+ *
+ * \includelineno confbench.c
+ */
+
+
+#include <pjmedia.h>
+#include <pjlib-util.h> /* pj_getopt */
+#include <pjlib.h>
+#include <stdlib.h> /* atoi() */
+#include <stdio.h>
+#include <windows.h>
+
+/* For logging purpose. */
+#define THIS_FILE "confsample.c"
+
+
+/* Configurable:
+ * LARGE_SET will create in total of about 232 ports.
+ * HAS_RESAMPLE will activate resampling on about half
+ * the port.
+ */
+#define TEST_SET LARGE_SET
+#define HAS_RESAMPLE 0
+
+
+#define SMALL_SET 16
+#define LARGE_SET 100
+
+
+#define PORT_COUNT 254
+#define CLOCK_RATE 16000
+#define SAMPLES_PER_FRAME (CLOCK_RATE/100)
+#if HAS_RESAMPLE
+# define SINE_CLOCK 32000
+#else
+# define SINE_CLOCK CLOCK_RATE
+#endif
+#define SINE_PTIME 20
+#define DURATION 10
+
+#define SINE_COUNT TEST_SET
+#define NULL_COUNT TEST_SET
+#define IDLE_COUNT 32
+
+
+static void app_perror(const char *sender, const char *title, pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(1,(sender, "%s: %s", title, errmsg));
+}
+
+
+struct Times
+{
+ FILETIME kernel_time;
+ ULARGE_INTEGER u_kernel_time;
+ FILETIME user_time;
+ ULARGE_INTEGER u_user_time;
+ ULARGE_INTEGER u_total;
+};
+
+static void process(struct Times *t)
+{
+ pj_memcpy(&t->u_kernel_time, &t->kernel_time, sizeof(FILETIME));
+ pj_memcpy(&t->u_user_time, &t->user_time, sizeof(FILETIME));
+ t->u_total.QuadPart = t->u_kernel_time.QuadPart + t->u_user_time.QuadPart;
+}
+
+static void benchmark(void)
+{
+ FILETIME creation_time, exit_time;
+ struct Times start, end;
+ DWORD ts, te;
+ LARGE_INTEGER elapsed;
+ BOOL rc;
+ int i;
+ double pct;
+
+ puts("Test started!"); fflush(stdout);
+
+ ts = GetTickCount();
+ rc = GetProcessTimes(GetCurrentProcess(), &creation_time, &exit_time,
+ &start.kernel_time, &start.user_time);
+ for (i=DURATION; i>0; --i) {
+ printf("\r%d ", i); fflush(stdout);
+ pj_thread_sleep(1000);
+ }
+ rc = GetProcessTimes(GetCurrentProcess(), &creation_time, &exit_time,
+ &end.kernel_time, &end.user_time);
+ te = GetTickCount();
+
+ process(&start);
+ process(&end);
+
+ elapsed.QuadPart = end.u_total.QuadPart - start.u_total.QuadPart;
+
+ pct = elapsed.QuadPart * 100.0 / ((te-ts)*10000.0);
+
+ printf("CPU usage=%6.4f%%\n", pct); fflush(stdout);
+}
+
+
+
+/* Struct attached to sine generator */
+typedef struct
+{
+ pj_int16_t *samples; /* Sine samples. */
+} port_data;
+
+
+/* This callback is called to feed more samples */
+static pj_status_t sine_get_frame( pjmedia_port *port,
+ pjmedia_frame *frame)
+{
+ port_data *sine = port->port_data.pdata;
+ pj_int16_t *samples = frame->buf;
+ unsigned i, count, left, right;
+
+ /* Get number of samples */
+ count = frame->size / 2 / PJMEDIA_PIA_CCNT(&port->info);
+
+ left = 0;
+ right = 0;
+
+ for (i=0; i<count; ++i) {
+ *samples++ = sine->samples[left];
+ ++left;
+
+ if (PJMEDIA_PIA_CCNT(&port->info) == 2) {
+ *samples++ = sine->samples[right];
+ right += 2; /* higher pitch so we can distinguish left and right. */
+ if (right >= count)
+ right = 0;
+ }
+ }
+
+ /* Must set frame->type correctly, otherwise the sound device
+ * will refuse to play.
+ */
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+ return PJ_SUCCESS;
+}
+
+#ifndef M_PI
+#define M_PI (3.14159265)
+#endif
+
+/*
+ * Create a media port to generate sine wave samples.
+ */
+static pj_status_t create_sine_port(pj_pool_t *pool,
+ unsigned sampling_rate,
+ unsigned channel_count,
+ pjmedia_port **p_port)
+{
+ pjmedia_port *port;
+ unsigned i;
+ unsigned count;
+ pj_str_t port_name;
+ port_data *sine;
+
+ PJ_ASSERT_RETURN(pool && channel_count > 0 && channel_count <= 2,
+ PJ_EINVAL);
+
+ port = pj_pool_zalloc(pool, sizeof(pjmedia_port));
+ PJ_ASSERT_RETURN(port != NULL, PJ_ENOMEM);
+
+ /* Fill in port info. */
+ port_name = pj_str("sine generator");
+ pjmedia_port_info_init(&port->info, &port_name,
+ 12345, sampling_rate, channel_count, 16,
+ sampling_rate * SINE_PTIME / 1000 * channel_count);
+
+ /* Set the function to feed frame */
+ port->get_frame = &sine_get_frame;
+
+ /* Create sine port data */
+ port->port_data.pdata = sine = pj_pool_zalloc(pool, sizeof(port_data));
+
+ /* Create samples */
+ count = PJMEDIA_PIA_SPF(&port->info) / channel_count;
+ sine->samples = pj_pool_alloc(pool, count * sizeof(pj_int16_t));
+ PJ_ASSERT_RETURN(sine->samples != NULL, PJ_ENOMEM);
+
+ /* initialise sinusoidal wavetable */
+ for( i=0; i<count; i++ )
+ {
+ sine->samples[i] = (pj_int16_t) (10000.0 *
+ sin(((double)i/(double)count) * M_PI * 8.) );
+ }
+
+ *p_port = port;
+
+ return PJ_SUCCESS;
+}
+
+int main()
+{
+ pj_caching_pool cp;
+ pjmedia_endpt *med_endpt;
+ pj_pool_t *pool;
+ pjmedia_conf *conf;
+ int i;
+ pjmedia_port *sine_port[SINE_COUNT], *null_port, *conf_port;
+ pjmedia_port *nulls[NULL_COUNT];
+ unsigned null_slots[NULL_COUNT];
+ pjmedia_master_port *master_port;
+ pj_status_t status;
+
+
+ pj_log_set_level(3);
+
+ status = pj_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+ pool = pj_pool_create( &cp.factory, /* pool factory */
+ "wav", /* pool name. */
+ 4000, /* init size */
+ 4000, /* increment size */
+ NULL /* callback on error */
+ );
+
+ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+
+ status = pjmedia_conf_create( pool,
+ PORT_COUNT,
+ CLOCK_RATE,
+ 1, SAMPLES_PER_FRAME, 16,
+ PJMEDIA_CONF_NO_DEVICE,
+ &conf);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create conference bridge", status);
+ return 1;
+ }
+
+ printf("Resampling is %s\n", (HAS_RESAMPLE?"active":"disabled"));
+
+ /* Create Null ports */
+ printf("Creating %d null ports..\n", NULL_COUNT);
+ for (i=0; i<NULL_COUNT; ++i) {
+ status = pjmedia_null_port_create(pool, CLOCK_RATE, 1, SAMPLES_PER_FRAME*2, 16, &nulls[i]);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ status = pjmedia_conf_add_port(conf, pool, nulls[i], NULL, &null_slots[i]);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ }
+
+ /* Create sine ports. */
+ printf("Creating %d sine generator ports..\n", SINE_COUNT);
+ for (i=0; i<SINE_COUNT; ++i) {
+ unsigned j, slot;
+
+ /* Load the WAV file to file port. */
+ status = create_sine_port(pool, SINE_CLOCK, 1, &sine_port[i]);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Add the file port to conference bridge */
+ status = pjmedia_conf_add_port( conf, /* The bridge */
+ pool, /* pool */
+ sine_port[i], /* port to connect */
+ NULL, /* Use port's name */
+ &slot /* ptr for slot # */
+ );
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to add conference port", status);
+ return 1;
+ }
+
+ status = pjmedia_conf_connect_port(conf, slot, 0, 0);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ for (j=0; j<NULL_COUNT; ++j) {
+ status = pjmedia_conf_connect_port(conf, slot, null_slots[j], 0);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ }
+ }
+
+ /* Create idle ports */
+ printf("Creating %d idle ports..\n", IDLE_COUNT);
+ for (i=0; i<IDLE_COUNT; ++i) {
+ pjmedia_port *dummy;
+ status = pjmedia_null_port_create(pool, CLOCK_RATE, 1, SAMPLES_PER_FRAME, 16, &dummy);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ status = pjmedia_conf_add_port(conf, pool, dummy, NULL, NULL);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ }
+
+ /* Create null port */
+ status = pjmedia_null_port_create(pool, CLOCK_RATE, 1, SAMPLES_PER_FRAME, 16,
+ &null_port);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ conf_port = pjmedia_conf_get_master_port(conf);
+
+ /* Create master port */
+ status = pjmedia_master_port_create(pool, null_port, conf_port, 0, &master_port);
+
+
+ pjmedia_master_port_start(master_port);
+
+ puts("Waiting to settle.."); fflush(stdout);
+ pj_thread_sleep(5000);
+
+
+ benchmark();
+
+
+ /* Done. */
+ return 0;
+}
+
+
diff --git a/pjsip-apps/src/samples/confsample.c b/pjsip-apps/src/samples/confsample.c
new file mode 100644
index 0000000..b8eb93f
--- /dev/null
+++ b/pjsip-apps/src/samples/confsample.c
@@ -0,0 +1,609 @@
+/* $Id: confsample.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+#include <pjmedia.h>
+#include <pjlib-util.h> /* pj_getopt */
+#include <pjlib.h>
+
+#include <stdlib.h> /* atoi() */
+#include <stdio.h>
+
+#include "util.h"
+
+/**
+ * \page page_pjmedia_samples_confsample_c Samples: Using Conference Bridge
+ *
+ * Sample to mix multiple files in the conference bridge and play the
+ * result to sound device.
+ *
+ * This file is pjsip-apps/src/samples/confsample.c
+ *
+ * \includelineno confsample.c
+ */
+
+
+/* For logging purpose. */
+#define THIS_FILE "confsample.c"
+
+
+/* Shall we put recorder in the conference */
+#define RECORDER 1
+
+
+static const char *desc =
+ " FILE: \n"
+ " \n"
+ " confsample.c \n"
+ " \n"
+ " PURPOSE: \n"
+ " \n"
+ " Demonstrate how to use conference bridge. \n"
+ " \n"
+ " USAGE: \n"
+ " \n"
+ " confsample [options] [file1.wav] [file2.wav] ... \n"
+ " \n"
+ " options: \n"
+ SND_USAGE
+ " \n"
+ " fileN.wav are optional WAV files to be connected to the conference \n"
+ " bridge. The WAV files MUST have single channel (mono) and 16 bit PCM \n"
+ " samples. It can have arbitrary sampling rate. \n"
+ " \n"
+ " DESCRIPTION: \n"
+ " \n"
+ " Here we create a conference bridge, with at least one port (port zero \n"
+ " is always created for the sound device). \n"
+ " \n"
+ " If WAV files are specified, the WAV file player ports will be connected \n"
+ " to slot starting from number one in the bridge. The WAV files can have \n"
+ " arbitrary sampling rate; the bridge will convert it to its clock rate. \n"
+ " However, the files MUST have a single audio channel only (i.e. mono). \n";
+
+
+
+/*
+ * Prototypes:
+ */
+
+/* List the ports in the conference bridge */
+static void conf_list(pjmedia_conf *conf, pj_bool_t detail);
+
+/* Display VU meter */
+static void monitor_level(pjmedia_conf *conf, int slot, int dir, int dur);
+
+
+/* Show usage */
+static void usage(void)
+{
+ puts("");
+ puts(desc);
+}
+
+
+
+/* Input simple string */
+static pj_bool_t input(const char *title, char *buf, pj_size_t len)
+{
+ char *p;
+
+ printf("%s (empty to cancel): ", title); fflush(stdout);
+ if (fgets(buf, len, stdin) == NULL)
+ return PJ_FALSE;
+
+ /* Remove trailing newlines. */
+ for (p=buf; ; ++p) {
+ if (*p=='\r' || *p=='\n') *p='\0';
+ else if (!*p) break;
+ }
+
+ if (!*buf)
+ return PJ_FALSE;
+
+ return PJ_TRUE;
+}
+
+
+/*****************************************************************************
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+ int dev_id = -1;
+ int clock_rate = CLOCK_RATE;
+ int channel_count = NCHANNELS;
+ int samples_per_frame = NSAMPLES;
+ int bits_per_sample = NBITS;
+
+ pj_caching_pool cp;
+ pjmedia_endpt *med_endpt;
+ pj_pool_t *pool;
+ pjmedia_conf *conf;
+
+ int i, port_count, file_count;
+ pjmedia_port **file_port; /* Array of file ports */
+ pjmedia_port *rec_port = NULL; /* Wav writer port */
+
+ char tmp[10];
+ pj_status_t status;
+
+
+ /* Must init PJLIB first: */
+ status = pj_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Get command line options. */
+ if (get_snd_options(THIS_FILE, argc, argv, &dev_id, &clock_rate,
+ &channel_count, &samples_per_frame, &bits_per_sample))
+ {
+ usage();
+ return 1;
+ }
+
+ /* Must create a pool factory before we can allocate any memory. */
+ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+
+ /*
+ * Initialize media endpoint.
+ * This will implicitly initialize PJMEDIA too.
+ */
+ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Create memory pool to allocate memory */
+ pool = pj_pool_create( &cp.factory, /* pool factory */
+ "wav", /* pool name. */
+ 4000, /* init size */
+ 4000, /* increment size */
+ NULL /* callback on error */
+ );
+
+
+ file_count = argc - pj_optind;
+ port_count = file_count + 1 + RECORDER;
+
+ /* Create the conference bridge.
+ * With default options (zero), the bridge will create an instance of
+ * sound capture and playback device and connect them to slot zero.
+ */
+ status = pjmedia_conf_create( pool, /* pool to use */
+ port_count,/* number of ports */
+ clock_rate,
+ channel_count,
+ samples_per_frame,
+ bits_per_sample,
+ 0, /* options */
+ &conf /* result */
+ );
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create conference bridge", status);
+ return 1;
+ }
+
+#if RECORDER
+ status = pjmedia_wav_writer_port_create( pool, "confrecord.wav",
+ clock_rate, channel_count,
+ samples_per_frame,
+ bits_per_sample, 0, 0,
+ &rec_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create WAV writer", status);
+ return 1;
+ }
+
+ pjmedia_conf_add_port(conf, pool, rec_port, NULL, NULL);
+#endif
+
+
+ /* Create file ports. */
+ file_port = pj_pool_alloc(pool, file_count * sizeof(pjmedia_port*));
+
+ for (i=0; i<file_count; ++i) {
+
+ /* Load the WAV file to file port. */
+ status = pjmedia_wav_player_port_create(
+ pool, /* pool. */
+ argv[i+pj_optind], /* filename */
+ 0, /* use default ptime */
+ 0, /* flags */
+ 0, /* buf size */
+ &file_port[i] /* result */
+ );
+ if (status != PJ_SUCCESS) {
+ char title[80];
+ pj_ansi_sprintf(title, "Unable to use %s", argv[i+pj_optind]);
+ app_perror(THIS_FILE, title, status);
+ usage();
+ return 1;
+ }
+
+ /* Add the file port to conference bridge */
+ status = pjmedia_conf_add_port( conf, /* The bridge */
+ pool, /* pool */
+ file_port[i], /* port to connect */
+ NULL, /* Use port's name */
+ NULL /* ptr for slot # */
+ );
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to add conference port", status);
+ return 1;
+ }
+ }
+
+
+ /*
+ * All ports are set up in the conference bridge.
+ * But at this point, no media will be flowing since no ports are
+ * "connected". User must connect the port manually.
+ */
+
+
+ /* Dump memory usage */
+ dump_pool_usage(THIS_FILE, &cp);
+
+ /* Sleep to allow log messages to flush */
+ pj_thread_sleep(100);
+
+
+ /*
+ * UI Menu:
+ */
+ for (;;) {
+ char tmp1[10];
+ char tmp2[10];
+ char *err;
+ int src, dst, level, dur;
+
+ puts("");
+ conf_list(conf, 0);
+ puts("");
+ puts("Menu:");
+ puts(" s Show ports details");
+ puts(" c Connect one port to another");
+ puts(" d Disconnect port connection");
+ puts(" t Adjust signal level transmitted (tx) to a port");
+ puts(" r Adjust signal level received (rx) from a port");
+ puts(" v Display VU meter for a particular port");
+ puts(" q Quit");
+ puts("");
+
+ printf("Enter selection: "); fflush(stdout);
+
+ if (fgets(tmp, sizeof(tmp), stdin) == NULL)
+ break;
+
+ switch (tmp[0]) {
+ case 's':
+ puts("");
+ conf_list(conf, 1);
+ break;
+
+ case 'c':
+ puts("");
+ puts("Connect source port to destination port");
+ if (!input("Enter source port number", tmp1, sizeof(tmp1)) )
+ continue;
+ src = strtol(tmp1, &err, 10);
+ if (*err || src < 0 || src >= port_count) {
+ puts("Invalid slot number");
+ continue;
+ }
+
+ if (!input("Enter destination port number", tmp2, sizeof(tmp2)) )
+ continue;
+ dst = strtol(tmp2, &err, 10);
+ if (*err || dst < 0 || dst >= port_count) {
+ puts("Invalid slot number");
+ continue;
+ }
+
+ status = pjmedia_conf_connect_port(conf, src, dst, 0);
+ if (status != PJ_SUCCESS)
+ app_perror(THIS_FILE, "Error connecting port", status);
+
+ break;
+
+ case 'd':
+ puts("");
+ puts("Disconnect port connection");
+ if (!input("Enter source port number", tmp1, sizeof(tmp1)) )
+ continue;
+ src = strtol(tmp1, &err, 10);
+ if (*err || src < 0 || src >= port_count) {
+ puts("Invalid slot number");
+ continue;
+ }
+
+ if (!input("Enter destination port number", tmp2, sizeof(tmp2)) )
+ continue;
+ dst = strtol(tmp2, &err, 10);
+ if (*err || dst < 0 || dst >= port_count) {
+ puts("Invalid slot number");
+ continue;
+ }
+
+ status = pjmedia_conf_disconnect_port(conf, src, dst);
+ if (status != PJ_SUCCESS)
+ app_perror(THIS_FILE, "Error connecting port", status);
+
+
+ break;
+
+ case 't':
+ puts("");
+ puts("Adjust transmit level of a port");
+ if (!input("Enter port number", tmp1, sizeof(tmp1)) )
+ continue;
+ src = strtol(tmp1, &err, 10);
+ if (*err || src < 0 || src >= port_count) {
+ puts("Invalid slot number");
+ continue;
+ }
+
+ if (!input("Enter level (-128 to >127, 0 for normal)",
+ tmp2, sizeof(tmp2)) )
+ continue;
+ level = strtol(tmp2, &err, 10);
+ if (*err || level < -128) {
+ puts("Invalid level");
+ continue;
+ }
+
+ status = pjmedia_conf_adjust_tx_level( conf, src, level);
+ if (status != PJ_SUCCESS)
+ app_perror(THIS_FILE, "Error adjusting level", status);
+ break;
+
+
+ case 'r':
+ puts("");
+ puts("Adjust receive level of a port");
+ if (!input("Enter port number", tmp1, sizeof(tmp1)) )
+ continue;
+ src = strtol(tmp1, &err, 10);
+ if (*err || src < 0 || src >= port_count) {
+ puts("Invalid slot number");
+ continue;
+ }
+
+ if (!input("Enter level (-128 to >127, 0 for normal)",
+ tmp2, sizeof(tmp2)) )
+ continue;
+ level = strtol(tmp2, &err, 10);
+ if (*err || level < -128) {
+ puts("Invalid level");
+ continue;
+ }
+
+ status = pjmedia_conf_adjust_rx_level( conf, src, level);
+ if (status != PJ_SUCCESS)
+ app_perror(THIS_FILE, "Error adjusting level", status);
+ break;
+
+ case 'v':
+ puts("");
+ puts("Display VU meter");
+ if (!input("Enter port number to monitor", tmp1, sizeof(tmp1)) )
+ continue;
+ src = strtol(tmp1, &err, 10);
+ if (*err || src < 0 || src >= port_count) {
+ puts("Invalid slot number");
+ continue;
+ }
+
+ if (!input("Enter r for rx level or t for tx level", tmp2, sizeof(tmp2)))
+ continue;
+ if (tmp2[0] != 'r' && tmp2[0] != 't') {
+ puts("Invalid option");
+ continue;
+ }
+
+ if (!input("Duration to monitor (in seconds)", tmp1, sizeof(tmp1)) )
+ continue;
+ dur = strtol(tmp1, &err, 10);
+ if (*err) {
+ puts("Invalid duration number");
+ continue;
+ }
+
+ monitor_level(conf, src, tmp2[0], dur);
+ break;
+
+ case 'q':
+ goto on_quit;
+
+ default:
+ printf("Invalid input character '%c'\n", tmp[0]);
+ break;
+ }
+ }
+
+on_quit:
+
+ /* Start deinitialization: */
+
+ /* Destroy conference bridge */
+ status = pjmedia_conf_destroy( conf );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Destroy file ports */
+ for (i=0; i<file_count; ++i) {
+ status = pjmedia_port_destroy( file_port[i]);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ }
+
+ /* Destroy recorder port */
+ if (rec_port)
+ pjmedia_port_destroy(rec_port);
+
+ /* Release application pool */
+ pj_pool_release( pool );
+
+ /* Destroy media endpoint. */
+ pjmedia_endpt_destroy( med_endpt );
+
+ /* Destroy pool factory */
+ pj_caching_pool_destroy( &cp );
+
+ /* Shutdown PJLIB */
+ pj_shutdown();
+
+ /* Done. */
+ return 0;
+}
+
+
+/*
+ * List the ports in conference bridge
+ */
+static void conf_list(pjmedia_conf *conf, int detail)
+{
+ enum { MAX_PORTS = 32 };
+ unsigned i, count;
+ pjmedia_conf_port_info info[MAX_PORTS];
+
+ printf("Conference ports:\n");
+
+ count = PJ_ARRAY_SIZE(info);
+ pjmedia_conf_get_ports_info(conf, &count, info);
+
+ for (i=0; i<count; ++i) {
+ char txlist[4*MAX_PORTS];
+ unsigned j;
+ pjmedia_conf_port_info *port_info = &info[i];
+
+ txlist[0] = '\0';
+ for (j=0; j<port_info->listener_cnt; ++j) {
+ char s[10];
+ pj_ansi_sprintf(s, "#%d ", port_info->listener_slots[j]);
+ pj_ansi_strcat(txlist, s);
+
+ }
+
+ if (txlist[0] == '\0') {
+ txlist[0] = '-';
+ txlist[1] = '\0';
+ }
+
+ if (!detail) {
+ printf("Port #%02d %-25.*s transmitting to: %s\n",
+ port_info->slot,
+ (int)port_info->name.slen,
+ port_info->name.ptr,
+ txlist);
+ } else {
+ unsigned tx_level, rx_level;
+
+ pjmedia_conf_get_signal_level(conf, port_info->slot,
+ &tx_level, &rx_level);
+
+ printf("Port #%02d:\n"
+ " Name : %.*s\n"
+ " Sampling rate : %d Hz\n"
+ " Samples per frame : %d\n"
+ " Frame time : %d ms\n"
+ " Signal level adjustment : tx=%d, rx=%d\n"
+ " Current signal level : tx=%u, rx=%u\n"
+ " Transmitting to ports : %s\n\n",
+ port_info->slot,
+ (int)port_info->name.slen,
+ port_info->name.ptr,
+ port_info->clock_rate,
+ port_info->samples_per_frame,
+ port_info->samples_per_frame*1000/port_info->clock_rate,
+ port_info->tx_adj_level,
+ port_info->rx_adj_level,
+ tx_level,
+ rx_level,
+ txlist);
+ }
+
+ }
+ puts("");
+}
+
+
+/*
+ * Display VU meter
+ */
+static void monitor_level(pjmedia_conf *conf, int slot, int dir, int dur)
+{
+ enum { SLEEP = 20, SAMP_CNT = 2};
+ pj_status_t status;
+ int i, total_count;
+ unsigned level, samp_cnt;
+
+
+ puts("");
+ printf("Displaying VU meter for port %d for about %d seconds\n",
+ slot, dur);
+
+ total_count = dur * 1000 / SLEEP;
+
+ level = 0;
+ samp_cnt = 0;
+
+ for (i=0; i<total_count; ++i) {
+ unsigned tx_level, rx_level;
+ int j, length;
+ char meter[21];
+
+ /* Poll the volume every 20 msec */
+ status = pjmedia_conf_get_signal_level(conf, slot,
+ &tx_level, &rx_level);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to read level", status);
+ return;
+ }
+
+ level += (dir=='r' ? rx_level : tx_level);
+ ++samp_cnt;
+
+ /* Accumulate until we have enough samples */
+ if (samp_cnt < SAMP_CNT) {
+ pj_thread_sleep(SLEEP);
+ continue;
+ }
+
+ /* Get average */
+ level = level / samp_cnt;
+
+ /* Draw bar */
+ length = 20 * level / 255;
+ for (j=0; j<length; ++j)
+ meter[j] = '#';
+ for (; j<20; ++j)
+ meter[j] = ' ';
+ meter[20] = '\0';
+
+ printf("Port #%02d %cx level: [%s] %d \r",
+ slot, dir, meter, level);
+
+ /* Next.. */
+ samp_cnt = 0;
+ level = 0;
+
+ pj_thread_sleep(SLEEP);
+ }
+
+ puts("");
+}
+
diff --git a/pjsip-apps/src/samples/debug.c b/pjsip-apps/src/samples/debug.c
new file mode 100644
index 0000000..1d38f9a
--- /dev/null
+++ b/pjsip-apps/src/samples/debug.c
@@ -0,0 +1,32 @@
+/* $Id: debug.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+/*
+ * PURPOSE:
+ * The purpose of this file is to allow debugging of a sample application
+ * using MSVC IDE.
+ */
+
+/* To debug a sample application, include the source file here.
+ * E.g.:
+ * #include "playfile.c"
+ */
+#include "icedemo.c"
+
diff --git a/pjsip-apps/src/samples/encdec.c b/pjsip-apps/src/samples/encdec.c
new file mode 100644
index 0000000..9ded990
--- /dev/null
+++ b/pjsip-apps/src/samples/encdec.c
@@ -0,0 +1,263 @@
+/* $Id: encdec.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+ /**
+ * \page page_pjmedia_samples_encdec_c Samples: Encoding and Decoding
+ *
+ * This sample shows how to use codec.
+ *
+ * This file is pjsip-apps/src/samples/encdec.c
+ *
+ * \includelineno encdec.c
+ */
+
+#include <pjlib.h>
+#include <pjmedia.h>
+#include <pjmedia-codec.h>
+
+#define THIS_FILE "encdec.c"
+
+static const char *desc =
+ " encdec \n"
+ " \n"
+ " PURPOSE: \n"
+ " Encode input WAV with a codec, and decode the result to another WAV \n"
+ "\n"
+ "\n"
+ " USAGE: \n"
+ " encdec codec input.wav output.wav \n"
+ "\n"
+ "\n"
+ " where:\n"
+ " codec Set the codec name. \n"
+ " input.wav Set the input WAV filename. \n"
+ " output.wav Set the output WAV filename. \n"
+
+ "\n"
+;
+
+//#undef PJ_TRACE
+//#define PJ_TRACE 1
+
+#ifndef PJ_TRACE
+# define PJ_TRACE 0
+#endif
+
+#if PJ_TRACE
+# define TRACE_(expr) PJ_LOG(4,expr)
+#else
+# define TRACE_(expr)
+#endif
+
+
+static void err(const char *op, pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(3,("", "%s error: %s", op, errmsg));
+}
+
+#define CHECK(op) do { \
+ status = op; \
+ if (status != PJ_SUCCESS) { \
+ err(#op, status); \
+ return status; \
+ } \
+ } \
+ while (0)
+
+static pjmedia_endpt *mept;
+static unsigned file_msec_duration;
+
+static pj_status_t enc_dec_test(const char *codec_id,
+ const char *filein,
+ const char *fileout)
+{
+ pj_pool_t *pool;
+ pjmedia_codec_mgr *cm;
+ pjmedia_codec *codec;
+ const pjmedia_codec_info *pci;
+ pjmedia_codec_param param;
+ unsigned cnt, samples_per_frame;
+ pj_str_t tmp;
+ pjmedia_port *wavin, *wavout;
+ unsigned lost_pct;
+ pj_status_t status;
+
+#define T file_msec_duration/1000, file_msec_duration%1000
+
+ pool = pjmedia_endpt_create_pool(mept, "encdec", 1000, 1000);
+
+ cm = pjmedia_endpt_get_codec_mgr(mept);
+
+#ifdef LOST_PCT
+ lost_pct = LOST_PCT;
+#else
+ lost_pct = 0;
+#endif
+
+ cnt = 1;
+ CHECK( pjmedia_codec_mgr_find_codecs_by_id(cm, pj_cstr(&tmp, codec_id),
+ &cnt, &pci, NULL) );
+ CHECK( pjmedia_codec_mgr_get_default_param(cm, pci, &param) );
+
+ samples_per_frame = param.info.clock_rate * param.info.frm_ptime / 1000;
+
+ /* Control VAD */
+ param.setting.vad = 1;
+
+ /* Open wav for reading */
+ CHECK( pjmedia_wav_player_port_create(pool, filein,
+ param.info.frm_ptime,
+ PJMEDIA_FILE_NO_LOOP, 0, &wavin) );
+
+ /* Open wav for writing */
+ CHECK( pjmedia_wav_writer_port_create(pool, fileout,
+ param.info.clock_rate,
+ param.info.channel_cnt,
+ samples_per_frame,
+ 16, 0, 0, &wavout) );
+
+ /* Alloc codec */
+ CHECK( pjmedia_codec_mgr_alloc_codec(cm, pci, &codec) );
+ CHECK( pjmedia_codec_init(codec, pool) );
+ CHECK( pjmedia_codec_open(codec, &param) );
+
+ for (;;) {
+ pjmedia_frame frm_pcm, frm_bit, out_frm, frames[4];
+ pj_int16_t pcmbuf[320];
+ pj_timestamp ts;
+ pj_uint8_t bitstream[160];
+
+ frm_pcm.buf = (char*)pcmbuf;
+ frm_pcm.size = samples_per_frame * 2;
+
+ /* Read from WAV */
+ if (pjmedia_port_get_frame(wavin, &frm_pcm) != PJ_SUCCESS)
+ break;
+ if (frm_pcm.type != PJMEDIA_FRAME_TYPE_AUDIO)
+ break;;
+
+ /* Update duration */
+ file_msec_duration += samples_per_frame * 1000 /
+ param.info.clock_rate;
+
+ /* Encode */
+ frm_bit.buf = bitstream;
+ frm_bit.size = sizeof(bitstream);
+ CHECK(pjmedia_codec_encode(codec, &frm_pcm, sizeof(bitstream),
+ &frm_bit));
+
+ /* On DTX, write zero frame to wavout to maintain duration */
+ if (frm_bit.size == 0 || frm_bit.type != PJMEDIA_FRAME_TYPE_AUDIO) {
+ out_frm.buf = (char*)pcmbuf;
+ out_frm.size = 160;
+ CHECK( pjmedia_port_put_frame(wavout, &out_frm) );
+ TRACE_((THIS_FILE, "%d.%03d read: %u, enc: %u",
+ T, frm_pcm.size, frm_bit.size));
+ continue;
+ }
+
+ /* Parse the bitstream (not really necessary for this case
+ * since we always decode 1 frame, but it's still good
+ * for testing)
+ */
+ ts.u64 = 0;
+ cnt = PJ_ARRAY_SIZE(frames);
+ CHECK( pjmedia_codec_parse(codec, bitstream, frm_bit.size, &ts, &cnt,
+ frames) );
+ CHECK( (cnt==1 ? PJ_SUCCESS : -1) );
+
+ /* Decode or simulate packet loss */
+ out_frm.buf = (char*)pcmbuf;
+ out_frm.size = sizeof(pcmbuf);
+
+ if ((pj_rand() % 100) < (int)lost_pct) {
+ /* Simulate loss */
+ CHECK( pjmedia_codec_recover(codec, sizeof(pcmbuf), &out_frm) );
+ TRACE_((THIS_FILE, "%d.%03d Packet lost", T));
+ } else {
+ /* Decode */
+ CHECK( pjmedia_codec_decode(codec, &frames[0], sizeof(pcmbuf),
+ &out_frm) );
+ }
+
+ /* Write to WAV */
+ CHECK( pjmedia_port_put_frame(wavout, &out_frm) );
+
+ TRACE_((THIS_FILE, "%d.%03d read: %u, enc: %u, dec/write: %u",
+ T, frm_pcm.size, frm_bit.size, out_frm.size));
+ }
+
+ /* Close wavs */
+ pjmedia_port_destroy(wavout);
+ pjmedia_port_destroy(wavin);
+
+ /* Close codec */
+ pjmedia_codec_close(codec);
+ pjmedia_codec_mgr_dealloc_codec(cm, codec);
+
+ /* Release pool */
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+
+
+int main(int argc, char *argv[])
+{
+ pj_caching_pool cp;
+ pj_time_val t0, t1;
+ pj_status_t status;
+
+ if (argc != 4) {
+ puts(desc);
+ return 1;
+ }
+
+ CHECK( pj_init() );
+
+ pj_caching_pool_init(&cp, NULL, 0);
+
+ CHECK( pjmedia_endpt_create(&cp.factory, NULL, 1, &mept) );
+
+ /* Register all codecs */
+ CHECK( pjmedia_codec_register_audio_codecs(mept, NULL) );
+
+ pj_gettimeofday(&t0);
+ status = enc_dec_test(argv[1], argv[2], argv[3]);
+ pj_gettimeofday(&t1);
+ PJ_TIME_VAL_SUB(t1, t0);
+
+ pjmedia_endpt_destroy(mept);
+ pj_caching_pool_destroy(&cp);
+ pj_shutdown();
+
+ if (status == PJ_SUCCESS) {
+ puts("");
+ puts("Success");
+ printf("Duration: %ds.%03d\n", file_msec_duration/1000,
+ file_msec_duration%1000);
+ printf("Time: %lds.%03ld\n", t1.sec, t1.msec);
+ }
+
+ return 0;
+}
+
diff --git a/pjsip-apps/src/samples/footprint.c b/pjsip-apps/src/samples/footprint.c
new file mode 100644
index 0000000..241ba1e
--- /dev/null
+++ b/pjsip-apps/src/samples/footprint.c
@@ -0,0 +1,654 @@
+/* $Id: footprint.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+/**
+ * The purpose of this file is to show the typical footprint of
+ * the application when various PJSIP/PJMEDIA components are used.
+ *
+ * This file will not be build as samples, but instead it is build
+ * by get-footprint.py Python script in pjsip-apps/build directory.
+ */
+
+#include <pjsip_ua.h>
+#include <pjsip_simple.h>
+#include <pjsip.h>
+#include <pjmedia.h>
+#include <pjmedia-codec.h>
+#include <pjlib-util.h>
+#include <pjlib.h>
+#include <pjnath.h>
+#include <stdlib.h>
+
+/* All flags: */
+#if 0
+#define HAS_PJLIB
+
+#define HAS_PJLIB_STUN
+#define HAS_PJLIB_GETOPT
+#define HAS_PJLIB_XML
+#define HAS_PJLIB_SCANNER
+#define HAS_PJLIB_DNS
+#define HAS_PJLIB_RESOLVER
+#define HAS_PJLIB_SRV_RESOLVER
+
+#define HAS_PJLIB_CRC32
+#define HAS_PJLIB_HMAC_MD5
+#define HAS_PJLIB_HMAC_SHA1
+
+#define HAS_PJSIP_CORE_MSG_ELEM
+#define HAS_PJSIP_CORE
+#define HAS_PJSIP_CORE_MSG_UTIL
+
+#define HAS_PJSIP_UDP_TRANSPORT
+#define HAS_PJSIP_TCP_TRANSPORT
+#define HAS_PJSIP_TLS_TRANSPORT
+#define HAS_PJSIP_TRANSACTION
+#define HAS_PJSIP_UA_LAYER
+#define HAS_PJMEDIA_SDP
+#define HAS_PJMEDIA_SDP_NEGOTIATOR
+#define HAS_PJSIP_AUTH_CLIENT
+#define HAS_PJSIP_INV_SESSION
+#define HAS_PJSIP_REGC
+#define HAS_PJSIP_EVENT_FRAMEWORK
+#define HAS_PJSIP_CALL_TRANSFER
+#define HAS_PJSIP_PRESENCE
+#define HAS_PJSIP_IS_COMPOSING
+
+#define HAS_PJNATH_STUN
+#define HAS_PJNATH_ICE
+
+#define HAS_PJMEDIA
+#define HAS_PJMEDIA_SND_DEV
+#define HAS_PJMEDIA_EC
+#define HAS_PJMEDIA_SND_PORT
+#define HAS_PJMEDIA_RESAMPLE
+#define HAS_PJMEDIA_SILENCE_DET
+#define HAS_PJMEDIA_PLC
+#define HAS_PJMEDIA_CONFERENCE
+#define HAS_PJMEDIA_MASTER_PORT
+#define HAS_PJMEDIA_RTP
+#define HAS_PJMEDIA_RTCP
+#define HAS_PJMEDIA_JBUF
+#define HAS_PJMEDIA_STREAM
+#define HAS_PJMEDIA_TONEGEN
+#define HAS_PJMEDIA_UDP_TRANSPORT
+#define HAS_PJMEDIA_FILE_PLAYER
+#define HAS_PJMEDIA_FILE_CAPTURE
+#define HAS_PJMEDIA_MEM_PLAYER
+#define HAS_PJMEDIA_MEM_CAPTURE
+#define HAS_PJMEDIA_ICE
+
+#define HAS_PJMEDIA_G711_CODEC
+#define HAS_PJMEDIA_GSM_CODEC
+#define HAS_PJMEDIA_SPEEX_CODEC
+#define HAS_PJMEDIA_ILBC_CODEC
+#endif
+
+
+int dummy_function()
+{
+ pj_caching_pool cp;
+
+ sprintf(NULL, "%d", 0);
+ rand();
+
+#ifdef HAS_PJLIB
+ pj_init();
+ pj_caching_pool_init(&cp, NULL, 0);
+ pj_array_erase(NULL, 0, 0, 0);
+ pj_create_unique_string(NULL, NULL);
+ pj_hash_create(NULL, 0);
+ pj_hash_get(NULL, NULL, 0, NULL);
+ pj_hash_set(NULL, NULL, NULL, 0, 0, NULL);
+ pj_ioqueue_create(NULL, 0, NULL);
+ pj_ioqueue_register_sock(NULL, NULL, 0, NULL, NULL, NULL);
+ pj_pool_alloc(NULL, 0);
+ pj_timer_heap_create(NULL, 0, NULL);
+#endif
+
+#ifdef HAS_PJLIB_STUN
+ pjstun_get_mapped_addr(&cp.factory, 0, NULL, NULL, 80, NULL, 80, NULL);
+#endif
+
+#ifdef HAS_PJLIB_GETOPT
+ pj_getopt_long(0, NULL, NULL, NULL, NULL);
+#endif
+
+#ifdef HAS_PJLIB_XML
+ pj_xml_parse(NULL, NULL, 100);
+ pj_xml_print(NULL, NULL, 10, PJ_FALSE);
+ pj_xml_clone(NULL, NULL);
+ pj_xml_node_new(NULL, NULL);
+ pj_xml_attr_new(NULL, NULL, NULL);
+ pj_xml_add_node(NULL, NULL);
+ pj_xml_add_attr(NULL, NULL);
+ pj_xml_find_node(NULL, NULL);
+ pj_xml_find_next_node(NULL, NULL, NULL);
+ pj_xml_find_attr(NULL, NULL, NULL);
+ pj_xml_find(NULL, NULL, NULL, NULL);
+#endif
+
+#ifdef HAS_PJLIB_SCANNER
+ pj_cis_buf_init(NULL);
+ pj_cis_init(NULL, NULL);
+ pj_cis_dup(NULL, NULL);
+ pj_cis_add_alpha(NULL);
+ pj_cis_add_str(NULL, NULL);
+
+ pj_scan_init(NULL, NULL, 0, 0, NULL);
+ pj_scan_fini(NULL);
+ pj_scan_peek(NULL, NULL, NULL);
+ pj_scan_peek_n(NULL, 0, NULL);
+ pj_scan_peek_until(NULL, NULL, NULL);
+ pj_scan_get(NULL, NULL, NULL);
+ pj_scan_get_unescape(NULL, NULL, NULL);
+ pj_scan_get_quote(NULL, 0, 0, NULL);
+ pj_scan_get_n(NULL, 0, NULL);
+ pj_scan_get_char(NULL);
+ pj_scan_get_until(NULL, NULL, NULL);
+ pj_scan_strcmp(NULL, NULL, 0);
+ pj_scan_stricmp(NULL, NULL, 0);
+ pj_scan_stricmp_alnum(NULL, NULL, 0);
+ pj_scan_get_newline(NULL);
+ pj_scan_restore_state(NULL, NULL);
+#endif
+
+#ifdef HAS_PJLIB_DNS
+ pj_dns_make_query(NULL, NULL, 0, 0, NULL);
+ pj_dns_parse_packet(NULL, NULL, 0, NULL);
+ pj_dns_packet_dup(NULL, NULL, 0, NULL);
+#endif
+
+#ifdef HAS_PJLIB_RESOLVER
+ pj_dns_resolver_create(NULL, NULL, 0, NULL, NULL, NULL);
+ pj_dns_resolver_set_ns(NULL, 0, NULL, NULL);
+ pj_dns_resolver_handle_events(NULL, NULL);
+ pj_dns_resolver_destroy(NULL, 0);
+ pj_dns_resolver_start_query(NULL, NULL, 0, 0, NULL, NULL, NULL);
+ pj_dns_resolver_cancel_query(NULL, 0);
+ pj_dns_resolver_add_entry(NULL, NULL, 0);
+#endif
+
+#ifdef HAS_PJLIB_SRV_RESOLVER
+ pj_dns_srv_resolve(NULL, NULL, 0, NULL, NULL, PJ_FALSE, NULL, NULL);
+#endif
+
+#ifdef HAS_PJLIB_CRC32
+ pj_crc32_init(NULL);
+ pj_crc32_update(NULL, NULL, 0);
+ pj_crc32_final(NULL);
+#endif
+
+#ifdef HAS_PJLIB_HMAC_MD5
+ pj_hmac_md5(NULL, 0, NULL, 0, NULL);
+#endif
+
+#ifdef HAS_PJLIB_HMAC_SHA1
+ pj_hmac_sha1(NULL, 0, NULL, 0, NULL);
+#endif
+
+#ifdef HAS_PJNATH_STUN
+ pj_stun_session_create(NULL, NULL, NULL, PJ_FALSE, NULL);
+ pj_stun_session_destroy(NULL);
+ pj_stun_session_set_credential(NULL, NULL);
+ pj_stun_session_create_req(NULL, 0, NULL, NULL);
+ pj_stun_session_create_ind(NULL, 0, NULL);
+ pj_stun_session_create_res(NULL, NULL, 0, NULL, NULL);
+ pj_stun_session_send_msg(NULL, PJ_FALSE, NULL, 0, NULL);
+#endif
+
+#ifdef HAS_PJNATH_ICE
+ pj_ice_strans_create(NULL, NULL, 0, NULL, NULL, NULL);
+ pj_ice_strans_set_stun_domain(NULL, NULL, NULL);
+ pj_ice_strans_create_comp(NULL, 0, 0, NULL);
+ pj_ice_strans_add_cand(NULL, 0, PJ_ICE_CAND_TYPE_HOST, 0, NULL, PJ_FALSE);
+ pj_ice_strans_init_ice(NULL, PJ_ICE_SESS_ROLE_CONTROLLED, NULL, NULL);
+ pj_ice_strans_start_ice(NULL, NULL, NULL, 0, NULL);
+ pj_ice_strans_stop_ice(NULL);
+ pj_ice_strans_sendto(NULL, 0, NULL, 0, NULL, 0);
+#endif
+
+#ifdef HAS_PJSIP_CORE_MSG_ELEM
+ /* Parameter container */
+ pjsip_param_find(NULL, NULL);
+ pjsip_param_print_on(NULL, NULL, 0, NULL, NULL, 0);
+
+ /* SIP URI */
+ pjsip_sip_uri_create(NULL, 0);
+ pjsip_name_addr_create(NULL);
+
+ /* TEL URI */
+ pjsip_tel_uri_create(NULL);
+
+ /* Message and headers */
+ pjsip_msg_create(NULL, PJSIP_REQUEST_MSG);
+ pjsip_msg_print(NULL, NULL, 0);
+ pjsip_accept_hdr_create(NULL);
+ pjsip_allow_hdr_create(NULL);
+ pjsip_cid_hdr_create(NULL);
+ pjsip_clen_hdr_create(NULL);
+ pjsip_cseq_hdr_create(NULL);
+ pjsip_contact_hdr_create(NULL);
+ pjsip_ctype_hdr_create(NULL);
+ pjsip_expires_hdr_create(NULL, 0);
+ pjsip_from_hdr_create(NULL);
+ pjsip_max_fwd_hdr_create(NULL, 0);
+ pjsip_min_expires_hdr_create(NULL, 0);
+ pjsip_rr_hdr_create(NULL);
+ pjsip_require_hdr_create(NULL);
+ pjsip_retry_after_hdr_create(NULL, 0);
+ pjsip_supported_hdr_create(NULL);
+ pjsip_unsupported_hdr_create(NULL);
+ pjsip_via_hdr_create(NULL);
+ pjsip_warning_hdr_create(NULL, 0, NULL, NULL);
+
+ pjsip_parse_uri(NULL, NULL, 0, 0);
+ pjsip_parse_msg(NULL, NULL, 0, NULL);
+ pjsip_parse_rdata(NULL, 0, NULL);
+ pjsip_find_msg(NULL, 0, 0, NULL);
+#endif
+
+#ifdef HAS_PJSIP_CORE
+ pjsip_endpt_create(NULL, NULL, NULL);
+
+ pjsip_tpmgr_create(NULL, NULL, NULL, NULL, NULL);
+ pjsip_tpmgr_destroy(NULL);
+ pjsip_transport_send(NULL, NULL, NULL, 0, NULL, NULL);
+
+
+#endif
+
+#ifdef HAS_PJSIP_CORE_MSG_UTIL
+ pjsip_endpt_create_request(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ -1, NULL, NULL);
+ pjsip_endpt_create_request_from_hdr(NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, -1, NULL, NULL);
+ pjsip_endpt_create_response(NULL, NULL, -1, NULL, NULL);
+ pjsip_endpt_create_ack(NULL, NULL, NULL, NULL);
+ pjsip_endpt_create_cancel(NULL, NULL, NULL);
+ pjsip_get_request_dest(NULL, NULL);
+ pjsip_endpt_send_request_stateless(NULL, NULL, NULL, NULL);
+ pjsip_get_response_addr(NULL, NULL, NULL);
+ pjsip_endpt_send_response(NULL, NULL, NULL, NULL, NULL);
+ pjsip_endpt_respond_stateless(NULL, NULL, -1, NULL, NULL, NULL);
+#endif
+
+#ifdef HAS_PJSIP_UDP_TRANSPORT
+ pjsip_udp_transport_start(NULL, NULL, NULL, 1, NULL);
+#endif
+
+#ifdef HAS_PJSIP_TCP_TRANSPORT
+ pjsip_tcp_transport_start(NULL, NULL, 1, NULL);
+#endif
+
+#ifdef HAS_PJSIP_TLS_TRANSPORT
+ pjsip_tls_transport_start(NULL, NULL, NULL, NULL, 0, NULL);
+#endif
+
+#ifdef HAS_PJSIP_TRANSACTION
+ pjsip_tsx_layer_init_module(NULL);
+
+ pjsip_tsx_layer_destroy();
+ pjsip_tsx_create_uac(NULL, NULL, NULL);
+ pjsip_tsx_create_uas(NULL, NULL, NULL);
+ pjsip_tsx_recv_msg(NULL, NULL);
+ pjsip_tsx_send_msg(NULL, NULL);
+ pjsip_tsx_terminate(NULL, 200);
+
+ pjsip_endpt_send_request(NULL, NULL, -1, NULL, NULL);
+ pjsip_endpt_respond(NULL, NULL, NULL, -1, NULL, NULL, NULL, NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_SDP
+ pjmedia_sdp_parse(NULL, NULL, 1024, NULL);
+ pjmedia_sdp_print(NULL, NULL, 1024);
+ pjmedia_sdp_validate(NULL);
+ pjmedia_sdp_session_clone(NULL, NULL);
+ pjmedia_sdp_session_cmp(NULL, NULL, 0);
+ pjmedia_sdp_attr_to_rtpmap(NULL, NULL, NULL);
+ pjmedia_sdp_attr_get_fmtp(NULL, NULL);
+ pjmedia_sdp_attr_get_rtcp(NULL, NULL);
+ pjmedia_sdp_conn_clone(NULL, NULL);
+ pjmedia_sdp_media_clone(NULL, NULL);
+ pjmedia_sdp_media_find_attr(NULL, NULL, NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_SDP_NEGOTIATOR
+ pjmedia_sdp_neg_create_w_local_offer(NULL, NULL, NULL);
+ pjmedia_sdp_neg_create_w_remote_offer(NULL, NULL, NULL, NULL);
+ pjmedia_sdp_neg_get_state(NULL);
+ pjmedia_sdp_neg_negotiate(NULL, NULL, PJ_FALSE);
+#endif
+
+#ifdef HAS_PJSIP_UA_LAYER
+ pjsip_ua_init_module(NULL, NULL);
+ pjsip_ua_destroy();
+ pjsip_dlg_create_uac(NULL, NULL, NULL, NULL, NULL, NULL);
+ pjsip_dlg_create_uas(NULL, NULL, NULL, NULL);
+ pjsip_dlg_terminate(NULL);
+ pjsip_dlg_set_route_set(NULL, NULL);
+ pjsip_dlg_create_request(NULL, NULL, -1, NULL);
+ pjsip_dlg_send_request(NULL, NULL, -1, NULL);
+ pjsip_dlg_create_response(NULL, NULL, -1, NULL, NULL);
+ pjsip_dlg_modify_response(NULL, NULL, -1, NULL);
+ pjsip_dlg_send_response(NULL, NULL, NULL);
+ pjsip_dlg_respond(NULL, NULL, -1, NULL, NULL, NULL);
+#endif
+
+#ifdef HAS_PJSIP_AUTH_CLIENT
+ pjsip_auth_clt_init(NULL, NULL, NULL, 0);
+ pjsip_auth_clt_clone(NULL, NULL, NULL);
+ pjsip_auth_clt_set_credentials(NULL, 0, NULL);
+ pjsip_auth_clt_init_req(NULL, NULL);
+ pjsip_auth_clt_reinit_req(NULL, NULL, NULL, NULL);
+#endif
+
+#ifdef HAS_PJSIP_INV_SESSION
+ pjsip_inv_usage_init(NULL, NULL);
+ pjsip_inv_create_uac(NULL, NULL, 0, NULL);
+ pjsip_inv_verify_request(NULL, NULL, NULL, NULL, NULL, NULL);
+ pjsip_inv_create_uas(NULL, NULL, NULL, 0, NULL);
+ pjsip_inv_terminate(NULL, 200, PJ_FALSE);
+ pjsip_inv_invite(NULL, NULL);
+ pjsip_inv_initial_answer(NULL, NULL, 200, NULL, NULL, NULL);
+ pjsip_inv_answer(NULL, 200, NULL, NULL, NULL);
+ pjsip_inv_end_session(NULL, 200, NULL, NULL);
+ pjsip_inv_reinvite(NULL, NULL, NULL, NULL);
+ pjsip_inv_update(NULL, NULL, NULL, NULL);
+ pjsip_inv_send_msg(NULL, NULL);
+ pjsip_dlg_get_inv_session(NULL);
+ //pjsip_tsx_get_inv_session(NULL);
+ pjsip_inv_state_name(PJSIP_INV_STATE_NULL);
+#endif
+
+#ifdef HAS_PJSIP_REGC
+ //pjsip_regc_get_module();
+ pjsip_regc_create(NULL, NULL, NULL, NULL);
+ pjsip_regc_destroy(NULL);
+ pjsip_regc_get_info(NULL, NULL);
+ pjsip_regc_get_pool(NULL);
+ pjsip_regc_init(NULL, NULL, NULL, NULL, 0, NULL, 600);
+ pjsip_regc_set_credentials(NULL, 1, NULL);
+ pjsip_regc_set_route_set(NULL, NULL);
+ pjsip_regc_register(NULL, PJ_TRUE, NULL);
+ pjsip_regc_unregister(NULL, NULL);
+ pjsip_regc_update_contact(NULL, 10, NULL);
+ pjsip_regc_update_expires(NULL, 600);
+ pjsip_regc_send(NULL, NULL);
+#endif
+
+#ifdef HAS_PJSIP_EVENT_FRAMEWORK
+ pjsip_evsub_init_module(NULL);
+ pjsip_evsub_instance();
+ pjsip_evsub_register_pkg(NULL, NULL, 30, 10, NULL);
+ pjsip_evsub_create_uac(NULL, NULL, NULL, 10, NULL);
+ pjsip_evsub_create_uas(NULL, NULL, NULL, 10, NULL);
+ pjsip_evsub_terminate(NULL, PJ_FALSE);
+ pjsip_evsub_get_state(NULL);
+ pjsip_evsub_get_state_name(NULL);
+ pjsip_evsub_initiate(NULL, NULL, -1, NULL);
+ pjsip_evsub_accept(NULL, NULL, 200, NULL);
+ pjsip_evsub_notify(NULL, PJSIP_EVSUB_STATE_ACTIVE, NULL, NULL, NULL);
+ pjsip_evsub_current_notify(NULL, NULL);
+ pjsip_evsub_send_request(NULL, NULL);
+ pjsip_tsx_get_evsub(NULL);
+ pjsip_evsub_set_mod_data(NULL, 1, NULL);
+ pjsip_evsub_get_mod_data(NULL, 1);
+#endif
+
+#ifdef HAS_PJSIP_CALL_TRANSFER
+ pjsip_xfer_init_module(NULL);
+ pjsip_xfer_create_uac(NULL, NULL, NULL);
+ pjsip_xfer_create_uas(NULL, NULL, NULL, NULL);
+ pjsip_xfer_initiate(NULL, NULL, NULL);
+ pjsip_xfer_accept(NULL, NULL, 200, NULL);
+ pjsip_xfer_notify(NULL, PJSIP_EVSUB_STATE_ACTIVE, 200, NULL, NULL);
+ pjsip_xfer_current_notify(NULL, NULL);
+ pjsip_xfer_send_request(NULL, NULL);
+#endif
+
+#ifdef HAS_PJSIP_PRESENCE
+ pjsip_pres_init_module(NULL, NULL);
+ pjsip_pres_instance();
+ pjsip_pres_create_uac(NULL, NULL, 0, NULL);
+ pjsip_pres_create_uas(NULL, NULL, NULL, NULL);
+ pjsip_pres_terminate(NULL, PJ_FALSE);
+ pjsip_pres_initiate(NULL, 100, NULL);
+ pjsip_pres_accept(NULL, NULL, 200, NULL);
+ pjsip_pres_notify(NULL, PJSIP_EVSUB_STATE_ACTIVE, NULL, NULL, NULL);
+ pjsip_pres_current_notify(NULL, NULL);
+ pjsip_pres_send_request(NULL, NULL);
+ pjsip_pres_get_status(NULL, NULL);
+ pjsip_pres_set_status(NULL, NULL);
+#endif
+
+#ifdef HAS_PJSIP_IS_COMPOSING
+ pjsip_iscomposing_create_xml(NULL, PJ_TRUE, NULL, NULL, 0);
+ pjsip_iscomposing_create_body(NULL, PJ_TRUE, NULL, NULL, 0);
+ pjsip_iscomposing_parse(NULL, NULL, 0, NULL, NULL, NULL, NULL);
+#endif
+
+#ifdef HAS_PJMEDIA
+ pjmedia_endpt_create(NULL, NULL, 1, NULL);
+ pjmedia_endpt_destroy(NULL);
+ pjmedia_endpt_create_sdp(NULL, NULL, 1, NULL, NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_EC
+ pjmedia_echo_create(NULL, 0, 0, 0, 0, 0, NULL);
+ pjmedia_echo_destroy(NULL);
+ pjmedia_echo_playback(NULL, NULL);
+ pjmedia_echo_capture(NULL, NULL, 0);
+ pjmedia_echo_cancel(NULL, NULL, NULL, 0, NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_SND_DEV
+ pjmedia_snd_init(NULL);
+ pjmedia_snd_get_dev_count();
+ pjmedia_snd_get_dev_info(0);
+ pjmedia_snd_open(-1, -1, 8000, 1, 80, 16, NULL, NULL, NULL, NULL);
+ pjmedia_snd_open_rec(-1, 8000, 1, 160, 16, NULL, NULL, NULL);
+ pjmedia_snd_open_player(-1, 8000, 1, 160, 16, NULL, NULL, NULL);
+ pjmedia_snd_stream_start(NULL);
+ pjmedia_snd_stream_stop(NULL);
+ pjmedia_snd_stream_close(NULL);
+ pjmedia_snd_deinit();
+#endif
+
+#ifdef HAS_PJMEDIA_SND_PORT
+ pjmedia_snd_port_create(NULL, -1, -1, 8000, 1, 180, 16, 0, NULL);
+ pjmedia_snd_port_create_rec(NULL, -1, 8000, 1, 160, 16, 0, NULL);
+ pjmedia_snd_port_create_player(NULL, -1, 8000, 1, 160, 16, 0, NULL);
+ pjmedia_snd_port_destroy(NULL);
+ pjmedia_snd_port_get_snd_stream(NULL);
+ pjmedia_snd_port_connect(NULL, NULL);
+ pjmedia_snd_port_get_port(NULL);
+ pjmedia_snd_port_disconnect(NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_RESAMPLE
+ pjmedia_resample_create(NULL, PJ_TRUE, PJ_TRUE, 0, 0, 0, 0, NULL);
+ pjmedia_resample_run(NULL, NULL, NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_SILENCE_DET
+ pjmedia_silence_det_create(NULL, 8000, 80, NULL);
+ pjmedia_silence_det_detect(NULL, NULL, 0, NULL);
+ pjmedia_silence_det_apply(NULL, 0);
+#endif
+
+#ifdef HAS_PJMEDIA_PLC
+ pjmedia_plc_create(NULL, 8000, 80, 0, NULL);
+ pjmedia_plc_save(NULL, NULL);
+ pjmedia_plc_generate(NULL, NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_CONFERENCE
+ pjmedia_conf_create(NULL, 10, 8000, 1, 160, 16, 0, NULL);
+ pjmedia_conf_destroy(NULL);
+ pjmedia_conf_get_master_port(NULL);
+ pjmedia_conf_add_port(NULL, NULL, NULL, NULL, NULL);
+ pjmedia_conf_configure_port(NULL, 1, 0, 0);
+ pjmedia_conf_connect_port(NULL, 0, 0, 0);
+ pjmedia_conf_disconnect_port(NULL, 0, 0);
+ pjmedia_conf_remove_port(NULL, 0);
+ pjmedia_conf_enum_ports(NULL, NULL, NULL);
+ pjmedia_conf_get_port_info(NULL, 0, NULL);
+ pjmedia_conf_get_ports_info(NULL, NULL, NULL);
+ pjmedia_conf_get_signal_level(NULL, 0, NULL, NULL);
+ pjmedia_conf_adjust_rx_level(NULL, 0, 0);
+ pjmedia_conf_adjust_tx_level(NULL, 0, 0);
+#endif
+
+#ifdef HAS_PJMEDIA_MASTER_PORT
+ pjmedia_master_port_create(NULL, NULL, NULL, 0, NULL);
+ pjmedia_master_port_start(NULL);
+ pjmedia_master_port_stop(NULL);
+ pjmedia_master_port_set_uport(NULL, NULL);
+ pjmedia_master_port_get_uport(NULL);
+ pjmedia_master_port_set_dport(NULL, NULL);
+ pjmedia_master_port_get_dport(NULL);
+ pjmedia_master_port_destroy(NULL, PJ_FALSE);
+#endif
+
+#ifdef HAS_PJMEDIA_RTP
+ pjmedia_rtp_session_init(NULL, 0, 0);
+ pjmedia_rtp_encode_rtp(NULL, 0, 0, 0, 0, NULL, NULL);
+ pjmedia_rtp_decode_rtp(NULL, NULL, 0, NULL, NULL, NULL);
+ pjmedia_rtp_session_update(NULL, NULL, NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_RTCP
+ pjmedia_rtcp_init(NULL, NULL, 0, 0, 0);
+ pjmedia_rtcp_get_ntp_time(NULL, NULL);
+ pjmedia_rtcp_fini(NULL);
+ pjmedia_rtcp_rx_rtp(NULL, 0, 0, 0);
+ pjmedia_rtcp_tx_rtp(NULL, 0);
+ pjmedia_rtcp_rx_rtcp(NULL, NULL, 0);
+ pjmedia_rtcp_build_rtcp(NULL, NULL, NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_JBUF
+ pjmedia_jbuf_create(NULL, NULL, 0, 0, 0, NULL);
+ pjmedia_jbuf_set_fixed(NULL, 0);
+ pjmedia_jbuf_set_adaptive(NULL, 0, 0, 0);
+ pjmedia_jbuf_destroy(NULL);
+ pjmedia_jbuf_put_frame(NULL, NULL, 0, 0);
+ pjmedia_jbuf_get_frame(NULL, NULL, NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_STREAM
+ pjmedia_stream_create(NULL, NULL, NULL, NULL, NULL, NULL);
+ pjmedia_stream_destroy(NULL);
+ pjmedia_stream_get_port(NULL, NULL);
+ pjmedia_stream_get_transport(NULL);
+ pjmedia_stream_start(NULL);
+ pjmedia_stream_get_stat(NULL, NULL);
+ pjmedia_stream_pause(NULL, PJMEDIA_DIR_ENCODING);
+ pjmedia_stream_resume(NULL, PJMEDIA_DIR_ENCODING);
+ pjmedia_stream_dial_dtmf(NULL, NULL);
+ pjmedia_stream_check_dtmf(NULL);
+ pjmedia_stream_get_dtmf(NULL, NULL, NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_TONEGEN
+ pjmedia_tonegen_create(NULL, 0, 0, 0, 0, 0, NULL);
+ pjmedia_tonegen_is_busy(NULL);
+ pjmedia_tonegen_stop(NULL);
+ pjmedia_tonegen_play(NULL, 0, NULL, 0);
+ pjmedia_tonegen_play_digits(NULL, 0, NULL, 0);
+ pjmedia_tonegen_get_digit_map(NULL, NULL);
+ pjmedia_tonegen_set_digit_map(NULL, NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_UDP_TRANSPORT
+ pjmedia_transport_udp_create(NULL, NULL, 0, 0, NULL);
+ pjmedia_transport_udp_close(NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_FILE_PLAYER
+ pjmedia_wav_player_port_create(NULL, NULL, 0, 0, 0, NULL);
+ pjmedia_wav_player_port_set_pos(NULL, 0);
+ pjmedia_wav_player_port_get_pos(NULL);
+ pjmedia_wav_player_set_eof_cb(NULL, NULL, NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_FILE_CAPTURE
+ pjmedia_wav_writer_port_create(NULL, NULL, 8000, 1, 80, 16, 0, 0, NULL);
+ pjmedia_wav_writer_port_get_pos(NULL);
+ pjmedia_wav_writer_port_set_cb(NULL, 0, NULL, NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_MEM_PLAYER
+ pjmedia_mem_player_create(NULL, NULL, 1000, 8000, 1, 80, 16, 0, NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_MEM_CAPTURE
+ pjmedia_mem_capture_create(NULL, NULL, 1000, 8000, 1, 80, 16, 0, NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_ICE
+ pjmedia_ice_create(NULL, NULL, 0, NULL, NULL);
+ pjmedia_ice_destroy(NULL);
+ pjmedia_ice_start_init(NULL, 0, NULL, NULL, NULL);
+ pjmedia_ice_init_ice(NULL, PJ_ICE_SESS_ROLE_CONTROLLED, NULL, NULL);
+ pjmedia_ice_modify_sdp(NULL, NULL, NULL);
+ pjmedia_ice_start_ice(NULL, NULL, NULL, 0);
+ pjmedia_ice_stop_ice(NULL);
+#endif
+
+#ifdef HAS_PJMEDIA_G711_CODEC
+ pjmedia_codec_g711_init(NULL);
+ pjmedia_codec_g711_deinit();
+#endif
+
+#ifdef HAS_PJMEDIA_GSM_CODEC
+ pjmedia_codec_gsm_init(NULL);
+ pjmedia_codec_gsm_deinit();
+#endif
+
+#ifdef HAS_PJMEDIA_SPEEX_CODEC
+ pjmedia_codec_speex_init(NULL, 0, 0, 0);
+ pjmedia_codec_speex_deinit();
+#endif
+
+#ifdef HAS_PJMEDIA_ILBC_CODEC
+ pjmedia_codec_ilbc_init(NULL, 0);
+ pjmedia_codec_ilbc_deinit();
+#endif
+
+ return 0;
+}
+
+
+int test_main()
+{
+ return dummy_function();
+}
+
+#if defined(PJ_RTEMS) && PJ_RTEMS!=0
+# include "../../pjlib/src/pjlib-test/main_rtems.c"
+#else
+int main()
+{
+ return test_main();
+}
+#endif
+
diff --git a/pjsip-apps/src/samples/httpdemo.c b/pjsip-apps/src/samples/httpdemo.c
new file mode 100644
index 0000000..6b2bf8e
--- /dev/null
+++ b/pjsip-apps/src/samples/httpdemo.c
@@ -0,0 +1,183 @@
+/* $Id: httpdemo.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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
+ */
+
+/**
+ * \page page_httpdemo_c Samples: HTTP Client demo
+ *
+ * This file is pjsip-apps/src/samples/httpdemo.c
+ *
+ * \includelineno httpdemo.c
+ */
+
+#include <pjlib.h>
+#include <pjlib-util.h>
+#include <pjlib-util/http_client.h>
+#include <pjsip.h>
+#include <pjmedia.h>
+#include <pjnath.h>
+#include <pjsip_simple.h>
+
+static pj_timer_heap_t *timer_heap;
+static pj_ioqueue_t *ioqueue;
+static pj_pool_t *pool;
+static pj_http_req *http_req;
+static pj_pool_factory *mem;
+static FILE *f = NULL;
+
+//#define VERBOSE
+#define THIS_FILE "http_demo"
+
+static void on_response(pj_http_req *http_req, const pj_http_resp *resp)
+{
+ unsigned i;
+
+ PJ_UNUSED_ARG(http_req);
+ PJ_LOG(3,(THIS_FILE, "%.*s %d %.*s", (int)resp->version.slen, resp->version.ptr,
+ resp->status_code,
+ (int)resp->reason.slen, resp->reason.ptr));
+
+ for (i=0; i<resp->headers.count; ++i) {
+ const pj_http_header_elmt *h = &resp->headers.header[i];
+
+ if (!pj_stricmp2(&h->name, "Content-Length") ||
+ !pj_stricmp2(&h->name, "Content-Type"))
+ {
+ PJ_LOG(3,(THIS_FILE, "%.*s: %.*s",
+ (int)h->name.slen, h->name.ptr,
+ (int)h->value.slen, h->value.ptr));
+ }
+ }
+}
+
+static void on_send_data(pj_http_req *http_req, void **data, pj_size_t *size)
+{
+ PJ_UNUSED_ARG(http_req);
+ PJ_UNUSED_ARG(size);
+ PJ_UNUSED_ARG(data);
+}
+
+static void on_data_read(pj_http_req *hreq, void *data, pj_size_t size)
+{
+ PJ_UNUSED_ARG(hreq);
+
+ if (size > 0) {
+ fwrite(data, 1, size, f);
+ fflush(f);
+#ifdef VERBOSE
+ PJ_LOG(3, (THIS_FILE, "Data received: %d bytes", size));
+ printf("%.*s\n", (int)size, (char *)data);
+#endif
+ }
+}
+
+static void on_complete(pj_http_req *hreq, pj_status_t status,
+ const pj_http_resp *resp)
+{
+ PJ_UNUSED_ARG(hreq);
+
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1, (THIS_FILE, status, "HTTP request completed with error"));
+ return;
+ }
+ PJ_LOG(3, (THIS_FILE, "Data completed: %d bytes", resp->size));
+ if (resp->size > 0 && resp->data) {
+#ifdef VERBOSE
+ printf("%.*s\n", (int)resp->size, (char *)resp->data);
+#endif
+ }
+}
+
+pj_status_t getURL(const char *curl)
+{
+ pj_str_t url;
+ pj_http_req_callback hcb;
+ pj_status_t status;
+
+ pj_bzero(&hcb, sizeof(hcb));
+ hcb.on_complete = &on_complete;
+ hcb.on_data_read = &on_data_read;
+ hcb.on_send_data = &on_send_data;
+ hcb.on_response = &on_response;
+
+ /* Create pool, timer, and ioqueue */
+ pool = pj_pool_create(mem, NULL, 8192, 4096, NULL);
+ if (pj_timer_heap_create(pool, 16, &timer_heap))
+ return -31;
+ if (pj_ioqueue_create(pool, 16, &ioqueue))
+ return -32;
+
+ pj_strdup2(pool, &url, curl);
+
+ if ((status = pj_http_req_create(pool, &url, timer_heap, ioqueue,
+ NULL, &hcb, &http_req)) != PJ_SUCCESS)
+ return status;
+
+ if ((status = pj_http_req_start(http_req)) != PJ_SUCCESS)
+ return status;
+
+ while (pj_http_req_is_running(http_req)) {
+ pj_time_val delay = {0, 50};
+ pj_ioqueue_poll(ioqueue, &delay);
+ pj_timer_heap_poll(timer_heap, NULL);
+ }
+
+ pj_http_req_destroy(http_req);
+ pj_ioqueue_destroy(ioqueue);
+ pj_timer_heap_destroy(timer_heap);
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+ pj_caching_pool cp;
+ pj_status_t status;
+
+ if (argc < 2 || argc > 3) {
+ puts("Usage: httpdemo URL [output-filename]");
+ return 1;
+ }
+
+ pj_log_set_level(5);
+
+ pj_init();
+ pj_caching_pool_init(&cp, NULL, 0);
+ mem = &cp.factory;
+ pjlib_util_init();
+
+ if (argc > 2)
+ f = fopen(argv[2], "wb");
+ else
+ f = stdout;
+
+ status = getURL(argv[1]);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1, (THIS_FILE, status, "Error"));
+ }
+
+ if (f != stdout)
+ fclose(f);
+
+ pj_caching_pool_destroy(&cp);
+ pj_shutdown();
+ return 0;
+}
diff --git a/pjsip-apps/src/samples/icedemo.c b/pjsip-apps/src/samples/icedemo.c
new file mode 100644
index 0000000..08292ee
--- /dev/null
+++ b/pjsip-apps/src/samples/icedemo.c
@@ -0,0 +1,1276 @@
+/* $Id: icedemo.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <pjlib.h>
+#include <pjlib-util.h>
+#include <pjnath.h>
+
+
+#define THIS_FILE "icedemo.c"
+
+/* For this demo app, configure longer STUN keep-alive time
+ * so that it does't clutter the screen output.
+ */
+#define KA_INTERVAL 300
+
+
+/* This is our global variables */
+static struct app_t
+{
+ /* Command line options are stored here */
+ struct options
+ {
+ unsigned comp_cnt;
+ pj_str_t ns;
+ int max_host;
+ pj_bool_t regular;
+ pj_str_t stun_srv;
+ pj_str_t turn_srv;
+ pj_bool_t turn_tcp;
+ pj_str_t turn_username;
+ pj_str_t turn_password;
+ pj_bool_t turn_fingerprint;
+ const char *log_file;
+ } opt;
+
+ /* Our global variables */
+ pj_caching_pool cp;
+ pj_pool_t *pool;
+ pj_thread_t *thread;
+ pj_bool_t thread_quit_flag;
+ pj_ice_strans_cfg ice_cfg;
+ pj_ice_strans *icest;
+ FILE *log_fhnd;
+
+ /* Variables to store parsed remote ICE info */
+ struct rem_info
+ {
+ char ufrag[80];
+ char pwd[80];
+ unsigned comp_cnt;
+ pj_sockaddr def_addr[PJ_ICE_MAX_COMP];
+ unsigned cand_cnt;
+ pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
+ } rem;
+
+} icedemo;
+
+/* Utility to display error messages */
+static void icedemo_perror(const char *title, pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(1,(THIS_FILE, "%s: %s", title, errmsg));
+}
+
+/* Utility: display error message and exit application (usually
+ * because of fatal error.
+ */
+static void err_exit(const char *title, pj_status_t status)
+{
+ if (status != PJ_SUCCESS) {
+ icedemo_perror(title, status);
+ }
+ PJ_LOG(3,(THIS_FILE, "Shutting down.."));
+
+ if (icedemo.icest)
+ pj_ice_strans_destroy(icedemo.icest);
+
+ pj_thread_sleep(500);
+
+ icedemo.thread_quit_flag = PJ_TRUE;
+ if (icedemo.thread) {
+ pj_thread_join(icedemo.thread);
+ pj_thread_destroy(icedemo.thread);
+ }
+
+ if (icedemo.ice_cfg.stun_cfg.ioqueue)
+ pj_ioqueue_destroy(icedemo.ice_cfg.stun_cfg.ioqueue);
+
+ if (icedemo.ice_cfg.stun_cfg.timer_heap)
+ pj_timer_heap_destroy(icedemo.ice_cfg.stun_cfg.timer_heap);
+
+ pj_caching_pool_destroy(&icedemo.cp);
+
+ pj_shutdown();
+
+ if (icedemo.log_fhnd) {
+ fclose(icedemo.log_fhnd);
+ icedemo.log_fhnd = NULL;
+ }
+
+ exit(status != PJ_SUCCESS);
+}
+
+#define CHECK(expr) status=expr; \
+ if (status!=PJ_SUCCESS) { \
+ err_exit(#expr, status); \
+ }
+
+/*
+ * This function checks for events from both timer and ioqueue (for
+ * network events). It is invoked by the worker thread.
+ */
+static pj_status_t handle_events(unsigned max_msec, unsigned *p_count)
+{
+ enum { MAX_NET_EVENTS = 1 };
+ pj_time_val max_timeout = {0, 0};
+ pj_time_val timeout = { 0, 0};
+ unsigned count = 0, net_event_count = 0;
+ int c;
+
+ max_timeout.msec = max_msec;
+
+ /* Poll the timer to run it and also to retrieve the earliest entry. */
+ timeout.sec = timeout.msec = 0;
+ c = pj_timer_heap_poll( icedemo.ice_cfg.stun_cfg.timer_heap, &timeout );
+ if (c > 0)
+ count += c;
+
+ /* timer_heap_poll should never ever returns negative value, or otherwise
+ * ioqueue_poll() will block forever!
+ */
+ pj_assert(timeout.sec >= 0 && timeout.msec >= 0);
+ if (timeout.msec >= 1000) timeout.msec = 999;
+
+ /* compare the value with the timeout to wait from timer, and use the
+ * minimum value.
+ */
+ if (PJ_TIME_VAL_GT(timeout, max_timeout))
+ timeout = max_timeout;
+
+ /* Poll ioqueue.
+ * Repeat polling the ioqueue while we have immediate events, because
+ * timer heap may process more than one events, so if we only process
+ * one network events at a time (such as when IOCP backend is used),
+ * the ioqueue may have trouble keeping up with the request rate.
+ *
+ * For example, for each send() request, one network event will be
+ * reported by ioqueue for the send() completion. If we don't poll
+ * the ioqueue often enough, the send() completion will not be
+ * reported in timely manner.
+ */
+ do {
+ c = pj_ioqueue_poll( icedemo.ice_cfg.stun_cfg.ioqueue, &timeout);
+ if (c < 0) {
+ pj_status_t err = pj_get_netos_error();
+ pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
+ if (p_count)
+ *p_count = count;
+ return err;
+ } else if (c == 0) {
+ break;
+ } else {
+ net_event_count += c;
+ timeout.sec = timeout.msec = 0;
+ }
+ } while (c > 0 && net_event_count < MAX_NET_EVENTS);
+
+ count += net_event_count;
+ if (p_count)
+ *p_count = count;
+
+ return PJ_SUCCESS;
+
+}
+
+/*
+ * This is the worker thread that polls event in the background.
+ */
+static int icedemo_worker_thread(void *unused)
+{
+ PJ_UNUSED_ARG(unused);
+
+ while (!icedemo.thread_quit_flag) {
+ handle_events(500, NULL);
+ }
+
+ return 0;
+}
+
+/*
+ * This is the callback that is registered to the ICE stream transport to
+ * receive notification about incoming data. By "data" it means application
+ * data such as RTP/RTCP, and not packets that belong to ICE signaling (such
+ * as STUN connectivity checks or TURN signaling).
+ */
+static void cb_on_rx_data(pj_ice_strans *ice_st,
+ unsigned comp_id,
+ void *pkt, pj_size_t size,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ char ipstr[PJ_INET6_ADDRSTRLEN+10];
+
+ PJ_UNUSED_ARG(ice_st);
+ PJ_UNUSED_ARG(src_addr_len);
+ PJ_UNUSED_ARG(pkt);
+
+ // Don't do this! It will ruin the packet buffer in case TCP is used!
+ //((char*)pkt)[size] = '\0';
+
+ PJ_LOG(3,(THIS_FILE, "Component %d: received %d bytes data from %s: \"%.*s\"",
+ comp_id, size,
+ pj_sockaddr_print(src_addr, ipstr, sizeof(ipstr), 3),
+ (unsigned)size,
+ (char*)pkt));
+}
+
+/*
+ * This is the callback that is registered to the ICE stream transport to
+ * receive notification about ICE state progression.
+ */
+static void cb_on_ice_complete(pj_ice_strans *ice_st,
+ pj_ice_strans_op op,
+ pj_status_t status)
+{
+ const char *opname =
+ (op==PJ_ICE_STRANS_OP_INIT? "initialization" :
+ (op==PJ_ICE_STRANS_OP_NEGOTIATION ? "negotiation" : "unknown_op"));
+
+ if (status == PJ_SUCCESS) {
+ PJ_LOG(3,(THIS_FILE, "ICE %s successful", opname));
+ } else {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(1,(THIS_FILE, "ICE %s failed: %s", opname, errmsg));
+ pj_ice_strans_destroy(ice_st);
+ icedemo.icest = NULL;
+ }
+}
+
+/* log callback to write to file */
+static void log_func(int level, const char *data, int len)
+{
+ pj_log_write(level, data, len);
+ if (icedemo.log_fhnd) {
+ if (fwrite(data, len, 1, icedemo.log_fhnd) != 1)
+ return;
+ }
+}
+
+/*
+ * This is the main application initialization function. It is called
+ * once (and only once) during application initialization sequence by
+ * main().
+ */
+static pj_status_t icedemo_init(void)
+{
+ pj_status_t status;
+
+ if (icedemo.opt.log_file) {
+ icedemo.log_fhnd = fopen(icedemo.opt.log_file, "a");
+ pj_log_set_log_func(&log_func);
+ }
+
+ /* Initialize the libraries before anything else */
+ CHECK( pj_init() );
+ CHECK( pjlib_util_init() );
+ CHECK( pjnath_init() );
+
+ /* Must create pool factory, where memory allocations come from */
+ pj_caching_pool_init(&icedemo.cp, NULL, 0);
+
+ /* Init our ICE settings with null values */
+ pj_ice_strans_cfg_default(&icedemo.ice_cfg);
+
+ icedemo.ice_cfg.stun_cfg.pf = &icedemo.cp.factory;
+
+ /* Create application memory pool */
+ icedemo.pool = pj_pool_create(&icedemo.cp.factory, "icedemo",
+ 512, 512, NULL);
+
+ /* Create timer heap for timer stuff */
+ CHECK( pj_timer_heap_create(icedemo.pool, 100,
+ &icedemo.ice_cfg.stun_cfg.timer_heap) );
+
+ /* and create ioqueue for network I/O stuff */
+ CHECK( pj_ioqueue_create(icedemo.pool, 16,
+ &icedemo.ice_cfg.stun_cfg.ioqueue) );
+
+ /* something must poll the timer heap and ioqueue,
+ * unless we're on Symbian where the timer heap and ioqueue run
+ * on themselves.
+ */
+ CHECK( pj_thread_create(icedemo.pool, "icedemo", &icedemo_worker_thread,
+ NULL, 0, 0, &icedemo.thread) );
+
+ icedemo.ice_cfg.af = pj_AF_INET();
+
+ /* Create DNS resolver if nameserver is set */
+ if (icedemo.opt.ns.slen) {
+ CHECK( pj_dns_resolver_create(&icedemo.cp.factory,
+ "resolver",
+ 0,
+ icedemo.ice_cfg.stun_cfg.timer_heap,
+ icedemo.ice_cfg.stun_cfg.ioqueue,
+ &icedemo.ice_cfg.resolver) );
+
+ CHECK( pj_dns_resolver_set_ns(icedemo.ice_cfg.resolver, 1,
+ &icedemo.opt.ns, NULL) );
+ }
+
+ /* -= Start initializing ICE stream transport config =- */
+
+ /* Maximum number of host candidates */
+ if (icedemo.opt.max_host != -1)
+ icedemo.ice_cfg.stun.max_host_cands = icedemo.opt.max_host;
+
+ /* Nomination strategy */
+ if (icedemo.opt.regular)
+ icedemo.ice_cfg.opt.aggressive = PJ_FALSE;
+ else
+ icedemo.ice_cfg.opt.aggressive = PJ_TRUE;
+
+ /* Configure STUN/srflx candidate resolution */
+ if (icedemo.opt.stun_srv.slen) {
+ char *pos;
+
+ /* Command line option may contain port number */
+ if ((pos=pj_strchr(&icedemo.opt.stun_srv, ':')) != NULL) {
+ icedemo.ice_cfg.stun.server.ptr = icedemo.opt.stun_srv.ptr;
+ icedemo.ice_cfg.stun.server.slen = (pos - icedemo.opt.stun_srv.ptr);
+
+ icedemo.ice_cfg.stun.port = (pj_uint16_t)atoi(pos+1);
+ } else {
+ icedemo.ice_cfg.stun.server = icedemo.opt.stun_srv;
+ icedemo.ice_cfg.stun.port = PJ_STUN_PORT;
+ }
+
+ /* For this demo app, configure longer STUN keep-alive time
+ * so that it does't clutter the screen output.
+ */
+ icedemo.ice_cfg.stun.cfg.ka_interval = KA_INTERVAL;
+ }
+
+ /* Configure TURN candidate */
+ if (icedemo.opt.turn_srv.slen) {
+ char *pos;
+
+ /* Command line option may contain port number */
+ if ((pos=pj_strchr(&icedemo.opt.turn_srv, ':')) != NULL) {
+ icedemo.ice_cfg.turn.server.ptr = icedemo.opt.turn_srv.ptr;
+ icedemo.ice_cfg.turn.server.slen = (pos - icedemo.opt.turn_srv.ptr);
+
+ icedemo.ice_cfg.turn.port = (pj_uint16_t)atoi(pos+1);
+ } else {
+ icedemo.ice_cfg.turn.server = icedemo.opt.turn_srv;
+ icedemo.ice_cfg.turn.port = PJ_STUN_PORT;
+ }
+
+ /* TURN credential */
+ icedemo.ice_cfg.turn.auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;
+ icedemo.ice_cfg.turn.auth_cred.data.static_cred.username = icedemo.opt.turn_username;
+ icedemo.ice_cfg.turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
+ icedemo.ice_cfg.turn.auth_cred.data.static_cred.data = icedemo.opt.turn_password;
+
+ /* Connection type to TURN server */
+ if (icedemo.opt.turn_tcp)
+ icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_TCP;
+ else
+ icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_UDP;
+
+ /* For this demo app, configure longer keep-alive time
+ * so that it does't clutter the screen output.
+ */
+ icedemo.ice_cfg.turn.alloc_param.ka_interval = KA_INTERVAL;
+ }
+
+ /* -= That's it for now, initialization is complete =- */
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create ICE stream transport instance, invoked from the menu.
+ */
+static void icedemo_create_instance(void)
+{
+ pj_ice_strans_cb icecb;
+ pj_status_t status;
+
+ if (icedemo.icest != NULL) {
+ puts("ICE instance already created, destroy it first");
+ return;
+ }
+
+ /* init the callback */
+ pj_bzero(&icecb, sizeof(icecb));
+ icecb.on_rx_data = cb_on_rx_data;
+ icecb.on_ice_complete = cb_on_ice_complete;
+
+ /* create the instance */
+ status = pj_ice_strans_create("icedemo", /* object name */
+ &icedemo.ice_cfg, /* settings */
+ icedemo.opt.comp_cnt, /* comp_cnt */
+ NULL, /* user data */
+ &icecb, /* callback */
+ &icedemo.icest) /* instance ptr */
+ ;
+ if (status != PJ_SUCCESS)
+ icedemo_perror("error creating ice", status);
+ else
+ PJ_LOG(3,(THIS_FILE, "ICE instance successfully created"));
+}
+
+/* Utility to nullify parsed remote info */
+static void reset_rem_info(void)
+{
+ pj_bzero(&icedemo.rem, sizeof(icedemo.rem));
+}
+
+
+/*
+ * Destroy ICE stream transport instance, invoked from the menu.
+ */
+static void icedemo_destroy_instance(void)
+{
+ if (icedemo.icest == NULL) {
+ PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
+ return;
+ }
+
+ pj_ice_strans_destroy(icedemo.icest);
+ icedemo.icest = NULL;
+
+ reset_rem_info();
+
+ PJ_LOG(3,(THIS_FILE, "ICE instance destroyed"));
+}
+
+
+/*
+ * Create ICE session, invoked from the menu.
+ */
+static void icedemo_init_session(unsigned rolechar)
+{
+ pj_ice_sess_role role = (pj_tolower((pj_uint8_t)rolechar)=='o' ?
+ PJ_ICE_SESS_ROLE_CONTROLLING :
+ PJ_ICE_SESS_ROLE_CONTROLLED);
+ pj_status_t status;
+
+ if (icedemo.icest == NULL) {
+ PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
+ return;
+ }
+
+ if (pj_ice_strans_has_sess(icedemo.icest)) {
+ PJ_LOG(1,(THIS_FILE, "Error: Session already created"));
+ return;
+ }
+
+ status = pj_ice_strans_init_ice(icedemo.icest, role, NULL, NULL);
+ if (status != PJ_SUCCESS)
+ icedemo_perror("error creating session", status);
+ else
+ PJ_LOG(3,(THIS_FILE, "ICE session created"));
+
+ reset_rem_info();
+}
+
+
+/*
+ * Stop/destroy ICE session, invoked from the menu.
+ */
+static void icedemo_stop_session(void)
+{
+ pj_status_t status;
+
+ if (icedemo.icest == NULL) {
+ PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
+ return;
+ }
+
+ if (!pj_ice_strans_has_sess(icedemo.icest)) {
+ PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
+ return;
+ }
+
+ status = pj_ice_strans_stop_ice(icedemo.icest);
+ if (status != PJ_SUCCESS)
+ icedemo_perror("error stopping session", status);
+ else
+ PJ_LOG(3,(THIS_FILE, "ICE session stopped"));
+
+ reset_rem_info();
+}
+
+#define PRINT(fmt, arg0, arg1, arg2, arg3, arg4, arg5) \
+ printed = pj_ansi_snprintf(p, maxlen - (p-buffer), \
+ fmt, arg0, arg1, arg2, arg3, arg4, arg5); \
+ if (printed <= 0) return -PJ_ETOOSMALL; \
+ p += printed
+
+
+/* Utility to create a=candidate SDP attribute */
+static int print_cand(char buffer[], unsigned maxlen,
+ const pj_ice_sess_cand *cand)
+{
+ char ipaddr[PJ_INET6_ADDRSTRLEN];
+ char *p = buffer;
+ int printed;
+
+ PRINT("a=candidate:%.*s %u UDP %u %s %u typ ",
+ (int)cand->foundation.slen,
+ cand->foundation.ptr,
+ (unsigned)cand->comp_id,
+ cand->prio,
+ pj_sockaddr_print(&cand->addr, ipaddr,
+ sizeof(ipaddr), 0),
+ (unsigned)pj_sockaddr_get_port(&cand->addr));
+
+ PRINT("%s\n",
+ pj_ice_get_cand_type_name(cand->type),
+ 0, 0, 0, 0, 0);
+
+ if (p == buffer+maxlen)
+ return -PJ_ETOOSMALL;
+
+ *p = '\0';
+
+ return p-buffer;
+}
+
+/*
+ * Encode ICE information in SDP.
+ */
+static int encode_session(char buffer[], unsigned maxlen)
+{
+ char *p = buffer;
+ unsigned comp;
+ int printed;
+ pj_str_t local_ufrag, local_pwd;
+ pj_status_t status;
+
+ /* Write "dummy" SDP v=, o=, s=, and t= lines */
+ PRINT("v=0\no=- 3414953978 3414953978 IN IP4 localhost\ns=ice\nt=0 0\n",
+ 0, 0, 0, 0, 0, 0);
+
+ /* Get ufrag and pwd from current session */
+ pj_ice_strans_get_ufrag_pwd(icedemo.icest, &local_ufrag, &local_pwd,
+ NULL, NULL);
+
+ /* Write the a=ice-ufrag and a=ice-pwd attributes */
+ PRINT("a=ice-ufrag:%.*s\na=ice-pwd:%.*s\n",
+ (int)local_ufrag.slen,
+ local_ufrag.ptr,
+ (int)local_pwd.slen,
+ local_pwd.ptr,
+ 0, 0);
+
+ /* Write each component */
+ for (comp=0; comp<icedemo.opt.comp_cnt; ++comp) {
+ unsigned j, cand_cnt;
+ pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
+ char ipaddr[PJ_INET6_ADDRSTRLEN];
+
+ /* Get default candidate for the component */
+ status = pj_ice_strans_get_def_cand(icedemo.icest, comp+1, &cand[0]);
+ if (status != PJ_SUCCESS)
+ return -status;
+
+ /* Write the default address */
+ if (comp==0) {
+ /* For component 1, default address is in m= and c= lines */
+ PRINT("m=audio %d RTP/AVP 0\n"
+ "c=IN IP4 %s\n",
+ (int)pj_sockaddr_get_port(&cand[0].addr),
+ pj_sockaddr_print(&cand[0].addr, ipaddr,
+ sizeof(ipaddr), 0),
+ 0, 0, 0, 0);
+ } else if (comp==1) {
+ /* For component 2, default address is in a=rtcp line */
+ PRINT("a=rtcp:%d IN IP4 %s\n",
+ (int)pj_sockaddr_get_port(&cand[0].addr),
+ pj_sockaddr_print(&cand[0].addr, ipaddr,
+ sizeof(ipaddr), 0),
+ 0, 0, 0, 0);
+ } else {
+ /* For other components, we'll just invent this.. */
+ PRINT("a=Xice-defcand:%d IN IP4 %s\n",
+ (int)pj_sockaddr_get_port(&cand[0].addr),
+ pj_sockaddr_print(&cand[0].addr, ipaddr,
+ sizeof(ipaddr), 0),
+ 0, 0, 0, 0);
+ }
+
+ /* Enumerate all candidates for this component */
+ status = pj_ice_strans_enum_cands(icedemo.icest, comp+1,
+ &cand_cnt, cand);
+ if (status != PJ_SUCCESS)
+ return -status;
+
+ /* And encode the candidates as SDP */
+ for (j=0; j<cand_cnt; ++j) {
+ printed = print_cand(p, maxlen - (p-buffer), &cand[j]);
+ if (printed < 0)
+ return -PJ_ETOOSMALL;
+ p += printed;
+ }
+ }
+
+ if (p == buffer+maxlen)
+ return -PJ_ETOOSMALL;
+
+ *p = '\0';
+ return p - buffer;
+}
+
+
+/*
+ * Show information contained in the ICE stream transport. This is
+ * invoked from the menu.
+ */
+static void icedemo_show_ice(void)
+{
+ static char buffer[1000];
+ int len;
+
+ if (icedemo.icest == NULL) {
+ PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
+ return;
+ }
+
+ puts("General info");
+ puts("---------------");
+ printf("Component count : %d\n", icedemo.opt.comp_cnt);
+ printf("Status : ");
+ if (pj_ice_strans_sess_is_complete(icedemo.icest))
+ puts("negotiation complete");
+ else if (pj_ice_strans_sess_is_running(icedemo.icest))
+ puts("negotiation is in progress");
+ else if (pj_ice_strans_has_sess(icedemo.icest))
+ puts("session ready");
+ else
+ puts("session not created");
+
+ if (!pj_ice_strans_has_sess(icedemo.icest)) {
+ puts("Create the session first to see more info");
+ return;
+ }
+
+ printf("Negotiated comp_cnt: %d\n",
+ pj_ice_strans_get_running_comp_cnt(icedemo.icest));
+ printf("Role : %s\n",
+ pj_ice_strans_get_role(icedemo.icest)==PJ_ICE_SESS_ROLE_CONTROLLED ?
+ "controlled" : "controlling");
+
+ len = encode_session(buffer, sizeof(buffer));
+ if (len < 0)
+ err_exit("not enough buffer to show ICE status", -len);
+
+ puts("");
+ printf("Local SDP (paste this to remote host):\n"
+ "--------------------------------------\n"
+ "%s\n", buffer);
+
+
+ puts("");
+ puts("Remote info:\n"
+ "----------------------");
+ if (icedemo.rem.cand_cnt==0) {
+ puts("No remote info yet");
+ } else {
+ unsigned i;
+
+ printf("Remote ufrag : %s\n", icedemo.rem.ufrag);
+ printf("Remote password : %s\n", icedemo.rem.pwd);
+ printf("Remote cand. cnt. : %d\n", icedemo.rem.cand_cnt);
+
+ for (i=0; i<icedemo.rem.cand_cnt; ++i) {
+ len = print_cand(buffer, sizeof(buffer), &icedemo.rem.cand[i]);
+ if (len < 0)
+ err_exit("not enough buffer to show ICE status", -len);
+
+ printf(" %s", buffer);
+ }
+ }
+}
+
+
+/*
+ * Input and parse SDP from the remote (containing remote's ICE information)
+ * and save it to global variables.
+ */
+static void icedemo_input_remote(void)
+{
+ char linebuf[80];
+ unsigned media_cnt = 0;
+ unsigned comp0_port = 0;
+ char comp0_addr[80];
+ pj_bool_t done = PJ_FALSE;
+
+ puts("Paste SDP from remote host, end with empty line");
+
+ reset_rem_info();
+
+ comp0_addr[0] = '\0';
+
+ while (!done) {
+ int len;
+ char *line;
+
+ printf(">");
+ if (stdout) fflush(stdout);
+
+ if (fgets(linebuf, sizeof(linebuf), stdin)==NULL)
+ break;
+
+ len = strlen(linebuf);
+ while (len && (linebuf[len-1] == '\r' || linebuf[len-1] == '\n'))
+ linebuf[--len] = '\0';
+
+ line = linebuf;
+ while (len && pj_isspace(*line))
+ ++line, --len;
+
+ if (len==0)
+ break;
+
+ /* Ignore subsequent media descriptors */
+ if (media_cnt > 1)
+ continue;
+
+ switch (line[0]) {
+ case 'm':
+ {
+ int cnt;
+ char media[32], portstr[32];
+
+ ++media_cnt;
+ if (media_cnt > 1) {
+ puts("Media line ignored");
+ break;
+ }
+
+ cnt = sscanf(line+2, "%s %s RTP/", media, portstr);
+ if (cnt != 2) {
+ PJ_LOG(1,(THIS_FILE, "Error parsing media line"));
+ goto on_error;
+ }
+
+ comp0_port = atoi(portstr);
+
+ }
+ break;
+ case 'c':
+ {
+ int cnt;
+ char c[32], net[32], ip[80];
+
+ cnt = sscanf(line+2, "%s %s %s", c, net, ip);
+ if (cnt != 3) {
+ PJ_LOG(1,(THIS_FILE, "Error parsing connection line"));
+ goto on_error;
+ }
+
+ strcpy(comp0_addr, ip);
+ }
+ break;
+ case 'a':
+ {
+ char *attr = strtok(line+2, ": \t\r\n");
+ if (strcmp(attr, "ice-ufrag")==0) {
+ strcpy(icedemo.rem.ufrag, attr+strlen(attr)+1);
+ } else if (strcmp(attr, "ice-pwd")==0) {
+ strcpy(icedemo.rem.pwd, attr+strlen(attr)+1);
+ } else if (strcmp(attr, "rtcp")==0) {
+ char *val = attr+strlen(attr)+1;
+ int af, cnt;
+ int port;
+ char net[32], ip[64];
+ pj_str_t tmp_addr;
+ pj_status_t status;
+
+ cnt = sscanf(val, "%d IN %s %s", &port, net, ip);
+ if (cnt != 3) {
+ PJ_LOG(1,(THIS_FILE, "Error parsing rtcp attribute"));
+ goto on_error;
+ }
+
+ if (strchr(ip, ':'))
+ af = pj_AF_INET6();
+ else
+ af = pj_AF_INET();
+
+ pj_sockaddr_init(af, &icedemo.rem.def_addr[1], NULL, 0);
+ tmp_addr = pj_str(ip);
+ status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[1],
+ &tmp_addr);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(1,(THIS_FILE, "Invalid IP address"));
+ goto on_error;
+ }
+ pj_sockaddr_set_port(&icedemo.rem.def_addr[1], (pj_uint16_t)port);
+
+ } else if (strcmp(attr, "candidate")==0) {
+ char *sdpcand = attr+strlen(attr)+1;
+ int af, cnt;
+ char foundation[32], transport[12], ipaddr[80], type[32];
+ pj_str_t tmpaddr;
+ int comp_id, prio, port;
+ pj_ice_sess_cand *cand;
+ pj_status_t status;
+
+ cnt = sscanf(sdpcand, "%s %d %s %d %s %d typ %s",
+ foundation,
+ &comp_id,
+ transport,
+ &prio,
+ ipaddr,
+ &port,
+ type);
+ if (cnt != 7) {
+ PJ_LOG(1, (THIS_FILE, "error: Invalid ICE candidate line"));
+ goto on_error;
+ }
+
+ cand = &icedemo.rem.cand[icedemo.rem.cand_cnt];
+ pj_bzero(cand, sizeof(*cand));
+
+ if (strcmp(type, "host")==0)
+ cand->type = PJ_ICE_CAND_TYPE_HOST;
+ else if (strcmp(type, "srflx")==0)
+ cand->type = PJ_ICE_CAND_TYPE_SRFLX;
+ else if (strcmp(type, "relay")==0)
+ cand->type = PJ_ICE_CAND_TYPE_RELAYED;
+ else {
+ PJ_LOG(1, (THIS_FILE, "Error: invalid candidate type '%s'",
+ type));
+ goto on_error;
+ }
+
+ cand->comp_id = (pj_uint8_t)comp_id;
+ pj_strdup2(icedemo.pool, &cand->foundation, foundation);
+ cand->prio = prio;
+
+ if (strchr(ipaddr, ':'))
+ af = pj_AF_INET6();
+ else
+ af = pj_AF_INET();
+
+ tmpaddr = pj_str(ipaddr);
+ pj_sockaddr_init(af, &cand->addr, NULL, 0);
+ status = pj_sockaddr_set_str_addr(af, &cand->addr, &tmpaddr);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(1,(THIS_FILE, "Error: invalid IP address '%s'",
+ ipaddr));
+ goto on_error;
+ }
+
+ pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)port);
+
+ ++icedemo.rem.cand_cnt;
+
+ if (cand->comp_id > icedemo.rem.comp_cnt)
+ icedemo.rem.comp_cnt = cand->comp_id;
+ }
+ }
+ break;
+ }
+ }
+
+ if (icedemo.rem.cand_cnt==0 ||
+ icedemo.rem.ufrag[0]==0 ||
+ icedemo.rem.pwd[0]==0 ||
+ icedemo.rem.comp_cnt == 0)
+ {
+ PJ_LOG(1, (THIS_FILE, "Error: not enough info"));
+ goto on_error;
+ }
+
+ if (comp0_port==0 || comp0_addr[0]=='\0') {
+ PJ_LOG(1, (THIS_FILE, "Error: default address for component 0 not found"));
+ goto on_error;
+ } else {
+ int af;
+ pj_str_t tmp_addr;
+ pj_status_t status;
+
+ if (strchr(comp0_addr, ':'))
+ af = pj_AF_INET6();
+ else
+ af = pj_AF_INET();
+
+ pj_sockaddr_init(af, &icedemo.rem.def_addr[0], NULL, 0);
+ tmp_addr = pj_str(comp0_addr);
+ status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[0],
+ &tmp_addr);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(1,(THIS_FILE, "Invalid IP address in c= line"));
+ goto on_error;
+ }
+ pj_sockaddr_set_port(&icedemo.rem.def_addr[0], (pj_uint16_t)comp0_port);
+ }
+
+ PJ_LOG(3, (THIS_FILE, "Done, %d remote candidate(s) added",
+ icedemo.rem.cand_cnt));
+ return;
+
+on_error:
+ reset_rem_info();
+}
+
+
+/*
+ * Start ICE negotiation! This function is invoked from the menu.
+ */
+static void icedemo_start_nego(void)
+{
+ pj_str_t rufrag, rpwd;
+ pj_status_t status;
+
+ if (icedemo.icest == NULL) {
+ PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
+ return;
+ }
+
+ if (!pj_ice_strans_has_sess(icedemo.icest)) {
+ PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
+ return;
+ }
+
+ if (icedemo.rem.cand_cnt == 0) {
+ PJ_LOG(1,(THIS_FILE, "Error: No remote info, input remote info first"));
+ return;
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Starting ICE negotiation.."));
+
+ status = pj_ice_strans_start_ice(icedemo.icest,
+ pj_cstr(&rufrag, icedemo.rem.ufrag),
+ pj_cstr(&rpwd, icedemo.rem.pwd),
+ icedemo.rem.cand_cnt,
+ icedemo.rem.cand);
+ if (status != PJ_SUCCESS)
+ icedemo_perror("Error starting ICE", status);
+ else
+ PJ_LOG(3,(THIS_FILE, "ICE negotiation started"));
+}
+
+
+/*
+ * Send application data to remote agent.
+ */
+static void icedemo_send_data(unsigned comp_id, const char *data)
+{
+ pj_status_t status;
+
+ if (icedemo.icest == NULL) {
+ PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
+ return;
+ }
+
+ if (!pj_ice_strans_has_sess(icedemo.icest)) {
+ PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
+ return;
+ }
+
+ /*
+ if (!pj_ice_strans_sess_is_complete(icedemo.icest)) {
+ PJ_LOG(1,(THIS_FILE, "Error: ICE negotiation has not been started or is in progress"));
+ return;
+ }
+ */
+
+ if (comp_id<1||comp_id>pj_ice_strans_get_running_comp_cnt(icedemo.icest)) {
+ PJ_LOG(1,(THIS_FILE, "Error: invalid component ID"));
+ return;
+ }
+
+ status = pj_ice_strans_sendto(icedemo.icest, comp_id, data, strlen(data),
+ &icedemo.rem.def_addr[comp_id-1],
+ pj_sockaddr_get_len(&icedemo.rem.def_addr[comp_id-1]));
+ if (status != PJ_SUCCESS)
+ icedemo_perror("Error sending data", status);
+ else
+ PJ_LOG(3,(THIS_FILE, "Data sent"));
+}
+
+
+/*
+ * Display help for the menu.
+ */
+static void icedemo_help_menu(void)
+{
+ puts("");
+ puts("-= Help on using ICE and this icedemo program =-");
+ puts("");
+ puts("This application demonstrates how to use ICE in pjnath without having\n"
+ "to use the SIP protocol. To use this application, you will need to run\n"
+ "two instances of this application, to simulate two ICE agents.\n");
+
+ puts("Basic ICE flow:\n"
+ " create instance [menu \"c\"]\n"
+ " repeat these steps as wanted:\n"
+ " - init session as offerer or answerer [menu \"i\"]\n"
+ " - display our SDP [menu \"s\"]\n"
+ " - \"send\" our SDP from the \"show\" output above to remote, by\n"
+ " copy-pasting the SDP to the other icedemo application\n"
+ " - parse remote SDP, by pasting SDP generated by the other icedemo\n"
+ " instance [menu \"r\"]\n"
+ " - begin ICE negotiation in our end [menu \"b\"], and \n"
+ " - immediately begin ICE negotiation in the other icedemo instance\n"
+ " - ICE negotiation will run, and result will be printed to screen\n"
+ " - send application data to remote [menu \"x\"]\n"
+ " - end/stop ICE session [menu \"e\"]\n"
+ " destroy instance [menu \"d\"]\n"
+ "");
+
+ puts("");
+ puts("This concludes the help screen.");
+ puts("");
+}
+
+
+/*
+ * Display console menu
+ */
+static void icedemo_print_menu(void)
+{
+ puts("");
+ puts("+----------------------------------------------------------------------+");
+ puts("| M E N U |");
+ puts("+---+------------------------------------------------------------------+");
+ puts("| c | create Create the instance |");
+ puts("| d | destroy Destroy the instance |");
+ puts("| i | init o|a Initialize ICE session as offerer or answerer |");
+ puts("| e | stop End/stop ICE session |");
+ puts("| s | show Display local ICE info |");
+ puts("| r | remote Input remote ICE info |");
+ puts("| b | start Begin ICE negotiation |");
+ puts("| x | send <compid> .. Send data to remote |");
+ puts("+---+------------------------------------------------------------------+");
+ puts("| h | help * Help! * |");
+ puts("| q | quit Quit |");
+ puts("+----------------------------------------------------------------------+");
+}
+
+
+/*
+ * Main console loop.
+ */
+static void icedemo_console(void)
+{
+ pj_bool_t app_quit = PJ_FALSE;
+
+ while (!app_quit) {
+ char input[80], *cmd;
+ const char *SEP = " \t\r\n";
+ int len;
+
+ icedemo_print_menu();
+
+ printf("Input: ");
+ if (stdout) fflush(stdout);
+
+ pj_bzero(input, sizeof(input));
+ if (fgets(input, sizeof(input), stdin) == NULL)
+ break;
+
+ len = strlen(input);
+ while (len && (input[len-1]=='\r' || input[len-1]=='\n'))
+ input[--len] = '\0';
+
+ cmd = strtok(input, SEP);
+ if (!cmd)
+ continue;
+
+ if (strcmp(cmd, "create")==0 || strcmp(cmd, "c")==0) {
+
+ icedemo_create_instance();
+
+ } else if (strcmp(cmd, "destroy")==0 || strcmp(cmd, "d")==0) {
+
+ icedemo_destroy_instance();
+
+ } else if (strcmp(cmd, "init")==0 || strcmp(cmd, "i")==0) {
+
+ char *role = strtok(NULL, SEP);
+ if (role)
+ icedemo_init_session(*role);
+ else
+ puts("error: Role required");
+
+ } else if (strcmp(cmd, "stop")==0 || strcmp(cmd, "e")==0) {
+
+ icedemo_stop_session();
+
+ } else if (strcmp(cmd, "show")==0 || strcmp(cmd, "s")==0) {
+
+ icedemo_show_ice();
+
+ } else if (strcmp(cmd, "remote")==0 || strcmp(cmd, "r")==0) {
+
+ icedemo_input_remote();
+
+ } else if (strcmp(cmd, "start")==0 || strcmp(cmd, "b")==0) {
+
+ icedemo_start_nego();
+
+ } else if (strcmp(cmd, "send")==0 || strcmp(cmd, "x")==0) {
+
+ char *comp = strtok(NULL, SEP);
+
+ if (!comp) {
+ PJ_LOG(1,(THIS_FILE, "Error: component ID required"));
+ } else {
+ char *data = comp + strlen(comp) + 1;
+ if (!data)
+ data = "";
+ icedemo_send_data(atoi(comp), data);
+ }
+
+ } else if (strcmp(cmd, "help")==0 || strcmp(cmd, "h")==0) {
+
+ icedemo_help_menu();
+
+ } else if (strcmp(cmd, "quit")==0 || strcmp(cmd, "q")==0) {
+
+ app_quit = PJ_TRUE;
+
+ } else {
+
+ printf("Invalid command '%s'\n", cmd);
+
+ }
+ }
+}
+
+
+/*
+ * Display program usage.
+ */
+static void icedemo_usage()
+{
+ puts("Usage: icedemo [optons]");
+ printf("icedemo v%s by pjsip.org\n", pj_get_version());
+ puts("");
+ puts("General options:");
+ puts(" --comp-cnt, -c N Component count (default=1)");
+ puts(" --nameserver, -n IP Configure nameserver to activate DNS SRV");
+ puts(" resolution");
+ puts(" --max-host, -H N Set max number of host candidates to N");
+ puts(" --regular, -R Use regular nomination (default aggressive)");
+ puts(" --log-file, -L FILE Save output to log FILE");
+ puts(" --help, -h Display this screen.");
+ puts("");
+ puts("STUN related options:");
+ puts(" --stun-srv, -s HOSTDOM Enable srflx candidate by resolving to STUN server.");
+ puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
+ puts(" name if DNS SRV resolution is used.");
+ puts("");
+ puts("TURN related options:");
+ puts(" --turn-srv, -t HOSTDOM Enable relayed candidate by using this TURN server.");
+ puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
+ puts(" name if DNS SRV resolution is used.");
+ puts(" --turn-tcp, -T Use TCP to connect to TURN server");
+ puts(" --turn-username, -u UID Set TURN username of the credential to UID");
+ puts(" --turn-password, -p PWD Set password of the credential to WPWD");
+ puts(" --turn-fingerprint, -F Use fingerprint for outgoing TURN requests");
+ puts("");
+}
+
+
+/*
+ * And here's the main()
+ */
+int main(int argc, char *argv[])
+{
+ struct pj_getopt_option long_options[] = {
+ { "comp-cnt", 1, 0, 'c'},
+ { "nameserver", 1, 0, 'n'},
+ { "max-host", 1, 0, 'H'},
+ { "help", 0, 0, 'h'},
+ { "stun-srv", 1, 0, 's'},
+ { "turn-srv", 1, 0, 't'},
+ { "turn-tcp", 0, 0, 'T'},
+ { "turn-username", 1, 0, 'u'},
+ { "turn-password", 1, 0, 'p'},
+ { "turn-fingerprint", 0, 0, 'F'},
+ { "regular", 0, 0, 'R'},
+ { "log-file", 1, 0, 'L'},
+ };
+ int c, opt_id;
+ pj_status_t status;
+
+ icedemo.opt.comp_cnt = 1;
+ icedemo.opt.max_host = -1;
+
+ while((c=pj_getopt_long(argc,argv, "c:n:s:t:u:p:H:L:hTFR", long_options, &opt_id))!=-1) {
+ switch (c) {
+ case 'c':
+ icedemo.opt.comp_cnt = atoi(pj_optarg);
+ if (icedemo.opt.comp_cnt < 1 || icedemo.opt.comp_cnt >= PJ_ICE_MAX_COMP) {
+ puts("Invalid component count value");
+ return 1;
+ }
+ break;
+ case 'n':
+ icedemo.opt.ns = pj_str(pj_optarg);
+ break;
+ case 'H':
+ icedemo.opt.max_host = atoi(pj_optarg);
+ break;
+ case 'h':
+ icedemo_usage();
+ return 0;
+ case 's':
+ icedemo.opt.stun_srv = pj_str(pj_optarg);
+ break;
+ case 't':
+ icedemo.opt.turn_srv = pj_str(pj_optarg);
+ break;
+ case 'T':
+ icedemo.opt.turn_tcp = PJ_TRUE;
+ break;
+ case 'u':
+ icedemo.opt.turn_username = pj_str(pj_optarg);
+ break;
+ case 'p':
+ icedemo.opt.turn_password = pj_str(pj_optarg);
+ break;
+ case 'F':
+ icedemo.opt.turn_fingerprint = PJ_TRUE;
+ break;
+ case 'R':
+ icedemo.opt.regular = PJ_TRUE;
+ break;
+ case 'L':
+ icedemo.opt.log_file = pj_optarg;
+ break;
+ default:
+ printf("Argument \"%s\" is not valid. Use -h to see help",
+ argv[pj_optind]);
+ return 1;
+ }
+ }
+
+ status = icedemo_init();
+ if (status != PJ_SUCCESS)
+ return 1;
+
+ icedemo_console();
+
+ err_exit("Quitting..", PJ_SUCCESS);
+ return 0;
+}
diff --git a/pjsip-apps/src/samples/invtester.c b/pjsip-apps/src/samples/invtester.c
new file mode 100644
index 0000000..a1eb413
--- /dev/null
+++ b/pjsip-apps/src/samples/invtester.c
@@ -0,0 +1,295 @@
+/* $Id: invtester.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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-apps/src/samples/jbsim.c b/pjsip-apps/src/samples/jbsim.c
new file mode 100644
index 0000000..510f67c
--- /dev/null
+++ b/pjsip-apps/src/samples/jbsim.c
@@ -0,0 +1,1141 @@
+/* $Id: jbsim.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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
+ */
+
+/* jbsim:
+
+ This program emulates various system and network impairment
+ conditions as well as application parameters and apply it to
+ an input WAV file. The output is another WAV file as well as
+ a detailed log file (in CSV format) for troubleshooting.
+ */
+
+
+/* Include PJMEDIA and PJLIB */
+#include <pjmedia.h>
+#include <pjmedia-codec.h>
+#include <pjlib.h>
+#include <pjlib-util.h>
+
+#define THIS_FILE "jbsim.c"
+
+/* Timer resolution in ms (must be NONZERO!) */
+#define WALL_CLOCK_TICK 1
+
+/* Defaults settings */
+#define CODEC "PCMU"
+#define LOG_FILE "jbsim.csv"
+#define WAV_REF "../../tests/pjsua/wavs/input.8.wav"
+#define WAV_OUT "jbsim.wav"
+#define DURATION 60
+#define DTX PJ_TRUE
+#define PLC PJ_TRUE
+#define MIN_LOST_BURST 0
+#define MAX_LOST_BURST 20
+#define LOSS_CORR 0
+#define LOSS_EXTRA 2
+#define SILENT 1
+
+/*
+ Test setup:
+
+ Input WAV --> TX Stream --> Loop transport --> RX Stream --> Out WAV
+ */
+
+/* Stream settings */
+struct stream_cfg
+{
+ const char *name; /* for logging purposes */
+ pjmedia_dir dir; /* stream direction */
+ pj_str_t codec; /* codec name */
+ unsigned ptime; /* zero for default */
+ pj_bool_t dtx; /* DTX enabled? */
+ pj_bool_t plc; /* PLC enabled? */
+};
+
+/* Stream instance. We will instantiate two streams, TX and RX */
+struct stream
+{
+ pj_pool_t *pool;
+ pjmedia_stream *strm;
+ pjmedia_port *port;
+
+ /*
+ * Running states:
+ */
+ union {
+ /* TX stream state */
+ struct {
+ pj_time_val next_schedule; /* Time to send next packet */
+ unsigned total_tx; /* # of TX packets so far */
+ int total_lost; /* # of dropped pkts so far */
+ unsigned cur_lost_burst; /* current # of lost bursts */
+ unsigned drop_prob; /* drop probability value */
+
+ } tx;
+
+ /* RX stream state */
+ struct {
+ pj_time_val next_schedule; /* Time to fetch next pkt */
+ } rx;
+ } state;
+};
+
+/*
+ * Logging
+ */
+
+/* Events names */
+#define EVENT_LOG ""
+#define EVENT_TX "TX/PUT"
+#define EVENT_TX_DROP "*** LOSS ***"
+#define EVENT_GET_PRE "GET (pre)"
+#define EVENT_GET_POST "GET (post)"
+
+
+/* Logging entry */
+struct log_entry
+{
+ pj_time_val wall_clock; /* Wall clock time */
+ const char *event; /* Event name */
+ pjmedia_jb_state *jb_state; /* JB state, optional */
+ pjmedia_rtcp_stat *stat; /* Stream stat, optional */
+ const char *log; /* Log message, optional */
+};
+
+/* Test settings, taken from command line */
+struct test_cfg
+{
+ /* General options */
+ pj_bool_t silent; /* Write little to stdout */
+ const char *log_file; /* The output log file */
+
+ /* Test settings */
+ pj_str_t codec; /* Codec to be used */
+ unsigned duration_msec; /* Test duration */
+
+ /* Transmitter setting */
+ const char *tx_wav_in; /* Input/reference WAV */
+ unsigned tx_ptime; /* TX stream ptime */
+ unsigned tx_min_jitter; /* Minimum jitter in ms */
+ unsigned tx_max_jitter; /* Max jitter in ms */
+ unsigned tx_dtx; /* DTX enabled? */
+ unsigned tx_pct_avg_lost; /* Average loss in percent */
+ unsigned tx_min_lost_burst; /* Min lost burst in #pkt */
+ unsigned tx_max_lost_burst; /* Max lost burst in #pkt */
+ unsigned tx_pct_loss_corr; /* Loss correlation in pct */
+
+ /* Receiver setting */
+ const char *rx_wav_out; /* Output WAV file */
+ unsigned rx_ptime; /* RX stream ptime */
+ unsigned rx_snd_burst; /* RX sound burst */
+ pj_bool_t rx_plc; /* RX PLC enabled? */
+ int rx_jb_init; /* if > 0 will enable prefetch (ms) */
+ int rx_jb_min_pre; /* JB minimum prefetch (ms) */
+ int rx_jb_max_pre; /* JB maximum prefetch (ms) */
+ int rx_jb_max; /* JB maximum size (ms) */
+};
+
+/*
+ * Global var
+ */
+struct global_app
+{
+ pj_caching_pool cp;
+ pj_pool_t *pool;
+ pj_int16_t *framebuf;
+ pjmedia_endpt *endpt;
+ pjmedia_transport *loop;
+
+ pj_oshandle_t log_fd;
+
+ struct test_cfg cfg;
+
+ struct stream *tx;
+ pjmedia_port *tx_wav;
+
+ struct stream *rx;
+ pjmedia_port *rx_wav;
+
+ pj_time_val wall_clock;
+};
+
+static struct global_app g_app;
+
+
+#ifndef MAX
+# define MAX(a,b) (a<b ? b : a)
+#endif
+
+#ifndef MIN
+# define MIN(a,b) (a<b ? a : b)
+#endif
+
+/*****************************************************************************
+ * Logging
+ */
+static void write_log(struct log_entry *entry, pj_bool_t to_stdout)
+{
+ /* Format (CSV): */
+ const char *format = "TIME;EVENT;#RX packets;#packets lost;#JB prefetch;#JB size;#JBDISCARD;#JBEMPTY;Log Message";
+ static char log[2000];
+ enum { D = 20 };
+ char s_jbprefetch[D],
+ s_jbsize[D],
+ s_rxpkt[D],
+ s_losspkt[D],
+ s_jbdiscard[D],
+ s_jbempty[D];
+ static pj_bool_t header_written;
+
+ if (!header_written) {
+ pj_ansi_snprintf(log, sizeof(log),
+ "%s\n", format);
+ if (g_app.log_fd != NULL) {
+ pj_ssize_t size = strlen(log);
+ pj_file_write(g_app.log_fd, log, &size);
+ }
+ if (to_stdout && !g_app.cfg.silent)
+ printf("%s", log);
+ header_written = PJ_TRUE;
+ }
+
+ if (entry->jb_state) {
+ sprintf(s_jbprefetch, "%d", entry->jb_state->prefetch);
+ sprintf(s_jbsize, "%d", entry->jb_state->size);
+ sprintf(s_jbdiscard, "%d", entry->jb_state->discard);
+ sprintf(s_jbempty, "%d", entry->jb_state->empty);
+ } else {
+ strcpy(s_jbprefetch, "");
+ strcpy(s_jbsize, "");
+ strcpy(s_jbdiscard, "");
+ strcpy(s_jbempty, "");
+ }
+
+ if (entry->stat) {
+ sprintf(s_rxpkt, "%d", entry->stat->rx.pkt);
+ sprintf(s_losspkt, "%d", entry->stat->rx.loss);
+ } else {
+ strcpy(s_rxpkt, "");
+ strcpy(s_losspkt, "");
+ }
+
+ if (entry->log == NULL)
+ entry->log = "";
+
+ pj_ansi_snprintf(log, sizeof(log),
+ "'%d.%03d;" /* time */
+ "%s;" /* event */
+ "%s;" /* rxpkt */
+ "%s;" /* jb prefetch */
+ "%s;" /* jbsize */
+ "%s;" /* losspkt */
+ "%s;" /* jbdiscard */
+ "%s;" /* jbempty */
+ "%s\n" /* logmsg */,
+
+ (int)entry->wall_clock.sec, (int)entry->wall_clock.msec, /* time */
+ entry->event,
+ s_rxpkt,
+ s_losspkt,
+ s_jbprefetch,
+ s_jbsize,
+ s_jbdiscard,
+ s_jbempty,
+ entry->log
+ );
+ if (g_app.log_fd != NULL) {
+ pj_ssize_t size = strlen(log);
+ pj_file_write(g_app.log_fd, log, &size);
+ }
+
+ if (to_stdout && !g_app.cfg.silent)
+ printf("%s", log);
+}
+
+static void log_cb(int level, const char *data, int len)
+{
+ struct log_entry entry;
+
+ /* Write to stdout */
+ pj_log_write(level, data, len);
+ puts("");
+
+ /* Also add to CSV file */
+ pj_bzero(&entry, sizeof(entry));
+ entry.event = EVENT_LOG;
+ entry.log = data;
+ entry.wall_clock = g_app.wall_clock;
+ write_log(&entry, PJ_FALSE);
+}
+
+static void jbsim_perror(const char *title, pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(1,(THIS_FILE, "%s: %s", title, errmsg));
+}
+
+/*****************************************************************************
+ * stream
+ */
+
+static void stream_destroy(struct stream *stream)
+{
+ if (stream->strm)
+ pjmedia_stream_destroy(stream->strm);
+ if (stream->pool)
+ pj_pool_release(stream->pool);
+}
+
+static pj_status_t stream_init(const struct stream_cfg *cfg, struct stream **p_stream)
+{
+ pj_pool_t *pool = NULL;
+ struct stream *stream = NULL;
+ pjmedia_codec_mgr *cm;
+ unsigned count;
+ const pjmedia_codec_info *ci;
+ pjmedia_stream_info si;
+ pj_status_t status;
+
+ /* Create instance */
+ pool = pj_pool_create(&g_app.cp.factory, cfg->name, 512, 512, NULL);
+ stream = PJ_POOL_ZALLOC_T(pool, struct stream);
+ stream->pool = pool;
+
+ /* Create stream info */
+ pj_bzero(&si, sizeof(si));
+ si.type = PJMEDIA_TYPE_AUDIO;
+ si.proto = PJMEDIA_TP_PROTO_RTP_AVP;
+ si.dir = cfg->dir;
+ pj_sockaddr_in_init(&si.rem_addr.ipv4, NULL, 4000); /* dummy */
+ pj_sockaddr_in_init(&si.rem_rtcp.ipv4, NULL, 4001); /* dummy */
+
+ /* Apply JB settings if this is RX direction */
+ if (cfg->dir == PJMEDIA_DIR_DECODING) {
+ si.jb_init = g_app.cfg.rx_jb_init;
+ si.jb_min_pre = g_app.cfg.rx_jb_min_pre;
+ si.jb_max_pre = g_app.cfg.rx_jb_max_pre;
+ si.jb_max = g_app.cfg.rx_jb_max;
+ }
+
+ /* Get the codec info and param */
+ cm = pjmedia_endpt_get_codec_mgr(g_app.endpt);
+ count = 1;
+ status = pjmedia_codec_mgr_find_codecs_by_id(cm, &cfg->codec, &count, &ci, NULL);
+ if (status != PJ_SUCCESS) {
+ jbsim_perror("Unable to find codec", status);
+ goto on_error;
+ }
+
+ pj_memcpy(&si.fmt, ci, sizeof(*ci));
+
+ si.param = PJ_POOL_ALLOC_T(pool, struct pjmedia_codec_param);
+ status = pjmedia_codec_mgr_get_default_param(cm, &si.fmt, si.param);
+ if (status != PJ_SUCCESS) {
+ jbsim_perror("Unable to get codec defaults", status);
+ goto on_error;
+ }
+
+ si.tx_pt = si.fmt.pt;
+
+ /* Apply ptime setting */
+ if (cfg->ptime) {
+ si.param->setting.frm_per_pkt = (pj_uint8_t)
+ ((cfg->ptime + si.param->info.frm_ptime - 1) /
+ si.param->info.frm_ptime);
+ }
+ /* Apply DTX setting */
+ si.param->setting.vad = cfg->dtx;
+
+ /* Apply PLC setting */
+ si.param->setting.plc = cfg->plc;
+
+ /* Create stream */
+ status = pjmedia_stream_create(g_app.endpt, pool, &si, g_app.loop, NULL, &stream->strm);
+ if (status != PJ_SUCCESS) {
+ jbsim_perror("Error creating stream", status);
+ goto on_error;
+ }
+
+ status = pjmedia_stream_get_port(stream->strm, &stream->port);
+ if (status != PJ_SUCCESS) {
+ jbsim_perror("Error retrieving stream", status);
+ goto on_error;
+ }
+
+ /* Start stream */
+ status = pjmedia_stream_start(stream->strm);
+ if (status != PJ_SUCCESS) {
+ jbsim_perror("Error starting stream", status);
+ goto on_error;
+ }
+
+ /* Done */
+ *p_stream = stream;
+ return PJ_SUCCESS;
+
+on_error:
+ if (stream) {
+ stream_destroy(stream);
+ } else {
+ if (pool)
+ pj_pool_release(pool);
+ }
+ return status;
+}
+
+
+/*****************************************************************************
+ * The test session
+ */
+static void test_destroy(void)
+{
+ if (g_app.tx)
+ stream_destroy(g_app.tx);
+ if (g_app.tx_wav)
+ pjmedia_port_destroy(g_app.tx_wav);
+ if (g_app.rx)
+ stream_destroy(g_app.rx);
+ if (g_app.rx_wav)
+ pjmedia_port_destroy(g_app.rx_wav);
+ if (g_app.loop)
+ pjmedia_transport_close(g_app.loop);
+ if (g_app.endpt)
+ pjmedia_endpt_destroy( g_app.endpt );
+ if (g_app.log_fd) {
+ pj_log_set_log_func(&pj_log_write);
+ pj_log_set_decor(pj_log_get_decor() | PJ_LOG_HAS_NEWLINE);
+ pj_file_close(g_app.log_fd);
+ g_app.log_fd = NULL;
+ }
+ if (g_app.pool)
+ pj_pool_release(g_app.pool);
+ pj_caching_pool_destroy( &g_app.cp );
+ pj_shutdown();
+}
+
+
+static pj_status_t test_init(void)
+{
+ struct stream_cfg strm_cfg;
+ pj_status_t status;
+
+ /* Must init PJLIB first: */
+ status = pj_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Must create a pool factory before we can allocate any memory. */
+ pj_caching_pool_init(&g_app.cp, &pj_pool_factory_default_policy, 0);
+
+ /* Pool */
+ g_app.pool = pj_pool_create(&g_app.cp.factory, "g_app", 512, 512, NULL);
+
+ /* Log file */
+ if (g_app.cfg.log_file) {
+ status = pj_file_open(g_app.pool, g_app.cfg.log_file,
+ PJ_O_WRONLY,
+ &g_app.log_fd);
+ if (status != PJ_SUCCESS) {
+ jbsim_perror("Error writing output file", status);
+ goto on_error;
+ }
+
+ pj_log_set_decor(PJ_LOG_HAS_SENDER | PJ_LOG_HAS_COLOR | PJ_LOG_HAS_LEVEL_TEXT);
+ pj_log_set_log_func(&log_cb);
+ }
+
+ /*
+ * Initialize media endpoint.
+ * This will implicitly initialize PJMEDIA too.
+ */
+ status = pjmedia_endpt_create(&g_app.cp.factory, NULL, 0, &g_app.endpt);
+ if (status != PJ_SUCCESS) {
+ jbsim_perror("Error creating media endpoint", status);
+ goto on_error;
+ }
+
+ /* Register codecs */
+ pjmedia_codec_register_audio_codecs(g_app.endpt, NULL);
+
+ /* Create the loop transport */
+ status = pjmedia_transport_loop_create(g_app.endpt, &g_app.loop);
+ if (status != PJ_SUCCESS) {
+ jbsim_perror("Error creating loop transport", status);
+ goto on_error;
+ }
+
+ /* Create transmitter stream */
+ pj_bzero(&strm_cfg, sizeof(strm_cfg));
+ strm_cfg.name = "tx";
+ strm_cfg.dir = PJMEDIA_DIR_ENCODING;
+ strm_cfg.codec = g_app.cfg.codec;
+ strm_cfg.ptime = g_app.cfg.tx_ptime;
+ strm_cfg.dtx = g_app.cfg.tx_dtx;
+ strm_cfg.plc = PJ_TRUE;
+ status = stream_init(&strm_cfg, &g_app.tx);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Create transmitter WAV */
+ status = pjmedia_wav_player_port_create(g_app.pool,
+ g_app.cfg.tx_wav_in,
+ g_app.cfg.tx_ptime,
+ 0,
+ 0,
+ &g_app.tx_wav);
+ if (status != PJ_SUCCESS) {
+ jbsim_perror("Error reading input WAV file", status);
+ goto on_error;
+ }
+
+ /* Make sure stream and WAV parameters match */
+ if (PJMEDIA_PIA_SRATE(&g_app.tx_wav->info) != PJMEDIA_PIA_SRATE(&g_app.tx->port->info) ||
+ PJMEDIA_PIA_CCNT(&g_app.tx_wav->info) != PJMEDIA_PIA_CCNT(&g_app.tx->port->info))
+ {
+ jbsim_perror("Error: Input WAV file has different clock rate "
+ "or number of channels than the codec", PJ_SUCCESS);
+ goto on_error;
+ }
+
+
+ /* Create receiver */
+ pj_bzero(&strm_cfg, sizeof(strm_cfg));
+ strm_cfg.name = "rx";
+ strm_cfg.dir = PJMEDIA_DIR_DECODING;
+ strm_cfg.codec = g_app.cfg.codec;
+ strm_cfg.ptime = g_app.cfg.rx_ptime;
+ strm_cfg.dtx = PJ_TRUE;
+ strm_cfg.plc = g_app.cfg.rx_plc;
+ status = stream_init(&strm_cfg, &g_app.rx);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Create receiver WAV */
+ status = pjmedia_wav_writer_port_create(g_app.pool,
+ g_app.cfg.rx_wav_out,
+ PJMEDIA_PIA_SRATE(&g_app.rx->port->info),
+ PJMEDIA_PIA_CCNT(&g_app.rx->port->info),
+ PJMEDIA_PIA_SPF(&g_app.rx->port->info),
+ PJMEDIA_PIA_BITS(&g_app.rx->port->info),
+ 0,
+ 0,
+ &g_app.rx_wav);
+ if (status != PJ_SUCCESS) {
+ jbsim_perror("Error creating output WAV file", status);
+ goto on_error;
+ }
+
+
+ /* Frame buffer */
+ g_app.framebuf = (pj_int16_t*)
+ pj_pool_alloc(g_app.pool,
+ MAX(PJMEDIA_PIA_SPF(&g_app.rx->port->info),
+ PJMEDIA_PIA_SPF(&g_app.tx->port->info)) * sizeof(pj_int16_t));
+
+
+ /* Set the receiver in the loop transport */
+ pjmedia_transport_loop_disable_rx(g_app.loop, g_app.tx->strm, PJ_TRUE);
+
+ /* Done */
+ return PJ_SUCCESS;
+
+on_error:
+ test_destroy();
+ return status;
+}
+
+static void run_one_frame(pjmedia_port *src, pjmedia_port *dst,
+ pj_bool_t *has_frame)
+{
+ pjmedia_frame frame;
+ pj_status_t status;
+
+ pj_bzero(&frame, sizeof(frame));
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ frame.buf = g_app.framebuf;
+ frame.size = PJMEDIA_PIA_SPF(&dst->info) * 2;
+
+ status = pjmedia_port_get_frame(src, &frame);
+ pj_assert(status == PJ_SUCCESS);
+
+ if (status!= PJ_SUCCESS || frame.type != PJMEDIA_FRAME_TYPE_AUDIO) {
+ frame.buf = g_app.framebuf;
+ pjmedia_zero_samples(g_app.framebuf, PJMEDIA_PIA_SPF(&src->info));
+ frame.size = PJMEDIA_PIA_SPF(&src->info) * 2;
+ if (has_frame)
+ *has_frame = PJ_FALSE;
+ } else {
+ if (has_frame)
+ *has_frame = PJ_TRUE;
+ }
+
+
+ status = pjmedia_port_put_frame(dst, &frame);
+ pj_assert(status == PJ_SUCCESS);
+}
+
+
+/* This is the transmission "tick".
+ * This function is called periodically every "tick" milliseconds, and
+ * it will determine whether to transmit packet(s) (or to drop it).
+ */
+static void tx_tick(const pj_time_val *t)
+{
+ struct stream *strm = g_app.tx;
+ static char log_msg[120];
+ pjmedia_port *port = g_app.tx->port;
+ long pkt_interval;
+
+ /* packet interval, without jitter */
+ pkt_interval = PJMEDIA_PIA_SPF(&port->info) * 1000 /
+ PJMEDIA_PIA_SRATE(&port->info);
+
+ while (PJ_TIME_VAL_GTE(*t, strm->state.tx.next_schedule)) {
+ struct log_entry entry;
+ pj_bool_t drop_this_pkt = PJ_FALSE;
+ int jitter;
+
+ /* Init log entry */
+ pj_bzero(&entry, sizeof(entry));
+ entry.wall_clock = *t;
+
+ /*
+ * Determine whether to drop this packet
+ */
+ if (strm->state.tx.cur_lost_burst) {
+ /* We are currently dropping packet */
+
+ /* Make it comply to minimum lost burst */
+ if (strm->state.tx.cur_lost_burst < g_app.cfg.tx_min_lost_burst) {
+ drop_this_pkt = PJ_TRUE;
+ }
+
+ /* Correlate the next packet loss */
+ if (!drop_this_pkt &&
+ strm->state.tx.cur_lost_burst < g_app.cfg.tx_max_lost_burst &&
+ MAX(strm->state.tx.total_lost-LOSS_EXTRA,0) * 100 / MAX(strm->state.tx.total_tx,1) < g_app.cfg.tx_pct_avg_lost
+ )
+ {
+ strm->state.tx.drop_prob = ((g_app.cfg.tx_pct_loss_corr * strm->state.tx.drop_prob) +
+ ((100-g_app.cfg.tx_pct_loss_corr) * (pj_rand()%100))
+ ) / 100;
+ if (strm->state.tx.drop_prob >= 100)
+ strm->state.tx.drop_prob = 99;
+
+ if (strm->state.tx.drop_prob >= 100 - g_app.cfg.tx_pct_avg_lost)
+ drop_this_pkt = PJ_TRUE;
+ }
+ }
+
+ /* If we're not dropping packet then use randomly distributed loss */
+ if (!drop_this_pkt &&
+ MAX(strm->state.tx.total_lost-LOSS_EXTRA,0) * 100 / MAX(strm->state.tx.total_tx,1) < g_app.cfg.tx_pct_avg_lost)
+ {
+ strm->state.tx.drop_prob = pj_rand() % 100;
+
+ if (strm->state.tx.drop_prob >= 100 - g_app.cfg.tx_pct_avg_lost)
+ drop_this_pkt = PJ_TRUE;
+ }
+
+ if (drop_this_pkt) {
+ /* Drop the frame */
+ pjmedia_transport_simulate_lost(g_app.loop, PJMEDIA_DIR_ENCODING, 100);
+ run_one_frame(g_app.tx_wav, g_app.tx->port, NULL);
+ pjmedia_transport_simulate_lost(g_app.loop, PJMEDIA_DIR_ENCODING, 0);
+
+ entry.event = EVENT_TX_DROP;
+ entry.log = "** This packet was lost **";
+
+ ++strm->state.tx.total_lost;
+ ++strm->state.tx.cur_lost_burst;
+
+ } else {
+ pjmedia_rtcp_stat stat;
+ pjmedia_jb_state jstate;
+ unsigned last_discard;
+
+ pjmedia_stream_get_stat_jbuf(g_app.rx->strm, &jstate);
+ last_discard = jstate.discard;
+
+ run_one_frame(g_app.tx_wav, g_app.tx->port, NULL);
+
+ pjmedia_stream_get_stat(g_app.rx->strm, &stat);
+ pjmedia_stream_get_stat_jbuf(g_app.rx->strm, &jstate);
+
+ entry.event = EVENT_TX;
+ entry.jb_state = &jstate;
+ entry.stat = &stat;
+ entry.log = log_msg;
+
+ if (jstate.discard > last_discard)
+ strcat(log_msg, "** Note: packet was discarded by jitter buffer **");
+
+ strm->state.tx.cur_lost_burst = 0;
+ }
+
+ write_log(&entry, PJ_TRUE);
+
+ ++strm->state.tx.total_tx;
+
+ /* Calculate next schedule */
+ strm->state.tx.next_schedule.sec = 0;
+ strm->state.tx.next_schedule.msec = (strm->state.tx.total_tx + 1) * pkt_interval;
+
+ /* Apply jitter */
+ if (g_app.cfg.tx_max_jitter || g_app.cfg.tx_min_jitter) {
+
+ if (g_app.cfg.tx_max_jitter == g_app.cfg.tx_min_jitter) {
+ /* Fixed jitter */
+ switch (pj_rand() % 3) {
+ case 0:
+ jitter = 0 - g_app.cfg.tx_min_jitter;
+ break;
+ case 2:
+ jitter = g_app.cfg.tx_min_jitter;
+ break;
+ default:
+ jitter = 0;
+ break;
+ }
+ } else {
+ int jitter_range;
+ jitter_range = (g_app.cfg.tx_max_jitter-g_app.cfg.tx_min_jitter)*2;
+ jitter = pj_rand() % jitter_range;
+ if (jitter < jitter_range/2) {
+ jitter = 0 - g_app.cfg.tx_min_jitter - (jitter/2);
+ } else {
+ jitter = g_app.cfg.tx_min_jitter + (jitter/2);
+ }
+ }
+
+ } else {
+ jitter = 0;
+ }
+
+ pj_time_val_normalize(&strm->state.tx.next_schedule);
+
+ sprintf(log_msg, "** Packet #%u tick is at %d.%03d, %d ms jitter applied **",
+ strm->state.tx.total_tx+1,
+ (int)strm->state.tx.next_schedule.sec, (int)strm->state.tx.next_schedule.msec,
+ jitter);
+
+ strm->state.tx.next_schedule.msec += jitter;
+ pj_time_val_normalize(&strm->state.tx.next_schedule);
+
+ } /* while */
+}
+
+
+/* This is the RX "tick".
+ * This function is called periodically every "tick" milliseconds, and
+ * it will determine whether to call get_frame() from the RX stream.
+ */
+static void rx_tick(const pj_time_val *t)
+{
+ struct stream *strm = g_app.rx;
+ pjmedia_port *port = g_app.rx->port;
+ long pkt_interval;
+
+ pkt_interval = PJMEDIA_PIA_SPF(&port->info) * 1000 /
+ PJMEDIA_PIA_SRATE(&port->info) *
+ g_app.cfg.rx_snd_burst;
+
+ if (PJ_TIME_VAL_GTE(*t, strm->state.rx.next_schedule)) {
+ unsigned i;
+ for (i=0; i<g_app.cfg.rx_snd_burst; ++i) {
+ struct log_entry entry;
+ pjmedia_rtcp_stat stat;
+ pjmedia_jb_state jstate;
+ pj_bool_t has_frame;
+ char msg[120];
+ unsigned last_empty;
+
+ pjmedia_stream_get_stat(g_app.rx->strm, &stat);
+ pjmedia_stream_get_stat_jbuf(g_app.rx->strm, &jstate);
+ last_empty = jstate.empty;
+
+ /* Pre GET event */
+ pj_bzero(&entry, sizeof(entry));
+ entry.event = EVENT_GET_PRE;
+ entry.wall_clock = *t;
+ entry.stat = &stat;
+ entry.jb_state = &jstate;
+
+ write_log(&entry, PJ_TRUE);
+
+ /* GET */
+ run_one_frame(g_app.rx->port, g_app.rx_wav, &has_frame);
+
+ /* Post GET event */
+ pjmedia_stream_get_stat(g_app.rx->strm, &stat);
+ pjmedia_stream_get_stat_jbuf(g_app.rx->strm, &jstate);
+
+ pj_bzero(&entry, sizeof(entry));
+ entry.event = EVENT_GET_POST;
+ entry.wall_clock = *t;
+ entry.stat = &stat;
+ entry.jb_state = &jstate;
+
+ msg[0] = '\0';
+ entry.log = msg;
+
+ if (jstate.empty > last_empty)
+ strcat(msg, "** JBUF was empty **");
+ if (!has_frame)
+ strcat(msg, "** NULL frame was returned **");
+
+ write_log(&entry, PJ_TRUE);
+
+ }
+
+
+ strm->state.rx.next_schedule.msec += pkt_interval;
+ pj_time_val_normalize(&strm->state.rx.next_schedule);
+ }
+
+}
+
+static void test_loop(long duration)
+{
+ g_app.wall_clock.sec = 0;
+ g_app.wall_clock.msec = 0;
+
+ while (PJ_TIME_VAL_MSEC(g_app.wall_clock) <= duration) {
+
+ /* Run TX tick */
+ tx_tick(&g_app.wall_clock);
+
+ /* Run RX tick */
+ rx_tick(&g_app.wall_clock);
+
+ /* Increment tick */
+ g_app.wall_clock.msec += WALL_CLOCK_TICK;
+ pj_time_val_normalize(&g_app.wall_clock);
+ }
+}
+
+
+/*****************************************************************************
+ * usage()
+ */
+enum {
+ OPT_CODEC = 'c',
+ OPT_INPUT = 'i',
+ OPT_OUTPUT = 'o',
+ OPT_DURATION = 'd',
+ OPT_LOG_FILE = 'l',
+ OPT_LOSS = 'x',
+ OPT_MIN_JITTER = 'j',
+ OPT_MAX_JITTER = 'J',
+ OPT_SND_BURST = 'b',
+ OPT_TX_PTIME = 't',
+ OPT_RX_PTIME = 'r',
+ OPT_NO_VAD = 'U',
+ OPT_NO_PLC = 'p',
+ OPT_JB_PREFETCH = 'P',
+ OPT_JB_MIN_PRE = 'm',
+ OPT_JB_MAX_PRE = 'M',
+ OPT_JB_MAX = 'X',
+ OPT_HELP = 'h',
+ OPT_MIN_LOST_BURST = 1,
+ OPT_MAX_LOST_BURST,
+ OPT_LOSS_CORR,
+};
+
+
+static void usage(void)
+{
+ printf("jbsim - System and network impairments simulator\n");
+ printf("Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)\n");
+ printf("\n");
+ printf("This program emulates various system and network impairment\n");
+ printf("conditions as well as application parameters and apply it to\n");
+ printf("an input WAV file. The output is another WAV file as well as\n");
+ printf("a detailed log file (in CSV format) for troubleshooting.\n");
+ printf("\n");
+ printf("Usage:\n");
+ printf(" jbsim [OPTIONS]\n");
+ printf("\n");
+ printf("General OPTIONS:\n");
+ printf(" --codec, -%c NAME Set the audio codec\n", OPT_CODEC);
+ printf(" Default: %s\n", CODEC);
+ printf(" --input, -%c FILE Set WAV reference file to FILE\n", OPT_INPUT);
+ printf(" Default: " WAV_REF "\n");
+ printf(" --output, -%c FILE Set WAV output file to FILE\n", OPT_OUTPUT);
+ printf(" Default: " WAV_OUT "\n");
+ printf(" --duration, -%c SEC Set test duration to SEC seconds\n", OPT_DURATION);
+ printf(" Default: %d\n", DURATION);
+ printf(" --log-file, -%c FILE Save simulation log file to FILE\n", OPT_LOG_FILE);
+ printf(" Note: FILE will be in CSV format with semicolon separator\n");
+ printf(" Default: %s\n", LOG_FILE);
+ printf(" --help, -h Display this screen\n");
+ printf("\n");
+ printf("Simulation OPTIONS:\n");
+ printf(" --loss, -%c PCT Set packet average loss to PCT percent\n", OPT_LOSS);
+ printf(" Default: 0\n");
+ printf(" --loss-corr PCT Set the loss correlation to PCT percent. Default: 0\n");
+ printf(" --min-lost-burst N Set minimum packet lost burst (default:%d)\n", MIN_LOST_BURST);
+ printf(" --max-lost-burst N Set maximum packet lost burst (default:%d)\n", MAX_LOST_BURST);
+ printf(" --min-jitter, -%c MSEC Set minimum network jitter to MSEC\n", OPT_MIN_JITTER);
+ printf(" Default: 0\n");
+ printf(" --max-jitter, -%c MSEC Set maximum network jitter to MSEC\n", OPT_MAX_JITTER);
+ printf(" Default: 0\n");
+ printf(" --snd-burst, -%c VAL Set RX sound burst value to VAL frames.\n", OPT_SND_BURST);
+ printf(" Default: 1\n");
+ printf(" --tx-ptime, -%c MSEC Set transmitter ptime to MSEC\n", OPT_TX_PTIME);
+ printf(" Default: 0 (not set, use default)\n");
+ printf(" --rx-ptime, -%c MSEC Set receiver ptime to MSEC\n", OPT_RX_PTIME);
+ printf(" Default: 0 (not set, use default)\n");
+ printf(" --no-vad, -%c Disable VAD/DTX in transmitter\n", OPT_NO_VAD);
+ printf(" --no-plc, -%c Disable PLC in receiver\n", OPT_NO_PLC);
+ printf(" --jb-prefetch, -%c Enable prefetch bufferring in jitter buffer\n", OPT_JB_PREFETCH);
+ printf(" --jb-min-pre, -%c MSEC Jitter buffer minimum prefetch delay in msec\n", OPT_JB_MIN_PRE);
+ printf(" --jb-max-pre, -%c MSEC Jitter buffer maximum prefetch delay in msec\n", OPT_JB_MAX_PRE);
+ printf(" --jb-max, -%c MSEC Set maximum delay that can be accomodated by the\n", OPT_JB_MAX);
+ printf(" jitter buffer msec.\n");
+}
+
+
+static int init_options(int argc, char *argv[])
+{
+ struct pj_getopt_option long_options[] = {
+ { "codec", 1, 0, OPT_CODEC },
+ { "input", 1, 0, OPT_INPUT },
+ { "output", 1, 0, OPT_OUTPUT },
+ { "duration", 1, 0, OPT_DURATION },
+ { "log-file", 1, 0, OPT_LOG_FILE},
+ { "loss", 1, 0, OPT_LOSS },
+ { "min-lost-burst", 1, 0, OPT_MIN_LOST_BURST},
+ { "max-lost-burst", 1, 0, OPT_MAX_LOST_BURST},
+ { "loss-corr", 1, 0, OPT_LOSS_CORR},
+ { "min-jitter", 1, 0, OPT_MIN_JITTER },
+ { "max-jitter", 1, 0, OPT_MAX_JITTER },
+ { "snd-burst", 1, 0, OPT_SND_BURST },
+ { "tx-ptime", 1, 0, OPT_TX_PTIME },
+ { "rx-ptime", 1, 0, OPT_RX_PTIME },
+ { "no-vad", 0, 0, OPT_NO_VAD },
+ { "no-plc", 0, 0, OPT_NO_PLC },
+ { "jb-prefetch", 0, 0, OPT_JB_PREFETCH },
+ { "jb-min-pre", 1, 0, OPT_JB_MIN_PRE },
+ { "jb-max-pre", 1, 0, OPT_JB_MAX_PRE },
+ { "jb-max", 1, 0, OPT_JB_MAX },
+ { "help", 0, 0, OPT_HELP},
+ { NULL, 0, 0, 0 },
+ };
+ int c;
+ int option_index;
+ char format[128];
+
+ /* Init default config */
+ g_app.cfg.codec = pj_str(CODEC);
+ g_app.cfg.duration_msec = DURATION * 1000;
+ g_app.cfg.silent = SILENT;
+ g_app.cfg.log_file = LOG_FILE;
+ g_app.cfg.tx_wav_in = WAV_REF;
+ g_app.cfg.tx_ptime = 0;
+ g_app.cfg.tx_min_jitter = 0;
+ g_app.cfg.tx_max_jitter = 0;
+ g_app.cfg.tx_dtx = DTX;
+ g_app.cfg.tx_pct_avg_lost = 0;
+ g_app.cfg.tx_min_lost_burst = MIN_LOST_BURST;
+ g_app.cfg.tx_max_lost_burst = MAX_LOST_BURST;
+ g_app.cfg.tx_pct_loss_corr = LOSS_CORR;
+
+ g_app.cfg.rx_wav_out = WAV_OUT;
+ g_app.cfg.rx_ptime = 0;
+ g_app.cfg.rx_plc = PLC;
+ g_app.cfg.rx_snd_burst = 1;
+ g_app.cfg.rx_jb_init = -1;
+ g_app.cfg.rx_jb_min_pre = -1;
+ g_app.cfg.rx_jb_max_pre = -1;
+ g_app.cfg.rx_jb_max = -1;
+
+ /* Build format */
+ format[0] = '\0';
+ for (c=0; c<PJ_ARRAY_SIZE(long_options)-1; ++c) {
+ if (long_options[c].has_arg) {
+ char cmd[10];
+ pj_ansi_snprintf(cmd, sizeof(cmd), "%c:", long_options[c].val);
+ pj_ansi_strcat(format, cmd);
+ }
+ }
+ for (c=0; c<PJ_ARRAY_SIZE(long_options)-1; ++c) {
+ if (long_options[c].has_arg == 0) {
+ char cmd[10];
+ pj_ansi_snprintf(cmd, sizeof(cmd), "%c", long_options[c].val);
+ pj_ansi_strcat(format, cmd);
+ }
+ }
+
+ /* Parse options */
+ pj_optind = 0;
+ while((c=pj_getopt_long(argc,argv, format,
+ long_options, &option_index))!=-1)
+ {
+ switch (c) {
+ case OPT_CODEC:
+ g_app.cfg.codec = pj_str(pj_optarg);
+ break;
+ case OPT_INPUT:
+ g_app.cfg.tx_wav_in = pj_optarg;
+ break;
+ case OPT_OUTPUT:
+ g_app.cfg.rx_wav_out = pj_optarg;
+ break;
+ case OPT_DURATION:
+ g_app.cfg.duration_msec = atoi(pj_optarg) * 1000;
+ break;
+ case OPT_LOG_FILE:
+ g_app.cfg.log_file = pj_optarg;
+ break;
+ case OPT_LOSS:
+ g_app.cfg.tx_pct_avg_lost = atoi(pj_optarg);
+ if (g_app.cfg.tx_pct_avg_lost > 100) {
+ puts("Error: Invalid loss value?");
+ return 1;
+ }
+ break;
+ case OPT_MIN_LOST_BURST:
+ g_app.cfg.tx_min_lost_burst = atoi(pj_optarg);
+ break;
+ case OPT_MAX_LOST_BURST:
+ g_app.cfg.tx_max_lost_burst = atoi(pj_optarg);
+ break;
+ case OPT_LOSS_CORR:
+ g_app.cfg.tx_pct_loss_corr = atoi(pj_optarg);
+ if (g_app.cfg.tx_pct_avg_lost > 100) {
+ puts("Error: Loss correlation is in percentage, value is not valid?");
+ return 1;
+ }
+ break;
+ case OPT_MIN_JITTER:
+ g_app.cfg.tx_min_jitter = atoi(pj_optarg);
+ break;
+ case OPT_MAX_JITTER:
+ g_app.cfg.tx_max_jitter = atoi(pj_optarg);
+ break;
+ case OPT_SND_BURST:
+ g_app.cfg.rx_snd_burst = atoi(pj_optarg);
+ break;
+ case OPT_TX_PTIME:
+ g_app.cfg.tx_ptime = atoi(pj_optarg);
+ break;
+ case OPT_RX_PTIME:
+ g_app.cfg.rx_ptime = atoi(pj_optarg);
+ break;
+ case OPT_NO_VAD:
+ g_app.cfg.tx_dtx = PJ_FALSE;
+ break;
+ case OPT_NO_PLC:
+ g_app.cfg.rx_plc = PJ_FALSE;
+ break;
+ case OPT_JB_PREFETCH:
+ g_app.cfg.rx_jb_init = 1;
+ break;
+ case OPT_JB_MIN_PRE:
+ g_app.cfg.rx_jb_min_pre = atoi(pj_optarg);
+ break;
+ case OPT_JB_MAX_PRE:
+ g_app.cfg.rx_jb_max_pre = atoi(pj_optarg);
+ break;
+ case OPT_JB_MAX:
+ g_app.cfg.rx_jb_max = atoi(pj_optarg);
+ break;
+ case OPT_HELP:
+ usage();
+ return 1;
+ default:
+ usage();
+ return 1;
+ }
+ }
+
+ /* Check for orphaned params */
+ if (pj_optind < argc) {
+ usage();
+ return 1;
+ }
+
+ /* Normalize options */
+ if (g_app.cfg.rx_jb_init < g_app.cfg.rx_jb_min_pre)
+ g_app.cfg.rx_jb_init = g_app.cfg.rx_jb_min_pre;
+ else if (g_app.cfg.rx_jb_init > g_app.cfg.rx_jb_max_pre)
+ g_app.cfg.rx_jb_init = g_app.cfg.rx_jb_max_pre;
+
+ if (g_app.cfg.tx_max_jitter < g_app.cfg.tx_min_jitter)
+ g_app.cfg.tx_max_jitter = g_app.cfg.tx_min_jitter;
+ return 0;
+}
+
+/*****************************************************************************
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+ pj_status_t status;
+
+ if (init_options(argc, argv) != 0)
+ return 1;
+
+
+ /* Init */
+ status = test_init();
+ if (status != PJ_SUCCESS)
+ return 1;
+
+ /* Print parameters */
+ PJ_LOG(3,(THIS_FILE, "Starting simulation. Parameters: "));
+ PJ_LOG(3,(THIS_FILE, " Codec=%.*s, tx_ptime=%d, rx_ptime=%d",
+ (int)g_app.cfg.codec.slen,
+ g_app.cfg.codec.ptr,
+ g_app.cfg.tx_ptime,
+ g_app.cfg.rx_ptime));
+ PJ_LOG(3,(THIS_FILE, " Loss avg=%d%%, min_burst=%d, max_burst=%d",
+ g_app.cfg.tx_pct_avg_lost,
+ g_app.cfg.tx_min_lost_burst,
+ g_app.cfg.tx_max_lost_burst));
+ PJ_LOG(3,(THIS_FILE, " TX jitter min=%dms, max=%dms",
+ g_app.cfg.tx_min_jitter,
+ g_app.cfg.tx_max_jitter));
+ PJ_LOG(3,(THIS_FILE, " RX jb init:%dms, min_pre=%dms, max_pre=%dms, max=%dms",
+ g_app.cfg.rx_jb_init,
+ g_app.cfg.rx_jb_min_pre,
+ g_app.cfg.rx_jb_max_pre,
+ g_app.cfg.rx_jb_max));
+ PJ_LOG(3,(THIS_FILE, " RX sound burst:%d frames",
+ g_app.cfg.rx_snd_burst));
+ PJ_LOG(3,(THIS_FILE, " DTX=%d, PLC=%d",
+ g_app.cfg.tx_dtx, g_app.cfg.rx_plc));
+
+ /* Run test loop */
+ test_loop(g_app.cfg.duration_msec);
+
+ /* Print statistics */
+ PJ_LOG(3,(THIS_FILE, "Simulation done"));
+ PJ_LOG(3,(THIS_FILE, " TX packets=%u, dropped=%u/%5.1f%%",
+ g_app.tx->state.tx.total_tx,
+ g_app.tx->state.tx.total_lost,
+ (float)(g_app.tx->state.tx.total_lost * 100.0 / g_app.tx->state.tx.total_tx)));
+
+ /* Done */
+ test_destroy();
+
+ return 0;
+}
diff --git a/pjsip-apps/src/samples/latency.c b/pjsip-apps/src/samples/latency.c
new file mode 100644
index 0000000..6193e61
--- /dev/null
+++ b/pjsip-apps/src/samples/latency.c
@@ -0,0 +1,202 @@
+/* $Id: latency.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+/* See http://trac.pjsip.org/repos/wiki/MeasuringSoundLatency on
+ * how to use this program.
+ */
+
+#include <pjmedia.h>
+#include <pjlib.h>
+
+#include <stdio.h>
+
+#define THIS_FILE "lacency.c"
+
+
+/* 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)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ PJ_UNUSED_ARG(sender);
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ printf("%s: %s [code=%d]\n", title, errmsg, status);
+ return 1;
+}
+
+/*
+ * Find out latency
+ */
+static int calculate_latency(pj_pool_t *pool, pjmedia_port *wav)
+{
+ pjmedia_frame frm;
+ short *buf;
+ unsigned i, samples_per_frame, read, len;
+ unsigned start_pos;
+ pj_status_t status;
+
+ unsigned lat_sum = 0,
+ lat_cnt = 0,
+ lat_min = 10000,
+ lat_max = 0;
+
+ samples_per_frame = PJMEDIA_PIA_SPF(&wav->info);
+ frm.buf = pj_pool_alloc(pool, samples_per_frame * 2);
+ frm.size = samples_per_frame * 2;
+ len = pjmedia_wav_player_get_len(wav);
+ buf = pj_pool_alloc(pool, len + samples_per_frame);
+
+ read = 0;
+ while (read < len/2) {
+ status = pjmedia_port_get_frame(wav, &frm);
+ if (status != PJ_SUCCESS)
+ break;
+
+ pjmedia_copy_samples(buf+read, (short*)frm.buf, samples_per_frame);
+ read += samples_per_frame;
+ }
+
+ if (read < 2 * PJMEDIA_PIA_SRATE(&wav->info)) {
+ puts("Error: too short");
+ return -1;
+ }
+
+ start_pos = 0;
+ while (start_pos < len/2 - PJMEDIA_PIA_SRATE(&wav->info)) {
+ int max_signal = 0;
+ unsigned max_signal_pos = start_pos;
+ unsigned max_echo_pos = 0;
+ unsigned pos;
+ unsigned lat;
+
+ /* Get the largest signal in the next 0.7s */
+ for (i=start_pos; i<start_pos + PJMEDIA_PIA_SRATE(&wav->info) * 700 / 1000; ++i) {
+ if (abs(buf[i]) > max_signal) {
+ max_signal = abs(buf[i]);
+ max_signal_pos = i;
+ }
+ }
+
+ /* Advance 10ms from max_signal_pos */
+ pos = max_signal_pos + 10 * PJMEDIA_PIA_SRATE(&wav->info) / 1000;
+
+ /* Get the largest signal in the next 500ms */
+ max_signal = 0;
+ max_echo_pos = pos;
+ for (i=pos; i<pos+PJMEDIA_PIA_SRATE(&wav->info)/2; ++i) {
+ if (abs(buf[i]) > max_signal) {
+ max_signal = abs(buf[i]);
+ max_echo_pos = i;
+ }
+ }
+
+ lat = (max_echo_pos - max_signal_pos) * 1000 / PJMEDIA_PIA_SRATE(&wav->info);
+
+#if 0
+ printf("Latency = %u\n", lat);
+#endif
+
+ lat_sum += lat;
+ lat_cnt++;
+ if (lat < lat_min)
+ lat_min = lat;
+ if (lat > lat_max)
+ lat_max = lat;
+
+ /* Advance next loop */
+ start_pos += PJMEDIA_PIA_SRATE(&wav->info);
+ }
+
+ printf("Latency average = %u\n", lat_sum / lat_cnt);
+ printf("Latency minimum = %u\n", lat_min);
+ printf("Latency maximum = %u\n", lat_max);
+ printf("Number of data = %u\n", lat_cnt);
+ return 0;
+}
+
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+ enum { NSAMPLES = 160, COUNT=100 };
+ pj_caching_pool cp;
+ pj_pool_t *pool;
+ pjmedia_port *wav;
+ pj_status_t status;
+
+
+ /* Verify cmd line arguments. */
+ if (argc != 2) {
+ puts("Error: missing argument(s)");
+ puts("Usage: latency REV.WAV");
+ return 1;
+ }
+
+ pj_log_set_level(0);
+
+ status = pj_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+
+ pool = pj_pool_create( &cp.factory, /* pool factory */
+ "wav", /* pool name. */
+ 4000, /* init size */
+ 4000, /* increment size */
+ NULL /* callback on error */
+ );
+
+ status = pj_register_strerror(PJMEDIA_ERRNO_START, PJ_ERRNO_SPACE_SIZE,
+ &pjmedia_strerror);
+ pj_assert(status == PJ_SUCCESS);
+
+ /* Wav */
+ status = pjmedia_wav_player_port_create( pool, /* memory pool */
+ argv[1], /* file to play */
+ 0, /* use default ptime*/
+ 0, /* flags */
+ 0, /* default buffer */
+ &wav /* returned port */
+ );
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, argv[1], status);
+ return 1;
+ }
+
+ status = calculate_latency(pool, wav);
+ if (status != PJ_SUCCESS)
+ return 1;
+
+ status = pjmedia_port_destroy( wav );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ pj_pool_release( pool );
+ pj_caching_pool_destroy( &cp );
+ pj_shutdown();
+
+ /* Done. */
+ return 0;
+}
+
diff --git a/pjsip-apps/src/samples/level.c b/pjsip-apps/src/samples/level.c
new file mode 100644
index 0000000..59bbb96
--- /dev/null
+++ b/pjsip-apps/src/samples/level.c
@@ -0,0 +1,179 @@
+/* $Id: level.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+
+/**
+ * \page page_pjmedia_samples_level_c Samples: Reading from WAV File
+ *
+ * This is a very simple example to use the @ref PJMEDIA_FILE_PLAY, to
+ * directly read the samples from the file.
+ *
+ * This file is pjsip-apps/src/samples/level.c
+ *
+ * \includelineno level.c
+ */
+
+
+static const char *desc =
+ " FILE: \n"
+ " level.c \n"
+ " \n"
+ " PURPOSE: \n"
+ " Read PCM WAV file and display the audio level the first 100 frames. \n"
+ " Each frame is assumed to have 160 samples. \n"
+ " \n"
+ " USAGE: \n"
+ " level file.wav \n"
+ " \n"
+ " The WAV file SHOULD have a 16bit mono samples. ";
+
+#include <pjmedia.h>
+#include <pjlib.h>
+
+#include <stdio.h>
+
+/* For logging purpose. */
+#define THIS_FILE "level.c"
+
+
+/* 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)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ PJ_UNUSED_ARG(sender);
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ printf("%s: %s [code=%d]\n", title, errmsg, status);
+ return 1;
+}
+
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+ enum { NSAMPLES = 640, COUNT=100 };
+ pj_caching_pool cp;
+ pjmedia_endpt *med_endpt;
+ pj_pool_t *pool;
+ pjmedia_port *file_port;
+ int i;
+ pj_status_t status;
+
+
+ /* Verify cmd line arguments. */
+ if (argc != 2) {
+ puts("");
+ puts(desc);
+ return 1;
+ }
+
+ /* Must init PJLIB first: */
+ status = pj_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);
+
+ /*
+ * Initialize media endpoint.
+ * This will implicitly initialize PJMEDIA too.
+ */
+ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Create memory pool for our file player */
+ pool = pj_pool_create( &cp.factory, /* pool factory */
+ "wav", /* pool name. */
+ 4000, /* init size */
+ 4000, /* increment size */
+ NULL /* callback on error */
+ );
+
+ /* Create file media port from the WAV file */
+ status = pjmedia_wav_player_port_create( pool, /* memory pool */
+ argv[1], /* file to play */
+ 0, /* use default ptime*/
+ 0, /* flags */
+ 0, /* default buffer */
+ &file_port/* returned port */
+ );
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to use WAV file", status);
+ return 1;
+ }
+
+ if (PJMEDIA_PIA_SPF(&file_port->info) > NSAMPLES) {
+ app_perror(THIS_FILE, "WAV clock rate is too big", PJ_EINVAL);
+ return 1;
+ }
+
+ puts("Time\tPCMU\tLinear");
+ puts("------------------------");
+
+ for (i=0; i<COUNT; ++i) {
+ pj_int16_t framebuf[NSAMPLES];
+ pjmedia_frame frm;
+ pj_int32_t level32;
+ unsigned ms;
+ int level;
+
+ frm.buf = framebuf;
+ frm.size = sizeof(framebuf);
+
+ pjmedia_port_get_frame(file_port, &frm);
+
+ level32 = pjmedia_calc_avg_signal(framebuf,
+ PJMEDIA_PIA_SPF(&file_port->info));
+ level = pjmedia_linear2ulaw(level32) ^ 0xFF;
+
+ ms = i * 1000 * PJMEDIA_PIA_SPF(&file_port->info) /
+ PJMEDIA_PIA_SRATE(&file_port->info);
+ printf("%03d.%03d\t%7d\t%7d\n",
+ ms/1000, ms%1000, level, level32);
+ }
+ puts("");
+
+
+ /* Destroy file port */
+ status = pjmedia_port_destroy( file_port );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Release application pool */
+ pj_pool_release( pool );
+
+ /* Destroy media endpoint. */
+ pjmedia_endpt_destroy( med_endpt );
+
+ /* Destroy pool factory */
+ pj_caching_pool_destroy( &cp );
+
+ /* Shutdown PJLIB */
+ pj_shutdown();
+
+
+ /* Done. */
+ return 0;
+}
+
diff --git a/pjsip-apps/src/samples/main_rtems.c b/pjsip-apps/src/samples/main_rtems.c
new file mode 100644
index 0000000..d26605a
--- /dev/null
+++ b/pjsip-apps/src/samples/main_rtems.c
@@ -0,0 +1,12 @@
+
+/*
+ * !! OIY OIY !!
+ *
+ * The purpose of this file is only to get the executable linked. I haven't
+ * actually tried to run this on RTEMS!!
+ *
+ */
+
+#include "../../pjlib/src/pjlib-test/main_rtems.c"
+
+
diff --git a/pjsip-apps/src/samples/mix.c b/pjsip-apps/src/samples/mix.c
new file mode 100644
index 0000000..c8e224f
--- /dev/null
+++ b/pjsip-apps/src/samples/mix.c
@@ -0,0 +1,237 @@
+/* $Id: mix.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+ /**
+ * \page page_pjmedia_samples_mix_c Samples: Mixing WAV files
+ *
+ * This file is pjsip-apps/src/samples/mix.c
+ *
+ * \includelineno mix.c
+ */
+
+#include <pjlib.h>
+#include <pjlib-util.h>
+#include <pjmedia.h>
+
+#define THIS_FILE "mix.c"
+
+static const char *desc =
+ " mix\n"
+ "\n"
+ " PURPOSE:\n"
+ " Mix input WAV files and save it to output WAV. Input WAV can have\n"
+ " different clock rate.\n"
+ "\n"
+ "\n"
+ " USAGE:\n"
+ " mix [options] output.wav input1.wav [input2.wav] ...\n"
+ "\n"
+ " arguments:\n"
+ " output.wav Set the output WAV filename.\n"
+ " input1.wav Set the input WAV filename.\n"
+ " input2.wav Set the input WAV filename.\n"
+ "\n"
+ " options:\n"
+ " -c N Set clock rate to N Hz (default 16000)\n"
+ " -f Force write (overwrite output without warning\n"
+;
+
+#define MAX_WAV 16
+#define PTIME 20
+#define APPEND 1000
+
+struct wav_input
+{
+ const char *fname;
+ pjmedia_port *port;
+ unsigned slot;
+};
+
+static int err_ret(const char *title, pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(3,(THIS_FILE, "%s error: %s", title, errmsg));
+ return 1;
+}
+
+static void usage(void)
+{
+ puts(desc);
+}
+
+int main(int argc, char *argv[])
+{
+ pj_caching_pool cp;
+ pj_pool_t *pool;
+ pjmedia_endpt *med_ept;
+ unsigned clock_rate = 16000;
+ int c, force=0;
+ const char *out_fname;
+ pjmedia_conf *conf;
+ pjmedia_port *wavout;
+ struct wav_input wav_input[MAX_WAV];
+ pj_size_t longest = 0, processed;
+ unsigned i, input_cnt = 0;
+ pj_status_t status;
+
+#define CHECK(op) do { \
+ status = op; \
+ if (status != PJ_SUCCESS) \
+ return err_ret(#op, status); \
+ } while (0)
+
+
+ /* Parse arguments */
+ while ((c=pj_getopt(argc, argv, "c:f")) != -1) {
+ switch (c) {
+ case 'c':
+ clock_rate = atoi(pj_optarg);
+ if (clock_rate < 1000) {
+ puts("Error: invalid clock rate");
+ usage();
+ return -1;
+ }
+ break;
+ case 'f':
+ force = 1;
+ break;
+ }
+ }
+
+ /* Get output WAV name */
+ if (pj_optind == argc) {
+ puts("Error: no WAV output is specified");
+ usage();
+ return 1;
+ }
+
+ out_fname = argv[pj_optind++];
+ if (force==0 && pj_file_exists(out_fname)) {
+ char in[8];
+
+ printf("File %s exists, overwrite? [Y/N] ", out_fname);
+ fflush(stdout);
+ if (fgets(in, sizeof(in), stdin) == NULL)
+ return 1;
+ if (pj_tolower(in[0]) != 'y')
+ return 1;
+ }
+
+ /* Scan input file names */
+ for (input_cnt=0 ; pj_optind<argc && input_cnt<MAX_WAV;
+ ++pj_optind, ++input_cnt)
+ {
+ if (!pj_file_exists(argv[pj_optind])) {
+ printf("Error: input file %s doesn't exist\n",
+ argv[pj_optind]);
+ return 1;
+ }
+ wav_input[input_cnt].fname = argv[pj_optind];
+ wav_input[input_cnt].port = NULL;
+ wav_input[input_cnt].slot = 0;
+ }
+
+ if (input_cnt == 0) {
+ puts("Error: no input WAV is specified");
+ return 0;
+ }
+
+ /* Initialialize */
+ CHECK( pj_init() );
+ CHECK( pjlib_util_init() );
+ pj_caching_pool_init(&cp, NULL, 0);
+ CHECK( pjmedia_endpt_create(&cp.factory, NULL, 1, &med_ept) );
+
+ pool = pj_pool_create(&cp.factory, "mix", 1000, 1000, NULL);
+
+ /* Create the bridge */
+ CHECK( pjmedia_conf_create(pool, MAX_WAV+4, clock_rate, 1,
+ clock_rate * PTIME / 1000, 16,
+ PJMEDIA_CONF_NO_DEVICE, &conf) );
+
+ /* Create the WAV output */
+ CHECK( pjmedia_wav_writer_port_create(pool, out_fname, clock_rate, 1,
+ clock_rate * PTIME / 1000,
+ 16, 0, 0, &wavout) );
+
+ /* Create and register each WAV input to the bridge */
+ for (i=0; i<input_cnt; ++i) {
+ pj_ssize_t len;
+
+ CHECK( pjmedia_wav_player_port_create(pool, wav_input[i].fname, 20,
+ PJMEDIA_FILE_NO_LOOP, 0,
+ &wav_input[i].port) );
+ len = pjmedia_wav_player_get_len(wav_input[i].port);
+ len = (pj_ssize_t)(len * 1.0 * clock_rate /
+ PJMEDIA_PIA_SRATE(&wav_input[i].port->info));
+ if (len > (pj_ssize_t)longest)
+ longest = len;
+
+ CHECK( pjmedia_conf_add_port(conf, pool, wav_input[i].port,
+ NULL, &wav_input[i].slot));
+
+ CHECK( pjmedia_conf_connect_port(conf, wav_input[i].slot, 0, 0) );
+ }
+
+ /* Loop reading frame from the bridge and write it to WAV */
+ processed = 0;
+ while (processed < longest + clock_rate * APPEND * 2 / 1000) {
+ pj_int16_t framebuf[PTIME * 48000 / 1000];
+ pjmedia_port *cp = pjmedia_conf_get_master_port(conf);
+ pjmedia_frame frame;
+
+ frame.buf = framebuf;
+ frame.size = PJMEDIA_PIA_SPF(&cp->info) * 2;
+ pj_assert(frame.size <= sizeof(framebuf));
+
+ CHECK( pjmedia_port_get_frame(cp, &frame) );
+
+ if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) {
+ pj_bzero(frame.buf, frame.size);
+ frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ }
+
+ CHECK( pjmedia_port_put_frame(wavout, &frame));
+
+ processed += frame.size;
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Done. Output duration: %d.%03d",
+ (processed >> 2)/clock_rate,
+ ((processed >> 2)*1000/clock_rate) % 1000));
+
+ /* Shutdown everything */
+ CHECK( pjmedia_port_destroy(wavout) );
+ for (i=0; i<input_cnt; ++i) {
+ CHECK( pjmedia_conf_remove_port(conf, wav_input[i].slot) );
+ CHECK( pjmedia_port_destroy(wav_input[i].port) );
+ }
+
+ CHECK(pjmedia_conf_destroy(conf));
+ CHECK(pjmedia_endpt_destroy(med_ept));
+
+ pj_pool_release(pool);
+ pj_caching_pool_destroy(&cp);
+ pj_shutdown();
+
+ return 0;
+}
+
diff --git a/pjsip-apps/src/samples/pcaputil.c b/pjsip-apps/src/samples/pcaputil.c
new file mode 100644
index 0000000..1c26484
--- /dev/null
+++ b/pjsip-apps/src/samples/pcaputil.c
@@ -0,0 +1,540 @@
+/* $Id: pcaputil.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+#include <pjlib.h>
+#include <pjlib-util.h>
+#include <pjmedia.h>
+#include <pjmedia-codec.h>
+
+static const char *USAGE =
+"pcaputil [options] INPUT OUTPUT\n"
+"\n"
+" Convert captured RTP packets in PCAP file to WAV file or play it\n"
+" to audio device.\n"
+"\n"
+" INPUT is the PCAP file name/path.\n"
+" OUTPUT is the WAV file name/path to store the output, or set to \"-\",\n"
+" to play the output to audio device. The program will decode\n"
+" the RTP contents using codec that is available in PJMEDIA,\n"
+" and optionally decrypt the content using the SRTP crypto and\n"
+" keys below.\n"
+"\n"
+"Options to filter packets from PCAP file:\n"
+"(you can always select the relevant packets from Wireshark of course!)\n"
+" --src-ip=IP Only include packets from this source address\n"
+" --dst-ip=IP Only include packets destined to this address\n"
+" --src-port=port Only include packets from this source port number\n"
+" --dst-port=port Only include packets destined to this port number\n"
+"\n"
+"Options for RTP packet processing:\n"
+""
+" --codec=codec_id The codec ID formatted \"name/clock-rate/channel-count\"\n"
+" must be specified for codec with dynamic PT,\n"
+" e.g: \"Speex/8000\"\n"
+" --srtp-crypto=TAG, -c Set crypto to be used to decrypt SRTP packets. Valid\n"
+" tags are: \n"
+" AES_CM_128_HMAC_SHA1_80 \n"
+" AES_CM_128_HMAC_SHA1_32\n"
+" --srtp-key=KEY, -k Set the base64 key to decrypt SRTP packets.\n"
+"\n"
+"Options for playing to audio device:\n"
+""
+" --play-dev-id=dev_id Audio device ID for playback.\n"
+"\n"
+" Example:\n"
+" pcaputil file.pcap output.wav\n"
+" pcaputil -c AES_CM_128_HMAC_SHA1_80 \\\n"
+" -k VLDONbsbGl2Puqy+0PV7w/uGfpSPKFevDpxGsxN3 \\\n"
+" file.pcap output.wav\n"
+"\n"
+;
+
+static struct app
+{
+ pj_caching_pool cp;
+ pj_pool_t *pool;
+ pjmedia_endpt *mept;
+ pj_pcap_file *pcap;
+ pjmedia_port *wav;
+ pjmedia_codec *codec;
+ pjmedia_aud_stream *aud_strm;
+ unsigned pt;
+ pjmedia_transport *srtp;
+ pjmedia_rtp_session rtp_sess;
+ pj_bool_t rtp_sess_init;
+} app;
+
+
+static void cleanup()
+{
+ if (app.srtp) pjmedia_transport_close(app.srtp);
+ if (app.wav) {
+ pj_ssize_t pos = pjmedia_wav_writer_port_get_pos(app.wav);
+ if (pos >= 0) {
+ unsigned msec;
+ msec = pos / 2 * 1000 / PJMEDIA_PIA_SRATE(&app.wav->info);
+ printf("Written: %dm:%02ds.%03d\n",
+ msec / 1000 / 60,
+ (msec / 1000) % 60,
+ msec % 1000);
+ }
+ pjmedia_port_destroy(app.wav);
+ }
+ if (app.pcap) pj_pcap_close(app.pcap);
+ if (app.codec) {
+ pjmedia_codec_mgr *cmgr;
+ pjmedia_codec_close(app.codec);
+ cmgr = pjmedia_endpt_get_codec_mgr(app.mept);
+ pjmedia_codec_mgr_dealloc_codec(cmgr, app.codec);
+ }
+ if (app.aud_strm) {
+ pjmedia_aud_stream_stop(app.aud_strm);
+ pjmedia_aud_stream_destroy(app.aud_strm);
+ }
+ if (app.mept) pjmedia_endpt_destroy(app.mept);
+ if (app.pool) pj_pool_release(app.pool);
+ pj_caching_pool_destroy(&app.cp);
+ pj_shutdown();
+}
+
+static void err_exit(const char *title, pj_status_t status)
+{
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ printf("Error: %s: %s\n", title, errmsg);
+ } else {
+ printf("Error: %s\n", title);
+ }
+ cleanup();
+ exit(1);
+}
+
+#define T(op) do { \
+ status = op; \
+ if (status != PJ_SUCCESS) \
+ err_exit(#op, status); \
+ } while (0)
+
+
+static void read_rtp(pj_uint8_t *buf, pj_size_t bufsize,
+ pjmedia_rtp_hdr **rtp,
+ pj_uint8_t **payload,
+ unsigned *payload_size,
+ pj_bool_t check_pt)
+{
+ pj_status_t status;
+
+ /* Init RTP session */
+ if (!app.rtp_sess_init) {
+ T(pjmedia_rtp_session_init(&app.rtp_sess, 0, 0));
+ app.rtp_sess_init = PJ_TRUE;
+ }
+
+ /* Loop reading until we have a good RTP packet */
+ for (;;) {
+ pj_size_t sz = bufsize;
+ const pjmedia_rtp_hdr *r;
+ const void *p;
+ pjmedia_rtp_status seq_st;
+
+ status = pj_pcap_read_udp(app.pcap, NULL, buf, &sz);
+ if (status != PJ_SUCCESS)
+ err_exit("Error reading PCAP file", status);
+
+ /* Decode RTP packet to make sure that this is an RTP packet.
+ * We will decode it again to get the payload after we do
+ * SRTP decoding
+ */
+ status = pjmedia_rtp_decode_rtp(&app.rtp_sess, buf, sz, &r,
+ &p, payload_size);
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ printf("Not RTP packet, skipping packet: %s\n", errmsg);
+ continue;
+ }
+
+ /* Decrypt SRTP */
+#if PJMEDIA_HAS_SRTP
+ if (app.srtp) {
+ int len = sz;
+ status = pjmedia_transport_srtp_decrypt_pkt(app.srtp, PJ_TRUE,
+ buf, &len);
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ printf("SRTP packet decryption failed, skipping packet: %s\n",
+ errmsg);
+ continue;
+ }
+ sz = len;
+
+ /* Decode RTP packet again */
+ status = pjmedia_rtp_decode_rtp(&app.rtp_sess, buf, sz, &r,
+ &p, payload_size);
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ printf("Not RTP packet, skipping packet: %s\n", errmsg);
+ continue;
+ }
+ }
+#endif
+
+ /* Update RTP session */
+ pjmedia_rtp_session_update2(&app.rtp_sess, r, &seq_st, PJ_FALSE);
+
+ /* Skip out-of-order packet */
+ if (seq_st.diff == 0) {
+ printf("Skipping out of order packet\n");
+ continue;
+ }
+
+ /* Skip if payload type is different */
+ if (check_pt && r->pt != app.pt) {
+ printf("Skipping RTP packet with bad payload type\n");
+ continue;
+ }
+
+ /* Skip bad packet */
+ if (seq_st.status.flag.bad) {
+ printf("Skipping bad RTP\n");
+ continue;
+ }
+
+
+ *rtp = (pjmedia_rtp_hdr*)r;
+ *payload = (pj_uint8_t*)p;
+
+ /* We have good packet */
+ break;
+ }
+}
+
+pjmedia_frame play_frm;
+static pj_bool_t play_frm_copied, play_frm_ready;
+
+static pj_status_t wait_play(pjmedia_frame *f)
+{
+ play_frm_copied = PJ_FALSE;
+ play_frm = *f;
+ play_frm_ready = PJ_TRUE;
+ while (!play_frm_copied) {
+ pj_thread_sleep(1);
+ }
+ play_frm_ready = PJ_FALSE;
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t play_cb(void *user_data, pjmedia_frame *f)
+{
+ PJ_UNUSED_ARG(user_data);
+
+ if (!play_frm_ready) {
+ PJ_LOG(3, ("play_cb()", "Warning! Play frame not ready"));
+ return PJ_SUCCESS;
+ }
+
+ pj_memcpy(f->buf, play_frm.buf, play_frm.size);
+ f->size = play_frm.size;
+
+ play_frm_copied = PJ_TRUE;
+ return PJ_SUCCESS;
+}
+
+static void pcap2wav(const pj_str_t *codec,
+ const pj_str_t *wav_filename,
+ pjmedia_aud_dev_index dev_id,
+ const pj_str_t *srtp_crypto,
+ const pj_str_t *srtp_key)
+{
+ const pj_str_t WAV = {".wav", 4};
+ struct pkt
+ {
+ pj_uint8_t buffer[320];
+ pjmedia_rtp_hdr *rtp;
+ pj_uint8_t *payload;
+ unsigned payload_len;
+ } pkt0;
+ pjmedia_codec_mgr *cmgr;
+ const pjmedia_codec_info *ci;
+ pjmedia_codec_param param;
+ unsigned samples_per_frame;
+ pj_status_t status;
+
+ /* Initialize all codecs */
+ T( pjmedia_codec_register_audio_codecs(app.mept, NULL) );
+
+ /* Create SRTP transport is needed */
+#if PJMEDIA_HAS_SRTP
+ if (srtp_crypto->slen) {
+ pjmedia_srtp_crypto crypto;
+
+ pj_bzero(&crypto, sizeof(crypto));
+ crypto.key = *srtp_key;
+ crypto.name = *srtp_crypto;
+ T( pjmedia_transport_srtp_create(app.mept, NULL, NULL, &app.srtp) );
+ T( pjmedia_transport_srtp_start(app.srtp, &crypto, &crypto) );
+ }
+#else
+ PJ_UNUSED_ARG(srtp_crypto);
+ PJ_UNUSED_ARG(srtp_key);
+#endif
+
+ /* Read first packet */
+ read_rtp(pkt0.buffer, sizeof(pkt0.buffer), &pkt0.rtp,
+ &pkt0.payload, &pkt0.payload_len, PJ_FALSE);
+
+ cmgr = pjmedia_endpt_get_codec_mgr(app.mept);
+
+ /* Get codec info and param for the specified payload type */
+ app.pt = pkt0.rtp->pt;
+ if (app.pt >=0 && app.pt < 96) {
+ T( pjmedia_codec_mgr_get_codec_info(cmgr, pkt0.rtp->pt, &ci) );
+ } else {
+ unsigned cnt = 2;
+ const pjmedia_codec_info *info[2];
+ T( pjmedia_codec_mgr_find_codecs_by_id(cmgr, codec, &cnt,
+ info, NULL) );
+ if (cnt != 1)
+ err_exit("Codec ID must be specified and unique!", 0);
+
+ ci = info[0];
+ }
+ T( pjmedia_codec_mgr_get_default_param(cmgr, ci, &param) );
+
+ /* Alloc and init codec */
+ T( pjmedia_codec_mgr_alloc_codec(cmgr, ci, &app.codec) );
+ T( pjmedia_codec_init(app.codec, app.pool) );
+ T( pjmedia_codec_open(app.codec, &param) );
+
+ /* Init audio device or WAV file */
+ samples_per_frame = ci->clock_rate * param.info.frm_ptime / 1000;
+ if (pj_strcmp2(wav_filename, "-") == 0) {
+ pjmedia_aud_param aud_param;
+
+ /* Open audio device */
+ T( pjmedia_aud_dev_default_param(dev_id, &aud_param) );
+ aud_param.dir = PJMEDIA_DIR_PLAYBACK;
+ aud_param.channel_count = ci->channel_cnt;
+ aud_param.clock_rate = ci->clock_rate;
+ aud_param.samples_per_frame = samples_per_frame;
+ T( pjmedia_aud_stream_create(&aud_param, NULL, &play_cb,
+ NULL, &app.aud_strm) );
+ T( pjmedia_aud_stream_start(app.aud_strm) );
+ } else if (pj_stristr(wav_filename, &WAV)) {
+ /* Open WAV file */
+ T( pjmedia_wav_writer_port_create(app.pool, wav_filename->ptr,
+ ci->clock_rate, ci->channel_cnt,
+ samples_per_frame,
+ param.info.pcm_bits_per_sample, 0, 0,
+ &app.wav) );
+ } else {
+ err_exit("invalid output file", PJ_EINVAL);
+ }
+
+ /* Loop reading PCAP and writing WAV file */
+ for (;;) {
+ struct pkt pkt1;
+ pj_timestamp ts;
+ pjmedia_frame frames[16], pcm_frame;
+ short pcm[320];
+ unsigned i, frame_cnt;
+ long samples_cnt, ts_gap;
+
+ pj_assert(sizeof(pcm) >= samples_per_frame);
+
+ /* Parse first packet */
+ ts.u64 = 0;
+ frame_cnt = PJ_ARRAY_SIZE(frames);
+ T( pjmedia_codec_parse(app.codec, pkt0.payload, pkt0.payload_len,
+ &ts, &frame_cnt, frames) );
+
+ /* Decode and write to WAV file */
+ samples_cnt = 0;
+ for (i=0; i<frame_cnt; ++i) {
+ pjmedia_frame pcm_frame;
+
+ pcm_frame.buf = pcm;
+ pcm_frame.size = samples_per_frame * 2;
+
+ T( pjmedia_codec_decode(app.codec, &frames[i], pcm_frame.size,
+ &pcm_frame) );
+ if (app.wav) {
+ T( pjmedia_port_put_frame(app.wav, &pcm_frame) );
+ }
+ if (app.aud_strm) {
+ T( wait_play(&pcm_frame) );
+ }
+ samples_cnt += samples_per_frame;
+ }
+
+ /* Read next packet */
+ read_rtp(pkt1.buffer, sizeof(pkt1.buffer), &pkt1.rtp,
+ &pkt1.payload, &pkt1.payload_len, PJ_TRUE);
+
+ /* Fill in the gap (if any) between pkt0 and pkt1 */
+ ts_gap = pj_ntohl(pkt1.rtp->ts) - pj_ntohl(pkt0.rtp->ts) -
+ samples_cnt;
+ while (ts_gap >= (long)samples_per_frame) {
+
+ pcm_frame.buf = pcm;
+ pcm_frame.size = samples_per_frame * 2;
+
+ if (app.codec->op->recover) {
+ T( pjmedia_codec_recover(app.codec, pcm_frame.size,
+ &pcm_frame) );
+ } else {
+ pj_bzero(pcm_frame.buf, pcm_frame.size);
+ }
+
+ if (app.wav) {
+ T( pjmedia_port_put_frame(app.wav, &pcm_frame) );
+ }
+ if (app.aud_strm) {
+ T( wait_play(&pcm_frame) );
+ }
+ ts_gap -= samples_per_frame;
+ }
+
+ /* Next */
+ pkt0 = pkt1;
+ pkt0.rtp = (pjmedia_rtp_hdr*)pkt0.buffer;
+ pkt0.payload = pkt0.buffer + (pkt1.payload - pkt1.buffer);
+ }
+}
+
+
+int main(int argc, char *argv[])
+{
+ pj_str_t input, output, srtp_crypto, srtp_key, codec;
+ pjmedia_aud_dev_index dev_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
+ pj_pcap_filter filter;
+ pj_status_t status;
+
+ enum {
+ OPT_SRC_IP = 1, OPT_DST_IP, OPT_SRC_PORT, OPT_DST_PORT,
+ OPT_CODEC, OPT_PLAY_DEV_ID
+ };
+ struct pj_getopt_option long_options[] = {
+ { "srtp-crypto", 1, 0, 'c' },
+ { "srtp-key", 1, 0, 'k' },
+ { "src-ip", 1, 0, OPT_SRC_IP },
+ { "dst-ip", 1, 0, OPT_DST_IP },
+ { "src-port", 1, 0, OPT_SRC_PORT },
+ { "dst-port", 1, 0, OPT_DST_PORT },
+ { "codec", 1, 0, OPT_CODEC },
+ { "play-dev-id", 1, 0, OPT_PLAY_DEV_ID },
+ { NULL, 0, 0, 0}
+ };
+ int c;
+ int option_index;
+ char key_bin[32];
+
+ srtp_crypto.slen = srtp_key.slen = 0;
+ codec.slen = 0;
+
+ pj_pcap_filter_default(&filter);
+ filter.link = PJ_PCAP_LINK_TYPE_ETH;
+ filter.proto = PJ_PCAP_PROTO_TYPE_UDP;
+
+ /* Parse arguments */
+ pj_optind = 0;
+ while((c=pj_getopt_long(argc,argv, "c:k:", long_options, &option_index))!=-1) {
+ switch (c) {
+ case 'c':
+ srtp_crypto = pj_str(pj_optarg);
+ break;
+ case 'k':
+ {
+ int key_len = sizeof(key_bin);
+ srtp_key = pj_str(pj_optarg);
+ if (pj_base64_decode(&srtp_key, (pj_uint8_t*)key_bin, &key_len)) {
+ puts("Error: invalid key");
+ return 1;
+ }
+ srtp_key.ptr = key_bin;
+ srtp_key.slen = key_len;
+ }
+ break;
+ case OPT_SRC_IP:
+ {
+ pj_str_t t = pj_str(pj_optarg);
+ pj_in_addr a = pj_inet_addr(&t);
+ filter.ip_src = a.s_addr;
+ }
+ break;
+ case OPT_DST_IP:
+ {
+ pj_str_t t = pj_str(pj_optarg);
+ pj_in_addr a = pj_inet_addr(&t);
+ filter.ip_dst = a.s_addr;
+ }
+ break;
+ case OPT_SRC_PORT:
+ filter.src_port = pj_htons((pj_uint16_t)atoi(pj_optarg));
+ break;
+ case OPT_DST_PORT:
+ filter.dst_port = pj_htons((pj_uint16_t)atoi(pj_optarg));
+ break;
+ case OPT_CODEC:
+ codec = pj_str(pj_optarg);
+ break;
+ case OPT_PLAY_DEV_ID:
+ dev_id = atoi(pj_optarg);
+ break;
+ default:
+ puts("Error: invalid option");
+ return 1;
+ }
+ }
+
+ if (pj_optind != argc - 2) {
+ puts(USAGE);
+ return 1;
+ }
+
+ if (!(srtp_crypto.slen) != !(srtp_key.slen)) {
+ puts("Error: both SRTP crypto and key must be specified");
+ puts(USAGE);
+ return 1;
+ }
+
+ input = pj_str(argv[pj_optind]);
+ output = pj_str(argv[pj_optind+1]);
+
+ T( pj_init() );
+
+ pj_caching_pool_init(&app.cp, NULL, 0);
+ app.pool = pj_pool_create(&app.cp.factory, "pcaputil", 1000, 1000, NULL);
+
+ T( pjlib_util_init() );
+ T( pjmedia_endpt_create(&app.cp.factory, NULL, 0, &app.mept) );
+
+ T( pj_pcap_open(app.pool, input.ptr, &app.pcap) );
+ T( pj_pcap_set_filter(app.pcap, &filter) );
+
+ pcap2wav(&codec, &output, dev_id, &srtp_crypto, &srtp_key);
+
+ cleanup();
+ return 0;
+}
+
diff --git a/pjsip-apps/src/samples/pjsip-perf.c b/pjsip-apps/src/samples/pjsip-perf.c
new file mode 100644
index 0000000..f1b7cb9
--- /dev/null
+++ b/pjsip-apps/src/samples/pjsip-perf.c
@@ -0,0 +1,1853 @@
+/* $Id: pjsip-perf.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+
+/**
+ * \page page_pjsip_perf_c Samples: SIP Performance Benchmark
+ *
+ * <b>pjsip-perf</b> is a complete program to measure the
+ * performance of PJSIP or other SIP endpoints. It consists of two
+ * parts:
+ * - the server, to respond incoming requests, and
+ * - the client, who actively submits requests and measure the
+ * performance of the server.
+ *
+ * Both server and client part can run simultaneously, to measure the
+ * performance when both endpoints are co-located in a single program.
+ *
+ * The server accepts both INVITE and non-INVITE requests.
+ * The server exports several different types of URL, which would
+ * control how the request would be handled by the server:
+ * - URL with "0" as the user part will be handled statelessly.
+ * It should not be used with INVITE method.
+ * - URL with "1" as the user part will be handled statefully.
+ * If the request is an INVITE request, INVITE transaction will
+ * be created and 200/OK response will be sent, along with a valid
+ * SDP body. However, the SDP is just a static text body, and
+ * is not a proper SDP generated by PJMEDIA.
+ * - URL with "2" as the user part is only meaningful for INVITE
+ * requests, as it would be handled <b>call-statefully</b> by the
+ * server. For this URL, the server also would generate SDP dynamically
+ * and perform a proper SDP negotiation for the incoming call.
+ * Also for every call, server will limit the call duration to
+ * 10 seconds, on which the call will be terminated if the client
+ * doesn't hangup the call.
+ *
+ *
+ *
+ * This file is pjsip-apps/src/samples/pjsip-perf.c
+ *
+ * \includelineno pjsip-perf.c
+ */
+
+/* Include all headers. */
+#include <pjsip.h>
+#include <pjmedia.h>
+#include <pjmedia-codec.h>
+#include <pjsip_ua.h>
+#include <pjsip_simple.h>
+#include <pjlib-util.h>
+#include <pjlib.h>
+#include <stdio.h>
+
+#if defined(PJ_WIN32) && PJ_WIN32!=0
+# include <windows.h>
+#endif
+
+#define THIS_FILE "pjsip-perf.c"
+#define DEFAULT_COUNT (pjsip_cfg()->tsx.max_count/2>10000?10000:pjsip_cfg()->tsx.max_count/2)
+#define JOB_WINDOW 1000
+#define TERMINATE_TSX(x,c)
+
+
+#ifndef CACHING_POOL_SIZE
+# define CACHING_POOL_SIZE (256*1024*1024)
+#endif
+
+
+/* Static message body for INVITE, when stateful processing is
+ * invoked (instead of call-stateful, where SDP is generated
+ * dynamically.
+ */
+static 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 8 3 103 102 101\r\n"
+ "a=rtcp:4001 IN IP4 192.168.0.68\r\n"
+ "a=rtpmap:103 speex/16000\r\n"
+ "a=rtpmap:102 speex/8000\r\n"
+ "a=rtpmap:3 GSM/8000\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "a=sendrecv\r\n"
+ "a=rtpmap:101 telephone-event/8000\r\n"
+ "a=fmtp:101 0-15\r\n",
+ 0
+};
+
+static pj_str_t mime_application = { "application", 11};
+static pj_str_t mime_sdp = {"sdp", 3};
+
+
+struct srv_state
+{
+ unsigned stateless_cnt;
+ unsigned stateful_cnt;
+ unsigned call_cnt;
+};
+
+
+struct app
+{
+ pj_caching_pool cp;
+ pj_pool_t *pool;
+ pj_bool_t use_tcp;
+ pj_str_t local_addr;
+ int local_port;
+ pjsip_endpoint *sip_endpt;
+ pjmedia_endpt *med_endpt;
+ pj_str_t local_uri;
+ pj_str_t local_contact;
+ unsigned skinfo_cnt;
+ pjmedia_sock_info skinfo[8];
+
+ pj_bool_t thread_quit;
+ unsigned thread_count;
+ pj_thread_t *thread[16];
+
+ pj_bool_t real_sdp;
+ pjmedia_sdp_session *dummy_sdp;
+
+ int log_level;
+
+ struct {
+ pjsip_method method;
+ pj_str_t dst_uri;
+ pj_bool_t stateless;
+ unsigned timeout;
+ unsigned job_count,
+ job_submitted,
+ job_finished,
+ job_window;
+ unsigned stat_max_window;
+ pj_time_val first_request;
+ pj_time_val requests_sent;
+ pj_time_val last_completion;
+ unsigned total_responses;
+ unsigned response_codes[800];
+ } client;
+
+ struct {
+ pj_bool_t send_trying;
+ pj_bool_t send_ringing;
+ unsigned delay;
+ struct srv_state prev_state;
+ struct srv_state cur_state;
+ } server;
+
+
+} app;
+
+struct call
+{
+ pjsip_inv_session *inv;
+ pj_timer_entry ans_timer;
+};
+
+
+static void app_perror(const char *sender, const char *title,
+ pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status));
+}
+
+
+/**************************************************************************
+ * STATELESS SERVER
+ */
+static pj_bool_t mod_stateless_on_rx_request(pjsip_rx_data *rdata);
+
+/* Module to handle incoming requests statelessly.
+ */
+static pjsip_module mod_stateless_server =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-stateless-server", 20 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &mod_stateless_on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+
+static pj_bool_t mod_stateless_on_rx_request(pjsip_rx_data *rdata)
+{
+ const pj_str_t stateless_user = { "0", 1 };
+ pjsip_uri *uri;
+ pjsip_sip_uri *sip_uri;
+
+ uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
+
+ /* Only want to receive SIP scheme */
+ if (!PJSIP_URI_SCHEME_IS_SIP(uri))
+ return PJ_FALSE;
+
+ sip_uri = (pjsip_sip_uri*) uri;
+
+ /* Check for matching user part */
+ if (pj_strcmp(&sip_uri->user, &stateless_user)!=0)
+ return PJ_FALSE;
+
+ /*
+ * Yes, this is for us.
+ */
+
+ /* Ignore ACK request */
+ if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
+ return PJ_TRUE;
+
+ /*
+ * Respond statelessly with 200/OK.
+ */
+ pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 200, NULL,
+ NULL, NULL);
+ app.server.cur_state.stateless_cnt++;
+ return PJ_TRUE;
+}
+
+
+/**************************************************************************
+ * STATEFUL SERVER
+ */
+static pj_bool_t mod_stateful_on_rx_request(pjsip_rx_data *rdata);
+
+/* Module to handle incoming requests statefully.
+ */
+static pjsip_module mod_stateful_server =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-stateful-server", 19 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &mod_stateful_on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+
+static pj_bool_t mod_stateful_on_rx_request(pjsip_rx_data *rdata)
+{
+ const pj_str_t stateful_user = { "1", 1 };
+ pjsip_uri *uri;
+ pjsip_sip_uri *sip_uri;
+
+ uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
+
+ /* Only want to receive SIP scheme */
+ if (!PJSIP_URI_SCHEME_IS_SIP(uri))
+ return PJ_FALSE;
+
+ sip_uri = (pjsip_sip_uri*) uri;
+
+ /* Check for matching user part */
+ if (pj_strcmp(&sip_uri->user, &stateful_user)!=0)
+ return PJ_FALSE;
+
+ /*
+ * Yes, this is for us.
+ * Respond statefully with 200/OK.
+ */
+ switch (rdata->msg_info.msg->line.req.method.id) {
+ case PJSIP_INVITE_METHOD:
+ {
+ pjsip_msg_body *body;
+
+ if (dummy_sdp_str.slen == 0)
+ dummy_sdp_str.slen = pj_ansi_strlen(dummy_sdp_str.ptr);
+
+ body = pjsip_msg_body_create(rdata->tp_info.pool,
+ &mime_application, &mime_sdp,
+ &dummy_sdp_str);
+ pjsip_endpt_respond(app.sip_endpt, &mod_stateful_server, rdata,
+ 200, NULL, NULL, body, NULL);
+ }
+ break;
+ case PJSIP_ACK_METHOD:
+ return PJ_TRUE;
+ default:
+ pjsip_endpt_respond(app.sip_endpt, &mod_stateful_server, rdata,
+ 200, NULL, NULL, NULL, NULL);
+ break;
+ }
+
+ app.server.cur_state.stateful_cnt++;
+ return PJ_TRUE;
+}
+
+
+/**************************************************************************
+ * CALL SERVER
+ */
+static pj_bool_t mod_call_on_rx_request(pjsip_rx_data *rdata);
+
+/* Module to handle incoming requests callly.
+ */
+static pjsip_module mod_call_server =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-call-server", 15 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &mod_call_on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+
+static pj_status_t send_response(pjsip_inv_session *inv,
+ pjsip_rx_data *rdata,
+ int code,
+ pj_bool_t *has_initial)
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ if (*has_initial) {
+ status = pjsip_inv_answer(inv, code, NULL, NULL, &tdata);
+ } else {
+ status = pjsip_inv_initial_answer(inv, rdata, code,
+ NULL, NULL, &tdata);
+ }
+
+ if (status != PJ_SUCCESS) {
+ if (*has_initial) {
+ status = pjsip_inv_answer(inv, PJSIP_SC_NOT_ACCEPTABLE,
+ NULL, NULL, &tdata);
+ } else {
+ status = pjsip_inv_initial_answer(inv, rdata,
+ PJSIP_SC_NOT_ACCEPTABLE,
+ NULL, NULL, &tdata);
+ }
+
+ if (status == PJ_SUCCESS) {
+ *has_initial = PJ_TRUE;
+ pjsip_inv_send_msg(inv, tdata);
+ } else {
+ pjsip_inv_terminate(inv, 500, PJ_FALSE);
+ return -1;
+ }
+ } else {
+ *has_initial = PJ_TRUE;
+
+ status = pjsip_inv_send_msg(inv, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+ }
+
+ return status;
+}
+
+static void answer_timer_cb(pj_timer_heap_t *h, pj_timer_entry *entry)
+{
+ struct call *call = entry->user_data;
+ pj_bool_t has_initial = PJ_TRUE;
+
+ PJ_UNUSED_ARG(h);
+
+ entry->id = 0;
+ send_response(call->inv, NULL, 200, &has_initial);
+}
+
+static pj_bool_t mod_call_on_rx_request(pjsip_rx_data *rdata)
+{
+ const pj_str_t call_user = { "2", 1 };
+ pjsip_uri *uri;
+ pjsip_sip_uri *sip_uri;
+ struct call *call;
+ pjsip_dialog *dlg;
+ pjmedia_sdp_session *sdp;
+ pjsip_tx_data *tdata;
+ pj_bool_t has_initial = PJ_FALSE;
+ pj_status_t status;
+
+ uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
+
+ /* Only want to receive SIP scheme */
+ if (!PJSIP_URI_SCHEME_IS_SIP(uri))
+ return PJ_FALSE;
+
+ sip_uri = (pjsip_sip_uri*) uri;
+
+ /* Only want to handle INVITE requests. */
+ if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
+ return PJ_FALSE;
+ }
+
+
+ /* Check for matching user part. Incoming requests will be handled
+ * call-statefully if:
+ * - user part is "2", or
+ * - user part is not "0" nor "1" and method is INVITE.
+ */
+ if (pj_strcmp(&sip_uri->user, &call_user) == 0 ||
+ sip_uri->user.slen != 1 ||
+ (*sip_uri->user.ptr != '0' && *sip_uri->user.ptr != '1'))
+ {
+ /* Match */
+
+ } else {
+ return PJ_FALSE;
+ }
+
+
+ /* Verify that we can handle the request. */
+ if (app.real_sdp) {
+ unsigned options = 0;
+ status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
+ app.sip_endpt, &tdata);
+ if (status != PJ_SUCCESS) {
+
+ /*
+ * No we can't handle the incoming INVITE request.
+ */
+
+ if (tdata) {
+ pjsip_response_addr res_addr;
+
+ pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
+ pjsip_endpt_send_response(app.sip_endpt, &res_addr, tdata,
+ NULL, NULL);
+
+ } else {
+
+ /* Respond with 500 (Internal Server Error) */
+ pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, NULL,
+ NULL, NULL);
+ }
+
+ return PJ_TRUE;
+ }
+ }
+
+ /* Create UAS dialog */
+ status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
+ &app.local_contact, &dlg);
+ if (status != PJ_SUCCESS) {
+ const pj_str_t reason = pj_str("Unable to create dialog");
+ pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
+ 500, &reason,
+ NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ /* Alloc call structure. */
+ call = pj_pool_zalloc(dlg->pool, sizeof(struct call));
+
+ /* Create SDP from PJMEDIA */
+ if (app.real_sdp) {
+ status = pjmedia_endpt_create_sdp(app.med_endpt, rdata->tp_info.pool,
+ app.skinfo_cnt, app.skinfo,
+ &sdp);
+ } else {
+ sdp = app.dummy_sdp;
+ }
+
+ /* Create UAS invite session */
+ status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
+ if (status != PJ_SUCCESS) {
+ pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
+ pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
+ return PJ_TRUE;
+ }
+
+ /* Send 100/Trying if needed */
+ if (app.server.send_trying) {
+ status = send_response(call->inv, rdata, 100, &has_initial);
+ if (status != PJ_SUCCESS)
+ return PJ_TRUE;
+ }
+
+ /* Send 180/Ringing if needed */
+ if (app.server.send_ringing) {
+ status = send_response(call->inv, rdata, 180, &has_initial);
+ if (status != PJ_SUCCESS)
+ return PJ_TRUE;
+ }
+
+ /* Simulate call processing delay */
+ if (app.server.delay) {
+ pj_time_val delay;
+
+ call->ans_timer.id = 1;
+ call->ans_timer.user_data = call;
+ call->ans_timer.cb = &answer_timer_cb;
+
+ delay.sec = 0;
+ delay.msec = app.server.delay;
+ pj_time_val_normalize(&delay);
+
+ pjsip_endpt_schedule_timer(app.sip_endpt, &call->ans_timer, &delay);
+
+ } else {
+ /* Send the 200 response immediately . */
+ status = send_response(call->inv, rdata, 200, &has_initial);
+ PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return PJ_TRUE);
+ }
+
+ /* Done */
+ app.server.cur_state.call_cnt++;
+
+ return PJ_TRUE;
+}
+
+
+
+/**************************************************************************
+ * Default handler when incoming request is not handled by any other
+ * modules.
+ */
+static pj_bool_t mod_responder_on_rx_request(pjsip_rx_data *rdata);
+
+/* Module to handle incoming requests statelessly.
+ */
+static pjsip_module mod_responder =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-responder", 13 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION+1, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &mod_responder_on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+
+static pj_bool_t mod_responder_on_rx_request(pjsip_rx_data *rdata)
+{
+ const pj_str_t reason = pj_str("Not expecting request at this URI");
+
+ /*
+ * Respond any requests (except ACK!) with 500.
+ */
+ if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
+ pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, &reason,
+ NULL, NULL);
+ }
+
+ return PJ_TRUE;
+}
+
+
+
+/*****************************************************************************
+ * Below is a simple module to log all incoming and outgoing SIP messages
+ */
+
+
+/* Notification on incoming messages */
+static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
+{
+ PJ_LOG(3,(THIS_FILE, "RX %d bytes %s from %s %s:%d:\n"
+ "%.*s\n"
+ "--end msg--",
+ rdata->msg_info.len,
+ pjsip_rx_data_get_info(rdata),
+ rdata->tp_info.transport->type_name,
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port,
+ (int)rdata->msg_info.len,
+ rdata->msg_info.msg_buf));
+
+ /* Always return false, otherwise messages will not get processed! */
+ return PJ_FALSE;
+}
+
+/* Notification on outgoing messages */
+static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
+{
+
+ /* Important note:
+ * tp_info field is only valid after outgoing messages has passed
+ * transport layer. So don't try to access tp_info when the module
+ * has lower priority than transport layer.
+ */
+
+ PJ_LOG(3,(THIS_FILE, "TX %d bytes %s to %s %s:%d:\n"
+ "%.*s\n"
+ "--end msg--",
+ (tdata->buf.cur - tdata->buf.start),
+ pjsip_tx_data_get_info(tdata),
+ tdata->tp_info.transport->type_name,
+ tdata->tp_info.dst_name,
+ tdata->tp_info.dst_port,
+ (int)(tdata->buf.cur - tdata->buf.start),
+ tdata->buf.start));
+
+ /* Always return success, otherwise message will not get sent! */
+ return PJ_SUCCESS;
+}
+
+/* The module instance. */
+static pjsip_module msg_logger =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-siprtp-log", 14 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &logger_on_rx_msg, /* on_rx_request() */
+ &logger_on_rx_msg, /* on_rx_response() */
+ &logger_on_tx_msg, /* on_tx_request. */
+ &logger_on_tx_msg, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+
+};
+
+
+
+/**************************************************************************
+ * Test Client.
+ */
+
+static pj_bool_t mod_test_on_rx_response(pjsip_rx_data *rdata);
+
+static void call_on_media_update( pjsip_inv_session *inv,
+ pj_status_t status);
+static void call_on_state_changed( pjsip_inv_session *inv,
+ pjsip_event *e);
+static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
+
+
+/* Module to handle incoming requests callly.
+ */
+static pjsip_module mod_test =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-test", 8 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ NULL, /* on_rx_request() */
+ &mod_test_on_rx_response, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+
+static void report_completion(int status_code)
+{
+ app.client.job_finished++;
+ if (status_code >= 200 && status_code < 800)
+ app.client.response_codes[status_code]++;
+ app.client.total_responses++;
+ pj_gettimeofday(&app.client.last_completion);
+}
+
+
+/* Handler when response is received. */
+static pj_bool_t mod_test_on_rx_response(pjsip_rx_data *rdata)
+{
+ if (pjsip_rdata_get_tsx(rdata) == NULL) {
+ report_completion(rdata->msg_info.msg->line.status.code);
+ }
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * Create app
+ */
+static pj_status_t create_app(void)
+{
+ pj_status_t status;
+
+ status = pj_init();
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error initializing pjlib", status);
+ return status;
+ }
+
+ /* init PJLIB-UTIL: */
+ status = pjlib_util_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Must create a pool factory before we can allocate any memory. */
+ pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy,
+ CACHING_POOL_SIZE);
+
+ /* Create application pool for misc. */
+ app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
+
+ /* Create the endpoint: */
+ status = pjsip_endpt_create(&app.cp.factory, pj_gethostname()->ptr,
+ &app.sip_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+ return status;
+}
+
+
+/*
+ * Init SIP stack
+ */
+static pj_status_t init_sip()
+{
+ pj_status_t status = -1;
+
+ /* Add UDP/TCP transport. */
+ {
+ pj_sockaddr_in addr;
+ pjsip_host_port addrname;
+ const char *transport_type = NULL;
+
+ pj_bzero(&addr, sizeof(addr));
+ addr.sin_family = pj_AF_INET();
+ addr.sin_addr.s_addr = 0;
+ addr.sin_port = pj_htons((pj_uint16_t)app.local_port);
+
+ if (app.local_addr.slen) {
+ addrname.host = app.local_addr;
+ addrname.port = 5060;
+ }
+ if (app.local_port != 0)
+ addrname.port = app.local_port;
+
+ if (0) {
+#if defined(PJ_HAS_TCP) && PJ_HAS_TCP!=0
+ } else if (app.use_tcp) {
+ pj_sockaddr_in local_addr;
+ pjsip_tpfactory *tpfactory;
+
+ transport_type = "tcp";
+ pj_sockaddr_in_init(&local_addr, 0, (pj_uint16_t)app.local_port);
+ status = pjsip_tcp_transport_start(app.sip_endpt, &local_addr,
+ app.thread_count, &tpfactory);
+ if (status == PJ_SUCCESS) {
+ app.local_addr = tpfactory->addr_name.host;
+ app.local_port = tpfactory->addr_name.port;
+ }
+#endif
+ } else {
+ pjsip_transport *tp;
+
+ transport_type = "udp";
+ status = pjsip_udp_transport_start(app.sip_endpt, &addr,
+ (app.local_addr.slen ? &addrname:NULL),
+ app.thread_count, &tp);
+ if (status == PJ_SUCCESS) {
+ app.local_addr = tp->local_name.host;
+ app.local_port = tp->local_name.port;
+ }
+
+ }
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to start transport", status);
+ return status;
+ }
+
+ app.local_uri.ptr = pj_pool_alloc(app.pool, 128);
+ app.local_uri.slen = pj_ansi_sprintf(app.local_uri.ptr,
+ "<sip:pjsip-perf@%.*s:%d;transport=%s>",
+ (int)app.local_addr.slen,
+ app.local_addr.ptr,
+ app.local_port,
+ transport_type);
+
+ app.local_contact = app.local_uri;
+ }
+
+ /*
+ * Init transaction layer.
+ * This will create/initialize transaction hash tables etc.
+ */
+ status = pjsip_tsx_layer_init_module(app.sip_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Initialize UA layer. */
+ status = pjsip_ua_init_module( app.sip_endpt, NULL );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Initialize 100rel support */
+ status = pjsip_100rel_init_module(app.sip_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Init invite session module. */
+ {
+ pjsip_inv_callback inv_cb;
+
+ /* Init the callback for INVITE session: */
+ pj_bzero(&inv_cb, sizeof(inv_cb));
+ inv_cb.on_state_changed = &call_on_state_changed;
+ inv_cb.on_new_session = &call_on_forked;
+ inv_cb.on_media_update = &call_on_media_update;
+
+ /* Initialize invite session module: */
+ status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ }
+
+ /* Register our module to receive incoming requests. */
+ status = pjsip_endpt_register_module( app.sip_endpt, &mod_test);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+ /* Register stateless server module */
+ status = pjsip_endpt_register_module( app.sip_endpt, &mod_stateless_server);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Register default responder module */
+ status = pjsip_endpt_register_module( app.sip_endpt, &mod_responder);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Register stateless server module */
+ status = pjsip_endpt_register_module( app.sip_endpt, &mod_stateful_server);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+ /* Register call server module */
+ status = pjsip_endpt_register_module( app.sip_endpt, &mod_call_server);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+ /* Done */
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy SIP
+ */
+static void destroy_app()
+{
+ unsigned i;
+
+ app.thread_quit = 1;
+ for (i=0; i<app.thread_count; ++i) {
+ if (app.thread[i]) {
+ pj_thread_join(app.thread[i]);
+ pj_thread_destroy(app.thread[i]);
+ app.thread[i] = NULL;
+ }
+ }
+
+ if (app.sip_endpt) {
+ pjsip_endpt_destroy(app.sip_endpt);
+ app.sip_endpt = NULL;
+ }
+
+ if (app.pool) {
+ pj_pool_release(app.pool);
+ app.pool = NULL;
+ PJ_LOG(3,(THIS_FILE, "Peak memory size: %uMB",
+ app.cp.peak_used_size / 1000000));
+ pj_caching_pool_destroy(&app.cp);
+ }
+
+ /* Shutdown PJLIB */
+ pj_shutdown();
+}
+
+
+/*
+ * Init media stack.
+ */
+static pj_status_t init_media()
+{
+ unsigned i;
+ pj_uint16_t rtp_port;
+ pj_status_t status;
+
+
+ /* Initialize media endpoint so that at least error subsystem is properly
+ * initialized.
+ */
+ status = pjmedia_endpt_create(&app.cp.factory,
+ pjsip_endpt_get_ioqueue(app.sip_endpt), 0,
+ &app.med_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+ /* Must register all codecs to be supported */
+ pjmedia_codec_register_audio_codecs(app.med_endpt, NULL);
+
+ /* Init dummy socket addresses */
+ app.skinfo_cnt = 0;
+ for (i=0, rtp_port=4000; i<PJ_ARRAY_SIZE(app.skinfo); ++i, rtp_port+=2) {
+ pjmedia_sock_info *skinfo;
+
+ skinfo = &app.skinfo[i];
+
+ pj_sockaddr_in_init(&skinfo->rtp_addr_name.ipv4, &app.local_addr,
+ (pj_uint16_t)rtp_port);
+ pj_sockaddr_in_init(&skinfo->rtp_addr_name.ipv4, &app.local_addr,
+ (pj_uint16_t)(rtp_port+1));
+ app.skinfo_cnt++;
+ }
+
+ /* Generate dummy SDP */
+ dummy_sdp_str.slen = pj_ansi_strlen(dummy_sdp_str.ptr);
+ status = pjmedia_sdp_parse(app.pool, dummy_sdp_str.ptr, dummy_sdp_str.slen,
+ &app.dummy_sdp);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error parsing dummy SDP", status);
+ return status;
+ }
+
+
+ /* Done */
+ return PJ_SUCCESS;
+}
+
+
+/* This is notification from the call about media negotiation
+ * status. This is called for client calls only.
+ */
+static void call_on_media_update( pjsip_inv_session *inv,
+ pj_status_t status)
+{
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ status = pjsip_inv_end_session(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE,
+ NULL, &tdata);
+ if (status == PJ_SUCCESS && tdata)
+ status = pjsip_inv_send_msg(inv, tdata);
+ }
+}
+
+
+/* This is notification from the call when the call state has changed.
+ * This is called for client calls only.
+ */
+static void call_on_state_changed( pjsip_inv_session *inv,
+ pjsip_event *e)
+{
+ PJ_UNUSED_ARG(e);
+
+ /* Bail out if the session has been counted before */
+ if (inv->mod_data[mod_test.id] != NULL)
+ return;
+
+ /* Bail out if this is not an outgoing call */
+ if (inv->role != PJSIP_UAC_ROLE)
+ return;
+
+ if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ //report_completion(200);
+ //inv->mod_data[mod_test.id] = (void*)1;
+
+ status = pjsip_inv_end_session(inv, PJSIP_SC_OK, NULL, &tdata);
+ if (status == PJ_SUCCESS && tdata)
+ status = pjsip_inv_send_msg(inv, tdata);
+
+ } else if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+ report_completion(inv->cause);
+ inv->mod_data[mod_test.id] = (void*)1;
+ }
+}
+
+
+/* Not implemented for now */
+static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
+{
+ /* Do nothing */
+ PJ_UNUSED_ARG(inv);
+ PJ_UNUSED_ARG(e);
+}
+
+
+/*
+ * Make outgoing call.
+ */
+static pj_status_t make_call(const pj_str_t *dst_uri)
+{
+ struct call *call;
+ pjsip_dialog *dlg;
+ pjmedia_sdp_session *sdp;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+
+ /* Create UAC dialog */
+ status = pjsip_dlg_create_uac( pjsip_ua_instance(),
+ &app.local_uri, /* local URI */
+ &app.local_contact, /* local Contact */
+ dst_uri, /* remote URI */
+ dst_uri, /* remote target */
+ &dlg); /* dialog */
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ /* Create call */
+ call = pj_pool_zalloc(dlg->pool, sizeof(struct call));
+
+ /* Create SDP */
+ if (app.real_sdp) {
+ status = pjmedia_endpt_create_sdp(app.med_endpt, dlg->pool, 1,
+ app.skinfo, &sdp);
+ if (status != PJ_SUCCESS) {
+ pjsip_dlg_terminate(dlg);
+ return status;
+ }
+ } else
+ sdp = app.dummy_sdp;
+
+ /* Create the INVITE session. */
+ status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
+ if (status != PJ_SUCCESS) {
+ pjsip_dlg_terminate(dlg);
+ return status;
+ }
+
+
+ /* Create initial INVITE request.
+ * This INVITE request will contain a perfectly good request and
+ * an SDP body as well.
+ */
+ status = pjsip_inv_invite(call->inv, &tdata);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+ /* 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(call->inv, tdata);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Verify that valid SIP url is given.
+ */
+static pj_status_t verify_sip_url(const char *c_url)
+{
+ pjsip_uri *p;
+ pj_pool_t *pool;
+ char *url;
+ int len = (c_url ? pj_ansi_strlen(c_url) : 0);
+
+ if (!len) return -1;
+
+ pool = pj_pool_create(&app.cp.factory, "check%p", 1024, 0, NULL);
+ if (!pool) return PJ_ENOMEM;
+
+ url = pj_pool_alloc(pool, len+1);
+ pj_ansi_strcpy(url, c_url);
+ url[len] = '\0';
+
+ p = pjsip_parse_uri(pool, url, len, 0);
+ if (!p || pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0)
+ p = NULL;
+
+ pj_pool_release(pool);
+ return p ? 0 : -1;
+}
+
+
+static void usage(void)
+{
+ printf(
+ "Usage:\n"
+ " pjsip-perf [OPTIONS] -- to start as server\n"
+ " pjsip-perf [OPTIONS] URL -- to call server (possibly itself)\n"
+ "\n"
+ "where:\n"
+ " URL The SIP URL to be contacted.\n"
+ "\n"
+ "Client options:\n"
+ " --method=METHOD, -m Set test method (set to INVITE for call benchmark)\n"
+ " [default: OPTIONS]\n"
+ " --count=N, -n Set total number of requests to initiate\n"
+ " [default=%d]\n"
+ " --stateless, -s Set to operate in stateless mode\n"
+ " [default: stateful]\n"
+ " --timeout=SEC, -t Set client timeout [default=60 sec]\n"
+ " --window=COUNT, -w Set maximum outstanding job [default: %d]\n"
+ "\n"
+ "SDP options (client and server):\n"
+ " --real-sdp Generate real SDP from pjmedia, and also perform\n"
+ " proper SDP negotiation [default: dummy]\n"
+ "\n"
+ "Client and Server options:\n"
+ " --local-port=PORT, -p Set local port [default: 5060]\n"
+ " --use-tcp, -T Use TCP instead of UDP. Note that when started as\n"
+ " client, you must add ;transport=tcp parameter to URL\n"
+ " [default: no]\n"
+ " --thread-count=N Set number of worker threads [default=1]\n"
+ " --trying Send 100/Trying response (server, default no)\n"
+ " --ringing Send 180/Ringing response (server, default no)\n"
+ " --delay=MS, -d Delay answering call by MS (server, default no)\n"
+ "\n"
+ "Misc options:\n"
+ " --help, -h Display this screen\n"
+ " --verbose, -v Verbose logging (put more than once for even more)\n"
+ "\n"
+ "When started as server, pjsip-perf can be contacted on the following URIs:\n"
+ " - sip:0@server-addr To handle requests statelessly.\n"
+ " - sip:1@server-addr To handle requests statefully.\n"
+ " - sip:2@server-addr To handle INVITE call.\n",
+ DEFAULT_COUNT, JOB_WINDOW);
+}
+
+
+static int my_atoi(const char *s)
+{
+ pj_str_t ss = pj_str((char*)s);
+ return pj_strtoul(&ss);
+}
+
+
+static pj_status_t init_options(int argc, char *argv[])
+{
+ enum { OPT_THREAD_COUNT = 1, OPT_REAL_SDP, OPT_TRYING, OPT_RINGING };
+ struct pj_getopt_option long_options[] = {
+ { "local-port", 1, 0, 'p' },
+ { "count", 1, 0, 'c' },
+ { "thread-count", 1, 0, OPT_THREAD_COUNT },
+ { "method", 1, 0, 'm' },
+ { "help", 0, 0, 'h' },
+ { "stateless", 0, 0, 's' },
+ { "timeout", 1, 0, 't' },
+ { "real-sdp", 0, 0, OPT_REAL_SDP },
+ { "verbose", 0, 0, 'v' },
+ { "use-tcp", 0, 0, 'T' },
+ { "window", 1, 0, 'w' },
+ { "delay", 1, 0, 'd' },
+ { "trying", 0, 0, OPT_TRYING},
+ { "ringing", 0, 0, OPT_RINGING},
+ { NULL, 0, 0, 0 },
+ };
+ int c;
+ int option_index;
+
+ /* Init default application configs */
+ app.local_port = 5060;
+ app.thread_count = 1;
+ app.client.job_count = DEFAULT_COUNT;
+ app.client.method = *pjsip_get_options_method();
+ app.client.job_window = c = JOB_WINDOW;
+ app.client.timeout = 60;
+ app.log_level = 3;
+
+
+ /* Parse options */
+ pj_optind = 0;
+ while((c=pj_getopt_long(argc,argv, "p:c:m:t:w:d:hsv",
+ long_options, &option_index))!=-1)
+ {
+ switch (c) {
+ case 'p':
+ app.local_port = my_atoi(pj_optarg);
+ if (app.local_port < 0 || app.local_port > 65535) {
+ PJ_LOG(3,(THIS_FILE, "Invalid --local-port %s", pj_optarg));
+ return -1;
+ }
+ break;
+
+ case 'c':
+ app.client.job_count = my_atoi(pj_optarg);
+ if (app.client.job_count < 0) {
+ PJ_LOG(3,(THIS_FILE, "Invalid --local-port %s", pj_optarg));
+ return -1;
+ }
+ if (app.client.job_count > pjsip_cfg()->tsx.max_count)
+ PJ_LOG(3,(THIS_FILE,
+ "Warning: --count value (%d) exceeds maximum "
+ "transaction count (%d)", app.client.job_count,
+ pjsip_cfg()->tsx.max_count));
+ break;
+
+ case OPT_THREAD_COUNT:
+ app.thread_count = my_atoi(pj_optarg);
+ if (app.thread_count < 1 || app.thread_count > 16) {
+ PJ_LOG(3,(THIS_FILE, "Invalid --thread-count %s", pj_optarg));
+ return -1;
+ }
+ break;
+
+ case 'm':
+ {
+ pj_str_t temp = pj_str((char*)pj_optarg);
+ pjsip_method_init_np(&app.client.method, &temp);
+ }
+ break;
+
+ case 'h':
+ usage();
+ return -1;
+
+ case 's':
+ app.client.stateless = PJ_TRUE;
+ break;
+
+ case OPT_REAL_SDP:
+ app.real_sdp = 1;
+ break;
+
+ case 'v':
+ app.log_level++;
+ break;
+
+ case 't':
+ app.client.timeout = my_atoi(pj_optarg);
+ if (app.client.timeout < 0 || app.client.timeout > 600) {
+ PJ_LOG(3,(THIS_FILE, "Invalid --timeout %s", pj_optarg));
+ return -1;
+ }
+ break;
+
+ case 'w':
+ app.client.job_window = my_atoi(pj_optarg);
+ if (app.client.job_window <= 0) {
+ PJ_LOG(3,(THIS_FILE, "Invalid --window %s", pj_optarg));
+ return -1;
+ }
+ break;
+
+ case 'T':
+ app.use_tcp = PJ_TRUE;
+ break;
+
+ case 'd':
+ app.server.delay = my_atoi(pj_optarg);
+ if (app.server.delay > 3600) {
+ PJ_LOG(3,(THIS_FILE, "I think --delay %s is too long",
+ pj_optarg));
+ return -1;
+ }
+ break;
+
+ case OPT_TRYING:
+ app.server.send_trying = 1;
+ break;
+
+ case OPT_RINGING:
+ app.server.send_ringing = 1;
+ break;
+
+ default:
+ PJ_LOG(1,(THIS_FILE,
+ "Invalid argument. Use --help to see help"));
+ return -1;
+ }
+ }
+
+ if (pj_optind != argc) {
+
+ if (verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
+ PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
+ return -1;
+ }
+ app.client.dst_uri = pj_str(argv[pj_optind]);
+
+ pj_optind++;
+
+ }
+
+ if (pj_optind != argc) {
+ PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/* Send one stateless request */
+static pj_status_t submit_stateless_job(void)
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ status = pjsip_endpt_create_request(app.sip_endpt, &app.client.method,
+ &app.client.dst_uri, &app.local_uri,
+ &app.client.dst_uri, &app.local_contact,
+ NULL, -1, NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error creating request", status);
+ report_completion(701);
+ return status;
+ }
+
+ status = pjsip_endpt_send_request_stateless(app.sip_endpt, tdata, NULL,
+ NULL);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ app_perror(THIS_FILE, "Error sending stateless request", status);
+ report_completion(701);
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* This callback is called when client transaction state has changed */
+static void tsx_completion_cb(void *token, pjsip_event *event)
+{
+ pjsip_transaction *tsx;
+
+ PJ_UNUSED_ARG(token);
+
+ if (event->type != PJSIP_EVENT_TSX_STATE)
+ return;
+
+ tsx = event->body.tsx_state.tsx;
+
+ if (tsx->mod_data[mod_test.id] != NULL) {
+ /* This transaction has been calculated before */
+ return;
+ }
+
+ if (tsx->state==PJSIP_TSX_STATE_TERMINATED) {
+ report_completion(tsx->status_code);
+ tsx->mod_data[mod_test.id] = (void*)1;
+ }
+ else if (tsx->method.id == PJSIP_INVITE_METHOD &&
+ tsx->state == PJSIP_TSX_STATE_CONFIRMED) {
+
+ report_completion(tsx->status_code);
+ tsx->mod_data[mod_test.id] = (void*)1;
+
+ } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
+
+ report_completion(tsx->status_code);
+ tsx->mod_data[mod_test.id] = (void*)1;
+
+ TERMINATE_TSX(tsx, tsx->status_code);
+ }
+}
+
+
+/* Send one stateful request */
+static pj_status_t submit_job(void)
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ status = pjsip_endpt_create_request(app.sip_endpt, &app.client.method,
+ &app.client.dst_uri, &app.local_uri,
+ &app.client.dst_uri, &app.local_contact,
+ NULL, -1, NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error creating request", status);
+ report_completion(701);
+ return status;
+ }
+
+ status = pjsip_endpt_send_request(app.sip_endpt, tdata, -1, NULL,
+ &tsx_completion_cb);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error sending stateful request", status);
+ //should have been reported by tsx_completion_cb().
+ //report_completion(701);
+ //No longer necessary (r777)
+ //pjsip_tx_data_dec_ref(tdata);
+ }
+ return status;
+}
+
+
+/* Client worker thread */
+static int client_thread(void *arg)
+{
+ pj_time_val end_time, last_report, now;
+ unsigned thread_index = (unsigned)(long)arg;
+ unsigned cycle = 0, last_cycle = 0;
+
+ pj_thread_sleep(100);
+
+ pj_gettimeofday(&end_time);
+ end_time.sec += app.client.timeout;
+
+ pj_gettimeofday(&last_report);
+
+ if (app.client.first_request.sec == 0) {
+ pj_gettimeofday(&app.client.first_request);
+ }
+
+ /* Submit all jobs */
+ while (app.client.job_submitted < app.client.job_count && !app.thread_quit){
+ pj_time_val timeout = { 0, 1 };
+ unsigned i;
+ int outstanding;
+ pj_status_t status;
+
+ /* Calculate current outstanding job */
+ outstanding = app.client.job_submitted - app.client.job_finished;
+
+ /* Update stats on max outstanding jobs */
+ if (outstanding > (int)app.client.stat_max_window)
+ app.client.stat_max_window = outstanding;
+
+ /* Wait if there are more pending jobs than allowed in the
+ * window. But spawn a new job anyway if no events are happening
+ * after we wait for some time.
+ */
+ for (i=0; outstanding > (int)app.client.job_window && i<1000; ++i) {
+ pj_time_val wait = { 0, 500 };
+ unsigned count = 0;
+
+ pjsip_endpt_handle_events2(app.sip_endpt, &wait, &count);
+ outstanding = app.client.job_submitted - app.client.job_finished;
+
+ if (count == 0)
+ break;
+
+ ++cycle;
+ }
+
+
+ /* Submit one job */
+ if (app.client.method.id == PJSIP_INVITE_METHOD) {
+ status = make_call(&app.client.dst_uri);
+ } else if (app.client.stateless) {
+ status = submit_stateless_job();
+ } else {
+ status = submit_job();
+ }
+
+ ++app.client.job_submitted;
+ ++cycle;
+
+ /* Handle event */
+ pjsip_endpt_handle_events2(app.sip_endpt, &timeout, NULL);
+
+ /* Check for time out, also print report */
+ if (cycle - last_cycle >= 500) {
+ pj_gettimeofday(&now);
+ if (PJ_TIME_VAL_GTE(now, end_time)) {
+ break;
+ }
+ last_cycle = cycle;
+
+
+ if (thread_index == 0 && now.sec-last_report.sec >= 2) {
+ printf("\r%d jobs started, %d completed... ",
+ app.client.job_submitted, app.client.job_finished);
+ fflush(stdout);
+ last_report = now;
+ }
+ }
+ }
+
+ if (app.client.requests_sent.sec == 0) {
+ pj_gettimeofday(&app.client.requests_sent);
+ }
+
+
+ if (thread_index == 0) {
+ printf("\r%d jobs started, %d completed%s\n",
+ app.client.job_submitted, app.client.job_finished,
+ (app.client.job_submitted!=app.client.job_finished ?
+ ", waiting..." : ".") );
+ fflush(stdout);
+ }
+
+ /* Wait until all jobs completes, or timed out */
+ pj_gettimeofday(&now);
+ while (PJ_TIME_VAL_LT(now, end_time) &&
+ app.client.job_finished < app.client.job_count &&
+ !app.thread_quit)
+ {
+ pj_time_val timeout = { 0, 1 };
+ unsigned i;
+
+ for (i=0; i<1000; ++i) {
+ unsigned count;
+ count = 0;
+ pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &count);
+ if (count == 0)
+ break;
+ }
+
+ pj_gettimeofday(&now);
+ }
+
+ /* Wait couple of seconds to let jobs completes (e.g. ACKs to be sent) */
+ pj_gettimeofday(&now);
+ end_time = now;
+ end_time.sec += 2;
+ while (PJ_TIME_VAL_LT(now, end_time))
+ {
+ pj_time_val timeout = { 0, 1 };
+ unsigned i;
+
+ for (i=0; i<1000; ++i) {
+ unsigned count;
+ count = 0;
+ pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &count);
+ if (count == 0)
+ break;
+ }
+
+ pj_gettimeofday(&now);
+ }
+
+ return 0;
+}
+
+
+static const char *good_number(char *buf, pj_int32_t val)
+{
+ if (val < 1000) {
+ pj_ansi_sprintf(buf, "%d", val);
+ } else if (val < 1000000) {
+ pj_ansi_sprintf(buf, "%d.%dK",
+ val / 1000,
+ (val % 1000) / 100);
+ } else {
+ pj_ansi_sprintf(buf, "%d.%02dM",
+ val / 1000000,
+ (val % 1000000) / 10000);
+ }
+
+ return buf;
+}
+
+
+static int server_thread(void *arg)
+{
+ pj_time_val timeout = { 0, 1 };
+ unsigned thread_index = (unsigned)(long)arg;
+ pj_time_val last_report, next_report;
+
+ pj_gettimeofday(&last_report);
+ next_report = last_report;
+ next_report.sec++;
+
+ while (!app.thread_quit) {
+ pj_time_val now;
+ unsigned i;
+
+ for (i=0; i<100; ++i) {
+ unsigned count = 0;
+ pjsip_endpt_handle_events2(app.sip_endpt, &timeout, &count);
+ if (count == 0)
+ break;
+ }
+
+ if (thread_index == 0) {
+ pj_gettimeofday(&now);
+
+ if (PJ_TIME_VAL_GTE(now, next_report)) {
+ pj_time_val tmp;
+ unsigned msec;
+ unsigned stateless, stateful, call;
+ char str_stateless[32], str_stateful[32], str_call[32];
+
+ tmp = now;
+ PJ_TIME_VAL_SUB(tmp, last_report);
+ msec = PJ_TIME_VAL_MSEC(tmp);
+
+ last_report = now;
+ next_report = last_report;
+ next_report.sec++;
+
+ stateless = app.server.cur_state.stateless_cnt - app.server.prev_state.stateless_cnt;
+ stateful = app.server.cur_state.stateful_cnt - app.server.prev_state.stateful_cnt;
+ call = app.server.cur_state.call_cnt - app.server.prev_state.call_cnt;
+
+ good_number(str_stateless, app.server.cur_state.stateless_cnt);
+ good_number(str_stateful, app.server.cur_state.stateful_cnt);
+ good_number(str_call, app.server.cur_state.call_cnt);
+
+ printf("Total(rate): stateless:%s (%d/s), statefull:%s (%d/s), call:%s (%d/s) \r",
+ str_stateless, stateless*1000/msec,
+ str_stateful, stateful*1000/msec,
+ str_call, call*1000/msec);
+ fflush(stdout);
+
+ app.server.prev_state = app.server.cur_state;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void write_report(const char *msg)
+{
+ puts(msg);
+
+#if defined(PJ_WIN32) && PJ_WIN32!=0
+ OutputDebugString(msg);
+ OutputDebugString("\n");
+#endif
+}
+
+
+int main(int argc, char *argv[])
+{
+ static char report[1024];
+
+ printf("PJSIP Performance Measurement Tool v%s\n"
+ "(c)2006 pjsip.org\n\n",
+ PJ_VERSION);
+
+ if (create_app() != 0)
+ return 1;
+
+ if (init_options(argc, argv) != 0)
+ return 1;
+
+ if (init_sip() != 0)
+ return 1;
+
+ if (init_media() != 0)
+ return 1;
+
+ pj_log_set_level(app.log_level);
+
+ if (app.log_level > 4) {
+ pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
+ }
+
+
+ /* Misc infos */
+ if (app.client.dst_uri.slen != 0) {
+ if (app.client.method.id == PJSIP_INVITE_METHOD) {
+ if (app.client.stateless) {
+ PJ_LOG(3,(THIS_FILE,
+ "Info: --stateless option makes no sense for INVITE,"
+ " ignored."));
+ }
+ }
+
+ }
+
+
+
+ if (app.client.dst_uri.slen) {
+ /* Client mode */
+ pj_status_t status;
+ char test_type[64];
+ unsigned msec_req, msec_res;
+ unsigned i;
+
+ /* Get the job name */
+ if (app.client.method.id == PJSIP_INVITE_METHOD) {
+ pj_ansi_strcpy(test_type, "INVITE calls");
+ } else if (app.client.stateless) {
+ pj_ansi_sprintf(test_type, "stateless %.*s requests",
+ (int)app.client.method.name.slen,
+ app.client.method.name.ptr);
+ } else {
+ pj_ansi_sprintf(test_type, "stateful %.*s requests",
+ (int)app.client.method.name.slen,
+ app.client.method.name.ptr);
+ }
+
+
+ printf("Sending %d %s to '%.*s' with %d maximum outstanding jobs, please wait..\n",
+ app.client.job_count, test_type,
+ (int)app.client.dst_uri.slen, app.client.dst_uri.ptr,
+ app.client.job_window);
+
+ for (i=0; i<app.thread_count; ++i) {
+ status = pj_thread_create(app.pool, NULL, &client_thread,
+ (void*)(long)i, 0, 0, &app.thread[i]);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create thread", status);
+ return 1;
+ }
+ }
+
+ for (i=0; i<app.thread_count; ++i) {
+ pj_thread_join(app.thread[i]);
+ app.thread[i] = NULL;
+ }
+
+ if (app.client.last_completion.sec) {
+ pj_time_val duration;
+ duration = app.client.last_completion;
+ PJ_TIME_VAL_SUB(duration, app.client.first_request);
+ msec_res = PJ_TIME_VAL_MSEC(duration);
+ } else {
+ msec_res = app.client.timeout * 1000;
+ }
+
+ if (msec_res == 0) msec_res = 1;
+
+ if (app.client.requests_sent.sec) {
+ pj_time_val duration;
+ duration = app.client.requests_sent;
+ PJ_TIME_VAL_SUB(duration, app.client.first_request);
+ msec_req = PJ_TIME_VAL_MSEC(duration);
+ } else {
+ msec_req = app.client.timeout * 1000;
+ }
+
+ if (msec_req == 0) msec_req = 1;
+
+ if (app.client.job_submitted < app.client.job_count)
+ puts("\ntimed-out!\n");
+ else
+ puts("\ndone.\n");
+
+ pj_ansi_snprintf(
+ report, sizeof(report),
+ "Total %d %s sent in %d ms at rate of %d/sec\n"
+ "Total %d responses receieved in %d ms at rate of %d/sec:",
+ app.client.job_submitted, test_type, msec_req,
+ app.client.job_submitted * 1000 / msec_req,
+ app.client.total_responses, msec_res,
+ app.client.total_responses*1000/msec_res);
+ write_report(report);
+
+ /* Print detailed response code received */
+ pj_ansi_sprintf(report, "\nDetailed responses received:");
+ write_report(report);
+
+ for (i=0; i<PJ_ARRAY_SIZE(app.client.response_codes); ++i) {
+ const pj_str_t *reason;
+
+ if (app.client.response_codes[i] == 0)
+ continue;
+
+ reason = pjsip_get_status_text(i);
+ pj_ansi_snprintf( report, sizeof(report),
+ " - %d responses: %7d (%.*s)",
+ i, app.client.response_codes[i],
+ (int)reason->slen, reason->ptr);
+ write_report(report);
+ }
+
+ /* Total responses and rate */
+ pj_ansi_snprintf( report, sizeof(report),
+ " ------\n"
+ " TOTAL responses: %7d (rate=%d/sec)\n",
+ app.client.total_responses,
+ app.client.total_responses*1000/msec_res);
+
+ write_report(report);
+
+ pj_ansi_sprintf(report, "Maximum outstanding job: %d",
+ app.client.stat_max_window);
+ write_report(report);
+
+
+ } else {
+ /* Server mode */
+ char s[10], *unused;
+ pj_status_t status;
+ unsigned i;
+
+ puts("pjsip-perf started in server-mode");
+
+ printf("Receiving requests on the following URIs:\n"
+ " sip:0@%.*s:%d%s for stateless handling\n"
+ " sip:1@%.*s:%d%s for stateful handling\n"
+ " sip:2@%.*s:%d%s for call handling\n",
+ (int)app.local_addr.slen,
+ app.local_addr.ptr,
+ app.local_port,
+ (app.use_tcp ? ";transport=tcp" : ""),
+ (int)app.local_addr.slen,
+ app.local_addr.ptr,
+ app.local_port,
+ (app.use_tcp ? ";transport=tcp" : ""),
+ (int)app.local_addr.slen,
+ app.local_addr.ptr,
+ app.local_port,
+ (app.use_tcp ? ";transport=tcp" : ""));
+ printf("INVITE with non-matching user part will be handled call-statefully\n");
+
+ for (i=0; i<app.thread_count; ++i) {
+ status = pj_thread_create(app.pool, NULL, &server_thread,
+ (void*)(long)i, 0, 0, &app.thread[i]);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create thread", status);
+ return 1;
+ }
+ }
+
+ puts("\nPress <ENTER> to quit\n");
+ fflush(stdout);
+ unused = fgets(s, sizeof(s), stdin);
+ PJ_UNUSED_ARG(unused);
+
+ app.thread_quit = PJ_TRUE;
+ for (i=0; i<app.thread_count; ++i) {
+ pj_thread_join(app.thread[i]);
+ app.thread[i] = NULL;
+ }
+
+ puts("");
+ }
+
+
+ destroy_app();
+
+ return 0;
+}
+
diff --git a/pjsip-apps/src/samples/playfile.c b/pjsip-apps/src/samples/playfile.c
new file mode 100644
index 0000000..778b3e5
--- /dev/null
+++ b/pjsip-apps/src/samples/playfile.c
@@ -0,0 +1,217 @@
+/* $Id: playfile.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+#include <pjmedia.h>
+#include <pjlib-util.h>
+#include <pjlib.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "util.h"
+
+
+/**
+ * \page page_pjmedia_samples_playfile_c Samples: Playing WAV File to Sound Device
+ *
+ * This is a very simple example to use the @ref PJMEDIA_FILE_PLAY and
+ * @ref PJMED_SND_PORT. In this example, we open both the file and sound
+ * device, and connect the two of them, and voila! Sound will be playing
+ * the contents of the file.
+ *
+ * @see page_pjmedia_samples_recfile_c
+ *
+ * This file is pjsip-apps/src/samples/playfile.c
+ *
+ * \includelineno playfile.c
+ */
+
+
+/*
+ * playfile.c
+ *
+ * PURPOSE:
+ * Play a WAV file to sound player device.
+ *
+ * USAGE:
+ * playfile FILE.WAV
+ *
+ * The WAV file could have mono or stereo channels with arbitrary
+ * sampling rate, but MUST contain uncompressed (i.e. 16bit) PCM.
+ *
+ */
+
+
+/* For logging purpose. */
+#define THIS_FILE "playfile.c"
+
+
+static const char *desc =
+" FILE \n"
+" \n"
+" playfile.c \n"
+" \n"
+" PURPOSE \n"
+" \n"
+" Demonstrate how to play a WAV file. \n"
+" \n"
+" USAGE \n"
+" \n"
+" playfile FILE.WAV \n"
+" \n"
+" The WAV file could have mono or stereo channels with arbitrary \n"
+" sampling rate, but MUST contain uncompressed (i.e. 16bit) PCM. \n";
+
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+ pj_caching_pool cp;
+ pjmedia_endpt *med_endpt;
+ pj_pool_t *pool;
+ pjmedia_port *file_port;
+ pjmedia_snd_port *snd_port;
+ char tmp[10];
+ pj_status_t status;
+
+
+ if (argc != 2) {
+ puts("Error: filename required");
+ puts(desc);
+ return 1;
+ }
+
+
+ /* Must init PJLIB first: */
+ status = pj_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);
+
+ /*
+ * Initialize media endpoint.
+ * This will implicitly initialize PJMEDIA too.
+ */
+ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Create memory pool for our file player */
+ pool = pj_pool_create( &cp.factory, /* pool factory */
+ "wav", /* pool name. */
+ 4000, /* init size */
+ 4000, /* increment size */
+ NULL /* callback on error */
+ );
+
+ /* Create file media port from the WAV file */
+ status = pjmedia_wav_player_port_create( pool, /* memory pool */
+ argv[1], /* file to play */
+ 20, /* ptime. */
+ 0, /* flags */
+ 0, /* default buffer */
+ &file_port/* returned port */
+ );
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to use WAV file", status);
+ return 1;
+ }
+
+ /* Create sound player port. */
+ status = pjmedia_snd_port_create_player(
+ pool, /* pool */
+ -1, /* use default dev. */
+ PJMEDIA_PIA_SRATE(&file_port->info),/* clock rate. */
+ PJMEDIA_PIA_CCNT(&file_port->info),/* # of channels. */
+ PJMEDIA_PIA_SPF(&file_port->info), /* samples per frame. */
+ PJMEDIA_PIA_BITS(&file_port->info),/* bits per sample. */
+ 0, /* options */
+ &snd_port /* returned port */
+ );
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to open sound device", status);
+ return 1;
+ }
+
+ /* Connect file port to the sound player.
+ * Stream playing will commence immediately.
+ */
+ status = pjmedia_snd_port_connect( snd_port, file_port);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+
+ /*
+ * File should be playing and looping now, using sound device's thread.
+ */
+
+
+ /* Sleep to allow log messages to flush */
+ pj_thread_sleep(100);
+
+
+ printf("Playing %s..\n", argv[1]);
+ puts("");
+ puts("Press <ENTER> to stop playing and quit");
+
+ if (fgets(tmp, sizeof(tmp), stdin) == NULL) {
+ puts("EOF while reading stdin, will quit now..");
+ }
+
+
+ /* Start deinitialization: */
+
+ /* Disconnect sound port from file port */
+ status = pjmedia_snd_port_disconnect(snd_port);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Without this sleep, Windows/DirectSound will repeteadly
+ * play the last frame during destroy.
+ */
+ pj_thread_sleep(100);
+
+ /* Destroy sound device */
+ status = pjmedia_snd_port_destroy( snd_port );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Destroy file port */
+ status = pjmedia_port_destroy( file_port );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Release application pool */
+ pj_pool_release( pool );
+
+ /* Destroy media endpoint. */
+ pjmedia_endpt_destroy( med_endpt );
+
+ /* Destroy pool factory */
+ pj_caching_pool_destroy( &cp );
+
+ /* Shutdown PJLIB */
+ pj_shutdown();
+
+
+ /* Done. */
+ return 0;
+}
+
diff --git a/pjsip-apps/src/samples/playsine.c b/pjsip-apps/src/samples/playsine.c
new file mode 100644
index 0000000..bab0c9b
--- /dev/null
+++ b/pjsip-apps/src/samples/playsine.c
@@ -0,0 +1,317 @@
+/* $Id: playsine.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+
+/**
+ * \page page_pjmedia_samples_playsine_c Samples: Using Custom Ports (Sine Wave Generator)
+ *
+ * This example demonstrate how to create a custom media port (in this case, a
+ * sine wave generator) and connect it to the sound device.
+ *
+ * This file is pjsip-apps/src/samples/playsine.c
+ *
+ * \includelineno playsine.c
+ */
+
+/*
+ * playsine.c
+ *
+ * PURPOSE:
+ * Demonstrate how to create and use custom media port which
+ * simply feed a sine wav to the sound player.
+ *
+ * USAGE:
+ * playsine [nchannel]
+ *
+ * where:
+ * nchannel is 1 for mono (this is the default) or 2 for stereo.
+ */
+
+#include <pjmedia.h>
+#include <pjlib.h>
+
+#include <stdlib.h> /* atoi() */
+#include <stdio.h>
+#include <math.h> /* sin() */
+
+/* For logging purpose. */
+#define THIS_FILE "playsine.c"
+
+
+/* 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)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ PJ_UNUSED_ARG(sender);
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ printf("%s: %s [code=%d]\n", title, errmsg, status);
+ return 1;
+}
+
+
+/* Struct attached to sine generator */
+typedef struct
+{
+ pj_int16_t *samples; /* Sine samples. */
+} port_data;
+
+
+/* This callback is called to feed more samples */
+static pj_status_t sine_get_frame( pjmedia_port *port,
+ pjmedia_frame *frame)
+{
+ port_data *sine = port->port_data.pdata;
+ pj_int16_t *samples = frame->buf;
+ unsigned i, count, left, right;
+
+ /* Get number of samples */
+ count = frame->size / 2 / PJMEDIA_PIA_CCNT(&port->info);
+
+ left = 0;
+ right = 0;
+
+ for (i=0; i<count; ++i) {
+ *samples++ = sine->samples[left];
+ ++left;
+
+ if (PJMEDIA_PIA_CCNT(&port->info) == 2) {
+ *samples++ = sine->samples[right];
+ right += 2; /* higher pitch so we can distinguish left and right. */
+ if (right >= count)
+ right = 0;
+ }
+ }
+
+ /* Must set frame->type correctly, otherwise the sound device
+ * will refuse to play.
+ */
+ frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+
+ return PJ_SUCCESS;
+}
+
+#ifndef M_PI
+#define M_PI (3.14159265)
+#endif
+
+/*
+ * Create a media port to generate sine wave samples.
+ */
+static pj_status_t create_sine_port(pj_pool_t *pool,
+ unsigned sampling_rate,
+ unsigned channel_count,
+ pjmedia_port **p_port)
+{
+ pjmedia_port *port;
+ unsigned i;
+ unsigned count;
+ pj_str_t name;
+ port_data *sine;
+
+ PJ_ASSERT_RETURN(pool && channel_count > 0 && channel_count <= 2,
+ PJ_EINVAL);
+
+ port = pj_pool_zalloc(pool, sizeof(pjmedia_port));
+ PJ_ASSERT_RETURN(port != NULL, PJ_ENOMEM);
+
+ /* Fill in port info. */
+ name = pj_str("sine generator");
+ pjmedia_port_info_init(&port->info, &name,
+ PJMEDIA_SIG_CLASS_PORT_AUD('s', 'i'),
+ sampling_rate,
+ channel_count,
+ 16, sampling_rate * 20 / 1000 * channel_count);
+
+ /* Set the function to feed frame */
+ port->get_frame = &sine_get_frame;
+
+ /* Create sine port data */
+ port->port_data.pdata = sine = pj_pool_zalloc(pool, sizeof(port_data));
+
+ /* Create samples */
+ count = PJMEDIA_PIA_SPF(&port->info) / channel_count;
+ sine->samples = pj_pool_alloc(pool, count * sizeof(pj_int16_t));
+ PJ_ASSERT_RETURN(sine->samples != NULL, PJ_ENOMEM);
+
+ /* initialise sinusoidal wavetable */
+ for( i=0; i<count; i++ )
+ {
+ sine->samples[i] = (pj_int16_t) (10000.0 *
+ sin(((double)i/(double)count) * M_PI * 8.) );
+ }
+
+ *p_port = port;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Show usage */
+static void usage(void)
+{
+ puts("");
+ puts("Usage: playsine [nchannel]");
+ puts("");
+ puts("where");
+ puts(" nchannel is number of audio channels (1 for mono, or 2 for stereo).");
+ puts(" Default is 1 (mono).");
+ puts("");
+}
+
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+ pj_caching_pool cp;
+ pjmedia_endpt *med_endpt;
+ pj_pool_t *pool;
+ pjmedia_port *sine_port;
+ pjmedia_snd_port *snd_port;
+ char tmp[10];
+ int channel_count = 1;
+ pj_status_t status;
+
+ if (argc == 2) {
+ channel_count = atoi(argv[1]);
+ if (channel_count < 1 || channel_count > 2) {
+ puts("Error: invalid arguments");
+ usage();
+ return 1;
+ }
+ }
+
+ /* Must init PJLIB first: */
+ status = pj_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);
+
+ /*
+ * Initialize media endpoint.
+ * This will implicitly initialize PJMEDIA too.
+ */
+ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Create memory pool for our sine generator */
+ pool = pj_pool_create( &cp.factory, /* pool factory */
+ "wav", /* pool name. */
+ 4000, /* init size */
+ 4000, /* increment size */
+ NULL /* callback on error */
+ );
+
+ /* Create a media port to generate sine wave samples. */
+ status = create_sine_port( pool, /* memory pool */
+ 11025, /* sampling rate */
+ channel_count,/* # of channels */
+ &sine_port /* returned port */
+ );
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create sine port", status);
+ return 1;
+ }
+
+ /* Create sound player port. */
+ status = pjmedia_snd_port_create_player(
+ pool, /* pool */
+ -1, /* use default dev. */
+ PJMEDIA_PIA_SRATE(&sine_port->info),/* clock rate. */
+ PJMEDIA_PIA_CCNT(&sine_port->info),/* # of channels. */
+ PJMEDIA_PIA_SPF(&sine_port->info), /* samples per frame. */
+ PJMEDIA_PIA_BITS(&sine_port->info),/* bits per sample. */
+ 0, /* options */
+ &snd_port /* returned port */
+ );
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to open sound device", status);
+ return 1;
+ }
+
+ /* Connect sine generator port to the sound player
+ * Stream playing will commence immediately.
+ */
+ status = pjmedia_snd_port_connect( snd_port, sine_port);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+
+ /*
+ * Audio should be playing in a loop now, using sound device's thread.
+ */
+
+
+ /* Sleep to allow log messages to flush */
+ pj_thread_sleep(100);
+
+
+ puts("Playing sine wave..");
+ puts("");
+ puts("Press <ENTER> to stop playing and quit");
+
+ if (fgets(tmp, sizeof(tmp), stdin) == NULL) {
+ puts("EOF while reading stdin, will quit now..");
+ }
+
+
+ /* Start deinitialization: */
+
+ /* Disconnect sound port from file port */
+ status = pjmedia_snd_port_disconnect(snd_port);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Without this sleep, Windows/DirectSound will repeteadly
+ * play the last frame during destroy.
+ */
+ pj_thread_sleep(100);
+
+ /* Destroy sound device */
+ status = pjmedia_snd_port_destroy( snd_port );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Destroy sine generator */
+ status = pjmedia_port_destroy( sine_port );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Release application pool */
+ pj_pool_release( pool );
+
+ /* Destroy media endpoint. */
+ pjmedia_endpt_destroy( med_endpt );
+
+ /* Destroy pool factory */
+ pj_caching_pool_destroy( &cp );
+
+ /* Shutdown PJLIB */
+ pj_shutdown();
+
+
+ /* Done. */
+ return 0;
+}
diff --git a/pjsip-apps/src/samples/proxy.h b/pjsip-apps/src/samples/proxy.h
new file mode 100644
index 0000000..2e1b383
--- /dev/null
+++ b/pjsip-apps/src/samples/proxy.h
@@ -0,0 +1,585 @@
+/* $Id: proxy.h 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+#include <pjsip.h>
+#include <pjlib-util.h>
+#include <pjlib.h>
+
+
+/* Options */
+static struct global_struct
+{
+ pj_caching_pool cp;
+ pjsip_endpoint *endpt;
+ int port;
+ pj_pool_t *pool;
+
+ pj_thread_t *thread;
+ pj_bool_t quit_flag;
+
+ pj_bool_t record_route;
+
+ unsigned name_cnt;
+ pjsip_host_port name[16];
+} global;
+
+
+
+static void app_perror(const char *msg, pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(1,(THIS_FILE, "%s: %s", msg, errmsg));
+}
+
+
+static void usage(void)
+{
+ puts("Options:\n"
+ "\n"
+ " -p, --port N Set local listener port to N\n"
+ " -R, --rr Perform record routing\n"
+ " -L, --log-level N Set log level to N (default: 4)\n"
+ " -h, --help Show this help screen\n"
+ );
+}
+
+
+static pj_status_t init_options(int argc, char *argv[])
+{
+ struct pj_getopt_option long_opt[] = {
+ { "port", 1, 0, 'p'},
+ { "rr", 0, 0, 'R'},
+ { "log-level", 1, 0, 'L'},
+ { "help", 0, 0, 'h'},
+ { NULL, 0, 0, 0}
+ };
+ int c;
+ int opt_ind;
+
+ pj_optind = 0;
+ while((c=pj_getopt_long(argc, argv, "p:L:Rh", long_opt, &opt_ind))!=-1) {
+ switch (c) {
+ case 'p':
+ global.port = atoi(pj_optarg);
+ printf("Port is set to %d\n", global.port);
+ break;
+
+ case 'R':
+ global.record_route = PJ_TRUE;
+ printf("Using record route mode\n");
+ break;
+
+ case 'L':
+ pj_log_set_level(atoi(pj_optarg));
+ break;
+
+ case 'h':
+ usage();
+ return -1;
+
+ default:
+ puts("Unknown option. Run with --help for help.");
+ return -1;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * This is a very simple PJSIP module, whose sole purpose is to display
+ * incoming and outgoing messages to log. This module will have priority
+ * higher than transport layer, which means:
+ *
+ * - incoming messages will come to this module first before reaching
+ * transaction layer.
+ *
+ * - outgoing messages will come to this module last, after the message
+ * has been 'printed' to contiguous buffer by transport layer and
+ * appropriate transport instance has been decided for this message.
+ *
+ */
+
+/* Notification on incoming messages */
+static pj_bool_t logging_on_rx_msg(pjsip_rx_data *rdata)
+{
+ PJ_LOG(5,(THIS_FILE, "RX %d bytes %s from %s %s:%d:\n"
+ "%.*s\n"
+ "--end msg--",
+ rdata->msg_info.len,
+ pjsip_rx_data_get_info(rdata),
+ rdata->tp_info.transport->type_name,
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port,
+ (int)rdata->msg_info.len,
+ rdata->msg_info.msg_buf));
+
+ /* Always return false, otherwise messages will not get processed! */
+ return PJ_FALSE;
+}
+
+/* Notification on outgoing messages */
+static pj_status_t logging_on_tx_msg(pjsip_tx_data *tdata)
+{
+
+ /* Important note:
+ * tp_info field is only valid after outgoing messages has passed
+ * transport layer. So don't try to access tp_info when the module
+ * has lower priority than transport layer.
+ */
+
+ PJ_LOG(5,(THIS_FILE, "TX %d bytes %s to %s %s:%d:\n"
+ "%.*s\n"
+ "--end msg--",
+ (tdata->buf.cur - tdata->buf.start),
+ pjsip_tx_data_get_info(tdata),
+ tdata->tp_info.transport->type_name,
+ tdata->tp_info.dst_name,
+ tdata->tp_info.dst_port,
+ (int)(tdata->buf.cur - tdata->buf.start),
+ tdata->buf.start));
+
+ /* Always return success, otherwise message will not get sent! */
+ return PJ_SUCCESS;
+}
+
+/* The module instance. */
+static pjsip_module mod_msg_logger =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-msg-logger", 14 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &logging_on_rx_msg, /* on_rx_request() */
+ &logging_on_rx_msg, /* on_rx_response() */
+ &logging_on_tx_msg, /* on_tx_request. */
+ &logging_on_tx_msg, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+
+};
+
+
+static pj_status_t init_stack(void)
+{
+ pj_status_t status;
+
+ /* Must init PJLIB first: */
+ status = pj_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+ /* Then init PJLIB-UTIL: */
+ status = pjlib_util_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+ /* Must create a pool factory before we can allocate any memory. */
+ pj_caching_pool_init(&global.cp, &pj_pool_factory_default_policy, 0);
+
+ /* Create the endpoint: */
+ status = pjsip_endpt_create(&global.cp.factory, NULL, &global.endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Init transaction layer for stateful proxy only */
+#if STATEFUL
+ status = pjsip_tsx_layer_init_module(global.endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+#endif
+
+ /* Create listening transport */
+ {
+ pj_sockaddr_in addr;
+
+ addr.sin_family = pj_AF_INET();
+ addr.sin_addr.s_addr = 0;
+ addr.sin_port = pj_htons((pj_uint16_t)global.port);
+
+ status = pjsip_udp_transport_start( global.endpt, &addr,
+ NULL, 1, NULL);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Create pool for the application */
+ global.pool = pj_pool_create(&global.cp.factory, "proxyapp",
+ 4000, 4000, NULL);
+
+ /* Register the logger module */
+ pjsip_endpt_register_module(global.endpt, &mod_msg_logger);
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t init_proxy(void)
+{
+ pj_sockaddr pri_addr;
+ pj_sockaddr addr_list[16];
+ unsigned addr_cnt = PJ_ARRAY_SIZE(addr_list);
+ unsigned i;
+
+ /* List all names matching local endpoint.
+ * Note that PJLIB version 0.6 and newer has a function to
+ * enumerate local IP interface (pj_enum_ip_interface()), so
+ * by using it would be possible to list all IP interfaces in
+ * this host.
+ */
+
+ /* The first address is important since this would be the one
+ * to be added in Record-Route.
+ */
+ if (pj_gethostip(pj_AF_INET(), &pri_addr)==PJ_SUCCESS) {
+ pj_strdup2(global.pool, &global.name[global.name_cnt].host,
+ pj_inet_ntoa(pri_addr.ipv4.sin_addr));
+ global.name[global.name_cnt].port = global.port;
+ global.name_cnt++;
+ }
+
+ /* Get the rest of IP interfaces */
+ if (pj_enum_ip_interface(pj_AF_INET(), &addr_cnt, addr_list) == PJ_SUCCESS) {
+ for (i=0; i<addr_cnt; ++i) {
+
+ if (addr_list[i].ipv4.sin_addr.s_addr == pri_addr.ipv4.sin_addr.s_addr)
+ continue;
+
+ pj_strdup2(global.pool, &global.name[global.name_cnt].host,
+ pj_inet_ntoa(addr_list[i].ipv4.sin_addr));
+ global.name[global.name_cnt].port = global.port;
+ global.name_cnt++;
+ }
+ }
+
+ /* Add loopback address. */
+#if PJ_IP_HELPER_IGNORE_LOOPBACK_IF
+ global.name[global.name_cnt].host = pj_str("127.0.0.1");
+ global.name[global.name_cnt].port = global.port;
+ global.name_cnt++;
+#endif
+
+ global.name[global.name_cnt].host = *pj_gethostname();
+ global.name[global.name_cnt].port = global.port;
+ global.name_cnt++;
+
+ global.name[global.name_cnt].host = pj_str("localhost");
+ global.name[global.name_cnt].port = global.port;
+ global.name_cnt++;
+
+ PJ_LOG(3,(THIS_FILE, "Proxy started, listening on port %d", global.port));
+ PJ_LOG(3,(THIS_FILE, "Local host aliases:"));
+ for (i=0; i<global.name_cnt; ++i) {
+ PJ_LOG(3,(THIS_FILE, " %.*s:%d",
+ (int)global.name[i].host.slen,
+ global.name[i].host.ptr,
+ global.name[i].port));
+ }
+
+ if (global.record_route) {
+ PJ_LOG(3,(THIS_FILE, "Using Record-Route mode"));
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+#if PJ_HAS_THREADS
+static int worker_thread(void *p)
+{
+ pj_time_val delay = {0, 10};
+
+ PJ_UNUSED_ARG(p);
+
+ while (!global.quit_flag) {
+ pjsip_endpt_handle_events(global.endpt, &delay);
+ }
+
+ return 0;
+}
+#endif
+
+
+/* Utility to determine if URI is local to this host. */
+static pj_bool_t is_uri_local(const pjsip_sip_uri *uri)
+{
+ unsigned i;
+ for (i=0; i<global.name_cnt; ++i) {
+ if ((uri->port == global.name[i].port ||
+ (uri->port==0 && global.name[i].port==5060)) &&
+ pj_stricmp(&uri->host, &global.name[i].host)==0)
+ {
+ /* Match */
+ return PJ_TRUE;
+ }
+ }
+
+ /* Doesn't match */
+ return PJ_FALSE;
+}
+
+
+/* Proxy utility to verify incoming requests.
+ * Return non-zero if verification failed.
+ */
+static pj_status_t proxy_verify_request(pjsip_rx_data *rdata)
+{
+ const pj_str_t STR_PROXY_REQUIRE = {"Proxy-Require", 13};
+
+ /* RFC 3261 Section 16.3 Request Validation */
+
+ /* Before an element can proxy a request, it MUST verify the message's
+ * validity. A valid message must pass the following checks:
+ *
+ * 1. Reasonable Syntax
+ * 2. URI scheme
+ * 3. Max-Forwards
+ * 4. (Optional) Loop Detection
+ * 5. Proxy-Require
+ * 6. Proxy-Authorization
+ */
+
+ /* 1. Reasonable Syntax.
+ * This would have been checked by transport layer.
+ */
+
+ /* 2. URI scheme.
+ * We only want to support "sip:" URI scheme for this simple proxy.
+ */
+ if (!PJSIP_URI_SCHEME_IS_SIP(rdata->msg_info.msg->line.req.uri)) {
+ pjsip_endpt_respond_stateless(global.endpt, rdata,
+ PJSIP_SC_UNSUPPORTED_URI_SCHEME, NULL,
+ NULL, NULL);
+ return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_UNSUPPORTED_URI_SCHEME);
+ }
+
+ /* 3. Max-Forwards.
+ * Send error if Max-Forwards is 1 or lower.
+ */
+ if (rdata->msg_info.max_fwd && rdata->msg_info.max_fwd->ivalue <= 1) {
+ pjsip_endpt_respond_stateless(global.endpt, rdata,
+ PJSIP_SC_TOO_MANY_HOPS, NULL,
+ NULL, NULL);
+ return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_TOO_MANY_HOPS);
+ }
+
+ /* 4. (Optional) Loop Detection.
+ * Nah, we don't do that with this simple proxy.
+ */
+
+ /* 5. Proxy-Require */
+ if (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_PROXY_REQUIRE,
+ NULL) != NULL)
+ {
+ pjsip_endpt_respond_stateless(global.endpt, rdata,
+ PJSIP_SC_BAD_EXTENSION, NULL,
+ NULL, NULL);
+ return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EXTENSION);
+ }
+
+ /* 6. Proxy-Authorization.
+ * Nah, we don't require any authorization with this sample.
+ */
+
+ return PJ_SUCCESS;
+}
+
+
+/* Process route information in the reqeust */
+static pj_status_t proxy_process_routing(pjsip_tx_data *tdata)
+{
+ pjsip_sip_uri *target;
+ pjsip_route_hdr *hroute;
+
+ /* RFC 3261 Section 16.4 Route Information Preprocessing */
+
+ target = (pjsip_sip_uri*) tdata->msg->line.req.uri;
+
+ /* The proxy MUST inspect the Request-URI of the request. If the
+ * Request-URI of the request contains a value this proxy previously
+ * placed into a Record-Route header field (see Section 16.6 item 4),
+ * the proxy MUST replace the Request-URI in the request with the last
+ * value from the Route header field, and remove that value from the
+ * Route header field. The proxy MUST then proceed as if it received
+ * this modified request.
+ */
+ if (is_uri_local(target)) {
+ pjsip_route_hdr *r;
+ pjsip_sip_uri *uri;
+
+ /* Find the first Route header */
+ r = hroute = (pjsip_route_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL);
+ if (r == NULL) {
+ /* No Route header. This request is destined for this proxy. */
+ return PJ_SUCCESS;
+ }
+
+ /* Find the last Route header */
+ while ( (r=(pjsip_route_hdr*)pjsip_msg_find_hdr(tdata->msg,
+ PJSIP_H_ROUTE,
+ r->next)) != NULL )
+ {
+ hroute = r;
+ }
+
+ /* If the last Route header doesn't have ";lr" parameter, then
+ * this is a strict-routed request indeed, and we follow the steps
+ * in processing strict-route requests above.
+ *
+ * But if it does contain ";lr" parameter, skip the strict-route
+ * processing.
+ */
+ uri = (pjsip_sip_uri*)
+ pjsip_uri_get_uri(&hroute->name_addr);
+ if (uri->lr_param == 0) {
+ /* Yes this is strict route, so:
+ * - replace req URI with the URI in Route header,
+ * - remove the Route header,
+ * - proceed as if it received this modified request.
+ */
+ tdata->msg->line.req.uri = hroute->name_addr.uri;
+ target = (pjsip_sip_uri*) tdata->msg->line.req.uri;
+ pj_list_erase(hroute);
+ }
+ }
+
+ /* If the Request-URI contains a maddr parameter, the proxy MUST check
+ * to see if its value is in the set of addresses or domains the proxy
+ * is configured to be responsible for. If the Request-URI has a maddr
+ * parameter with a value the proxy is responsible for, and the request
+ * was received using the port and transport indicated (explicitly or by
+ * default) in the Request-URI, the proxy MUST strip the maddr and any
+ * non-default port or transport parameter and continue processing as if
+ * those values had not been present in the request.
+ */
+ if (target->maddr_param.slen != 0) {
+ pjsip_sip_uri maddr_uri;
+
+ maddr_uri.host = target->maddr_param;
+ maddr_uri.port = global.port;
+
+ if (is_uri_local(&maddr_uri)) {
+ target->maddr_param.slen = 0;
+ target->port = 0;
+ target->transport_param.slen = 0;
+ }
+ }
+
+ /* If the first value in the Route header field indicates this proxy,
+ * the proxy MUST remove that value from the request.
+ */
+ hroute = (pjsip_route_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL);
+ if (hroute && is_uri_local((pjsip_sip_uri*)hroute->name_addr.uri)) {
+ pj_list_erase(hroute);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* Postprocess the request before forwarding it */
+static void proxy_postprocess(pjsip_tx_data *tdata)
+{
+ /* Optionally record-route */
+ if (global.record_route) {
+ char uribuf[128];
+ pj_str_t uri;
+ const pj_str_t H_RR = { "Record-Route", 12 };
+ pjsip_generic_string_hdr *rr;
+
+ pj_ansi_snprintf(uribuf, sizeof(uribuf), "<sip:%.*s:%d;lr>",
+ (int)global.name[0].host.slen,
+ global.name[0].host.ptr,
+ global.name[0].port);
+ uri = pj_str(uribuf);
+ rr = pjsip_generic_string_hdr_create(tdata->pool,
+ &H_RR, &uri);
+ pjsip_msg_insert_first_hdr(tdata->msg, (pjsip_hdr*)rr);
+ }
+}
+
+
+/* Calculate new target for the request */
+static pj_status_t proxy_calculate_target(pjsip_rx_data *rdata,
+ pjsip_tx_data *tdata)
+{
+ pjsip_sip_uri *target;
+
+ /* RFC 3261 Section 16.5 Determining Request Targets */
+
+ target = (pjsip_sip_uri*) tdata->msg->line.req.uri;
+
+ /* If the Request-URI of the request contains an maddr parameter, the
+ * Request-URI MUST be placed into the target set as the only target
+ * URI, and the proxy MUST proceed to Section 16.6.
+ */
+ if (target->maddr_param.slen) {
+ proxy_postprocess(tdata);
+ return PJ_SUCCESS;
+ }
+
+
+ /* If the domain of the Request-URI indicates a domain this element is
+ * not responsible for, the Request-URI MUST be placed into the target
+ * set as the only target, and the element MUST proceed to the task of
+ * Request Forwarding (Section 16.6).
+ */
+ if (!is_uri_local(target)) {
+ proxy_postprocess(tdata);
+ return PJ_SUCCESS;
+ }
+
+ /* If the target set for the request has not been predetermined as
+ * described above, this implies that the element is responsible for the
+ * domain in the Request-URI, and the element MAY use whatever mechanism
+ * it desires to determine where to send the request.
+ */
+
+ /* We're not interested to receive request destined to us, so
+ * respond with 404/Not Found (only if request is not ACK!).
+ */
+ if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
+ pjsip_endpt_respond_stateless(global.endpt, rdata,
+ PJSIP_SC_NOT_FOUND, NULL,
+ NULL, NULL);
+ }
+
+ /* Delete the request since we're not forwarding it */
+ pjsip_tx_data_dec_ref(tdata);
+
+ return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_FOUND);
+}
+
+
+/* Destroy stack */
+static void destroy_stack(void)
+{
+ pjsip_endpt_destroy(global.endpt);
+ pj_pool_release(global.pool);
+ pj_caching_pool_destroy(&global.cp);
+
+ pj_shutdown();
+}
+
diff --git a/pjsip-apps/src/samples/recfile.c b/pjsip-apps/src/samples/recfile.c
new file mode 100644
index 0000000..fedd5c6
--- /dev/null
+++ b/pjsip-apps/src/samples/recfile.c
@@ -0,0 +1,202 @@
+/* $Id: recfile.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+/**
+ * \page page_pjmedia_samples_recfile_c Samples: Capturing Audio to WAV File
+ *
+ * In this example, we capture audio from the sound device and save it to
+ * WAVE file.
+ *
+ * @see page_pjmedia_samples_playfile_c
+ *
+ * This file is pjsip-apps/src/samples/recfile.c
+ *
+ * \includelineno recfile.c
+ */
+
+#include <pjmedia.h>
+#include <pjlib.h>
+
+#include <stdio.h>
+
+/* For logging purpose. */
+#define THIS_FILE "recfile.c"
+
+
+/* Configs */
+#define CLOCK_RATE 44100
+#define NCHANNELS 2
+#define SAMPLES_PER_FRAME (NCHANNELS * (CLOCK_RATE * 10 / 1000))
+#define BITS_PER_SAMPLE 16
+
+
+static const char *desc =
+ " FILE \n"
+ " recfile.c \n"
+ " \n"
+ " PURPOSE: \n"
+ " Record microphone to WAVE file. \n"
+ " \n"
+ " USAGE: \n"
+ " recfile FILE.WAV \n"
+ "";
+
+
+/* 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)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ PJ_UNUSED_ARG(sender);
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ printf("%s: %s [code=%d]\n", title, errmsg, status);
+ return 1;
+}
+
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+ pj_caching_pool cp;
+ pjmedia_endpt *med_endpt;
+ pj_pool_t *pool;
+ pjmedia_port *file_port;
+ pjmedia_snd_port *snd_port;
+ char tmp[10];
+ pj_status_t status;
+
+
+ /* Verify cmd line arguments. */
+ if (argc != 2) {
+ puts("");
+ puts(desc);
+ return 0;
+ }
+
+ /* Must init PJLIB first: */
+ status = pj_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);
+
+ /*
+ * Initialize media endpoint.
+ * This will implicitly initialize PJMEDIA too.
+ */
+ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Create memory pool for our file player */
+ pool = pj_pool_create( &cp.factory, /* pool factory */
+ "app", /* pool name. */
+ 4000, /* init size */
+ 4000, /* increment size */
+ NULL /* callback on error */
+ );
+
+ /* Create WAVE file writer port. */
+ status = pjmedia_wav_writer_port_create( pool, argv[1],
+ CLOCK_RATE,
+ NCHANNELS,
+ SAMPLES_PER_FRAME,
+ BITS_PER_SAMPLE,
+ 0, 0,
+ &file_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to open WAV file for writing", status);
+ return 1;
+ }
+
+ /* Create sound player port. */
+ status = pjmedia_snd_port_create_rec(
+ pool, /* pool */
+ -1, /* use default dev. */
+ PJMEDIA_PIA_SRATE(&file_port->info),/* clock rate. */
+ PJMEDIA_PIA_CCNT(&file_port->info),/* # of channels. */
+ PJMEDIA_PIA_SPF(&file_port->info), /* samples per frame. */
+ PJMEDIA_PIA_BITS(&file_port->info),/* bits per sample. */
+ 0, /* options */
+ &snd_port /* returned port */
+ );
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to open sound device", status);
+ return 1;
+ }
+
+ /* Connect file port to the sound player.
+ * Stream playing will commence immediately.
+ */
+ status = pjmedia_snd_port_connect( snd_port, file_port);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+
+ /*
+ * Recording should be started now.
+ */
+
+
+ /* Sleep to allow log messages to flush */
+ pj_thread_sleep(10);
+
+
+ printf("Recodring %s..\n", argv[1]);
+ puts("");
+ puts("Press <ENTER> to stop recording and quit");
+
+ if (fgets(tmp, sizeof(tmp), stdin) == NULL) {
+ puts("EOF while reading stdin, will quit now..");
+ }
+
+
+ /* Start deinitialization: */
+
+ /* Destroy sound device */
+ status = pjmedia_snd_port_destroy( snd_port );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Destroy file port */
+ status = pjmedia_port_destroy( file_port );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Release application pool */
+ pj_pool_release( pool );
+
+ /* Destroy media endpoint. */
+ pjmedia_endpt_destroy( med_endpt );
+
+ /* Destroy pool factory */
+ pj_caching_pool_destroy( &cp );
+
+ /* Shutdown PJLIB */
+ pj_shutdown();
+
+
+ /* Done. */
+ return 0;
+}
diff --git a/pjsip-apps/src/samples/resampleplay.c b/pjsip-apps/src/samples/resampleplay.c
new file mode 100644
index 0000000..c73b2e3
--- /dev/null
+++ b/pjsip-apps/src/samples/resampleplay.c
@@ -0,0 +1,232 @@
+/* $Id: resampleplay.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+/**
+ * \page page_pjmedia_samples_resampleplay_c Samples: Using Resample Port
+ *
+ * This example demonstrates how to use @ref PJMEDIA_RESAMPLE_PORT to
+ * change the sampling rate of the media streams.
+ *
+ * This file is pjsip-apps/src/samples/resampleplay.c
+ *
+ * \includelineno resampleplay.c
+ */
+
+#include <pjmedia.h>
+#include <pjlib-util.h>
+#include <pjlib.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "util.h"
+
+/* For logging purpose. */
+#define THIS_FILE "resampleplay.c"
+
+
+static const char *desc =
+" FILE \n"
+" \n"
+" resampleplay.c \n"
+" \n"
+" PURPOSE \n"
+" \n"
+" Demonstrate how use resample port to play a WAV file to sound \n"
+" device using different sampling rate. \n"
+" \n"
+" USAGE \n"
+" \n"
+" resampleplay [options] FILE.WAV \n"
+" \n"
+" where options: \n"
+SND_USAGE
+" \n"
+" The WAV file could have mono or stereo channels with arbitrary \n"
+" sampling rate, but MUST contain uncompressed (i.e. 16bit) PCM. \n";
+
+
+int main(int argc, char *argv[])
+{
+ pj_caching_pool cp;
+ pjmedia_endpt *med_endpt;
+ pj_pool_t *pool;
+ pjmedia_port *file_port;
+ pjmedia_port *resample_port;
+ pjmedia_snd_port *snd_port;
+ char tmp[10];
+ pj_status_t status;
+
+ int dev_id = -1;
+ int sampling_rate = CLOCK_RATE;
+ int channel_count = NCHANNELS;
+ int samples_per_frame = NSAMPLES;
+ int bits_per_sample = NBITS;
+ //int ptime;
+ //int down_samples;
+
+ /* Must init PJLIB first: */
+ status = pj_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Get options */
+ if (get_snd_options(THIS_FILE, argc, argv, &dev_id, &sampling_rate,
+ &channel_count, &samples_per_frame, &bits_per_sample))
+ {
+ puts("");
+ puts(desc);
+ return 1;
+ }
+
+ if (!argv[pj_optind]) {
+ puts("Error: no file is specified");
+ puts(desc);
+ return 1;
+ }
+
+ /* Must create a pool factory before we can allocate any memory. */
+ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+
+ /*
+ * Initialize media endpoint.
+ * This will implicitly initialize PJMEDIA too.
+ */
+ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Create memory pool for our file player */
+ pool = pj_pool_create( &cp.factory, /* pool factory */
+ "app", /* pool name. */
+ 4000, /* init size */
+ 4000, /* increment size */
+ NULL /* callback on error */
+ );
+
+ /* Create the file port. */
+ status = pjmedia_wav_player_port_create( pool, argv[pj_optind], 0, 0,
+ 0, &file_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to open file", status);
+ return 1;
+ }
+
+ /* File must have same number of channels. */
+ if (PJMEDIA_PIA_CCNT(&file_port->info) != (unsigned)channel_count) {
+ PJ_LOG(3,(THIS_FILE, "Error: file has different number of channels. "
+ "Perhaps you'd need -c option?"));
+ pjmedia_port_destroy(file_port);
+ return 1;
+ }
+
+ /* Calculate number of samples per frame to be taken from file port */
+ //ptime = samples_per_frame * 1000 / sampling_rate;
+
+ /* Create the resample port. */
+ status = pjmedia_resample_port_create( pool, file_port,
+ sampling_rate, 0,
+ &resample_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create resample port", status);
+ return 1;
+ }
+
+ /* Create sound player port. */
+ status = pjmedia_snd_port_create(
+ pool, /* pool */
+ dev_id, /* device */
+ dev_id, /* device */
+ sampling_rate, /* clock rate. */
+ channel_count, /* # of channels. */
+ samples_per_frame, /* samples per frame. */
+ bits_per_sample, /* bits per sample. */
+ 0, /* options */
+ &snd_port /* returned port */
+ );
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to open sound device", status);
+ return 1;
+ }
+
+ /* Connect resample port to sound device */
+ status = pjmedia_snd_port_connect( snd_port, resample_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error connecting sound ports", status);
+ return 1;
+ }
+
+
+ /* Dump memory usage */
+ dump_pool_usage(THIS_FILE, &cp);
+
+ /*
+ * File should be playing and looping now, using sound device's thread.
+ */
+
+
+ /* Sleep to allow log messages to flush */
+ pj_thread_sleep(100);
+
+
+ printf("Playing %s at sampling rate %d (original file sampling rate=%d)\n",
+ argv[pj_optind], sampling_rate,
+ PJMEDIA_PIA_SRATE(&file_port->info));
+ puts("");
+ puts("Press <ENTER> to stop playing and quit");
+
+ if (fgets(tmp, sizeof(tmp), stdin) == NULL) {
+ puts("EOF while reading stdin, will quit now..");
+ }
+
+ /* Start deinitialization: */
+
+
+ /* Destroy sound device */
+ status = pjmedia_snd_port_destroy( snd_port );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Destroy resample port.
+ * This will destroy all downstream ports (e.g. the file port)
+ */
+ status = pjmedia_port_destroy( resample_port );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Release application pool */
+ pj_pool_release( pool );
+
+ /* Destroy media endpoint. */
+ pjmedia_endpt_destroy( med_endpt );
+
+ /* Destroy pool factory */
+ pj_caching_pool_destroy( &cp );
+
+ /* Shutdown PJLIB */
+ pj_shutdown();
+
+
+ /* Done. */
+ return 0;
+
+}
+
+
+
diff --git a/pjsip-apps/src/samples/simple_pjsua.c b/pjsip-apps/src/samples/simple_pjsua.c
new file mode 100644
index 0000000..7716f7f
--- /dev/null
+++ b/pjsip-apps/src/samples/simple_pjsua.c
@@ -0,0 +1,201 @@
+/* $Id: simple_pjsua.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+/**
+ * simple_pjsua.c
+ *
+ * This is a very simple but fully featured SIP user agent, with the
+ * following capabilities:
+ * - SIP registration
+ * - Making and receiving call
+ * - Audio/media to sound device.
+ *
+ * Usage:
+ * - To make outgoing call, start simple_pjsua with the URL of remote
+ * destination to contact.
+ * E.g.:
+ * simpleua sip:user@remote
+ *
+ * - Incoming calls will automatically be answered with 200.
+ *
+ * This program will quit once it has completed a single call.
+ */
+
+#include <pjsua-lib/pjsua.h>
+
+#define THIS_FILE "APP"
+
+#define SIP_DOMAIN "example.com"
+#define SIP_USER "alice"
+#define SIP_PASSWD "secret"
+
+
+/* Callback called by the library upon receiving incoming call */
+static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
+ pjsip_rx_data *rdata)
+{
+ pjsua_call_info ci;
+
+ PJ_UNUSED_ARG(acc_id);
+ PJ_UNUSED_ARG(rdata);
+
+ pjsua_call_get_info(call_id, &ci);
+
+ PJ_LOG(3,(THIS_FILE, "Incoming call from %.*s!!",
+ (int)ci.remote_info.slen,
+ ci.remote_info.ptr));
+
+ /* Automatically answer incoming calls with 200/OK */
+ pjsua_call_answer(call_id, 200, NULL, NULL);
+}
+
+/* Callback called by the library when call's state has changed */
+static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
+{
+ pjsua_call_info ci;
+
+ PJ_UNUSED_ARG(e);
+
+ pjsua_call_get_info(call_id, &ci);
+ PJ_LOG(3,(THIS_FILE, "Call %d state=%.*s", call_id,
+ (int)ci.state_text.slen,
+ ci.state_text.ptr));
+}
+
+/* Callback called by the library when call's media state has changed */
+static void on_call_media_state(pjsua_call_id call_id)
+{
+ pjsua_call_info ci;
+
+ pjsua_call_get_info(call_id, &ci);
+
+ if (ci.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
+ // When media is active, connect call to sound device.
+ pjsua_conf_connect(ci.conf_slot, 0);
+ pjsua_conf_connect(0, ci.conf_slot);
+ }
+}
+
+/* Display error and exit application */
+static void error_exit(const char *title, pj_status_t status)
+{
+ pjsua_perror(THIS_FILE, title, status);
+ pjsua_destroy();
+ exit(1);
+}
+
+/*
+ * main()
+ *
+ * argv[1] may contain URL to call.
+ */
+int main(int argc, char *argv[])
+{
+ pjsua_acc_id acc_id;
+ pj_status_t status;
+
+ /* Create pjsua first! */
+ status = pjsua_create();
+ if (status != PJ_SUCCESS) error_exit("Error in pjsua_create()", status);
+
+ /* If argument is specified, it's got to be a valid SIP URL */
+ if (argc > 1) {
+ status = pjsua_verify_url(argv[1]);
+ if (status != PJ_SUCCESS) error_exit("Invalid URL in argv", status);
+ }
+
+ /* Init pjsua */
+ {
+ pjsua_config cfg;
+ pjsua_logging_config log_cfg;
+
+ pjsua_config_default(&cfg);
+ cfg.cb.on_incoming_call = &on_incoming_call;
+ cfg.cb.on_call_media_state = &on_call_media_state;
+ cfg.cb.on_call_state = &on_call_state;
+
+ pjsua_logging_config_default(&log_cfg);
+ log_cfg.console_level = 4;
+
+ status = pjsua_init(&cfg, &log_cfg, NULL);
+ if (status != PJ_SUCCESS) error_exit("Error in pjsua_init()", status);
+ }
+
+ /* Add UDP transport. */
+ {
+ pjsua_transport_config cfg;
+
+ pjsua_transport_config_default(&cfg);
+ cfg.port = 5060;
+ status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg, NULL);
+ if (status != PJ_SUCCESS) error_exit("Error creating transport", status);
+ }
+
+ /* Initialization is done, now start pjsua */
+ status = pjsua_start();
+ if (status != PJ_SUCCESS) error_exit("Error starting pjsua", status);
+
+ /* Register to SIP server by creating SIP account. */
+ {
+ pjsua_acc_config cfg;
+
+ pjsua_acc_config_default(&cfg);
+ cfg.id = pj_str("sip:" SIP_USER "@" SIP_DOMAIN);
+ cfg.reg_uri = pj_str("sip:" SIP_DOMAIN);
+ cfg.cred_count = 1;
+ cfg.cred_info[0].realm = pj_str(SIP_DOMAIN);
+ cfg.cred_info[0].scheme = pj_str("digest");
+ cfg.cred_info[0].username = pj_str(SIP_USER);
+ cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
+ cfg.cred_info[0].data = pj_str(SIP_PASSWD);
+
+ status = pjsua_acc_add(&cfg, PJ_TRUE, &acc_id);
+ if (status != PJ_SUCCESS) error_exit("Error adding account", status);
+ }
+
+ /* If URL is specified, make call to the URL. */
+ if (argc > 1) {
+ pj_str_t uri = pj_str(argv[1]);
+ status = pjsua_call_make_call(acc_id, &uri, 0, NULL, NULL, NULL);
+ if (status != PJ_SUCCESS) error_exit("Error making call", status);
+ }
+
+ /* Wait until user press "q" to quit. */
+ for (;;) {
+ char option[10];
+
+ puts("Press 'h' to hangup all calls, 'q' to quit");
+ if (fgets(option, sizeof(option), stdin) == NULL) {
+ puts("EOF while reading stdin, will quit now..");
+ break;
+ }
+
+ if (option[0] == 'q')
+ break;
+
+ if (option[0] == 'h')
+ pjsua_call_hangup_all();
+ }
+
+ /* Destroy pjsua */
+ pjsua_destroy();
+
+ return 0;
+}
diff --git a/pjsip-apps/src/samples/simpleua.c b/pjsip-apps/src/samples/simpleua.c
new file mode 100644
index 0000000..2906323
--- /dev/null
+++ b/pjsip-apps/src/samples/simpleua.c
@@ -0,0 +1,1030 @@
+/* $Id: simpleua.c 4051 2012-04-13 08:16:30Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+
+/**
+ * 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
+ * - Should support IPv6 (not tested)
+ * - 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 headers. */
+#include <pjsip.h>
+#include <pjmedia.h>
+#include <pjmedia-codec.h>
+#include <pjsip_ua.h>
+#include <pjsip_simple.h>
+#include <pjlib-util.h>
+#include <pjlib.h>
+
+/* For logging purpose. */
+#define THIS_FILE "simpleua.c"
+
+#include "util.h"
+
+
+/* Settings */
+#define AF pj_AF_INET() /* Change to pj_AF_INET6() for IPv6.
+ * PJ_HAS_IPV6 must be enabled and
+ * your system must support IPv6. */
+#if 0
+#define SIP_PORT 5080 /* Listening SIP port */
+#define RTP_PORT 5000 /* RTP port */
+#else
+#define SIP_PORT 5060 /* Listening SIP port */
+#define RTP_PORT 4000 /* RTP port */
+#endif
+
+#define MAX_MEDIA_CNT 2 /* Media count, set to 1 for audio
+ * only or 2 for audio and video */
+
+/*
+ * Static variables.
+ */
+
+static pj_bool_t g_complete; /* Quit flag. */
+static pjsip_endpoint *g_endpt; /* SIP endpoint. */
+static pj_caching_pool cp; /* Global pool factory. */
+
+static pjmedia_endpt *g_med_endpt; /* Media endpoint. */
+
+static pjmedia_transport_info g_med_tpinfo[MAX_MEDIA_CNT];
+ /* Socket info for media */
+static pjmedia_transport *g_med_transport[MAX_MEDIA_CNT];
+ /* Media stream transport */
+static pjmedia_sock_info g_sock_info[MAX_MEDIA_CNT];
+ /* Socket info array */
+
+/* Call variables: */
+static pjsip_inv_session *g_inv; /* Current invite session. */
+static pjmedia_stream *g_med_stream; /* Call's audio stream. */
+static pjmedia_snd_port *g_snd_port; /* Sound device. */
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+static pjmedia_vid_stream *g_med_vstream; /* Call's video stream. */
+static pjmedia_vid_port *g_vid_capturer;/* Call's video capturer. */
+static pjmedia_vid_port *g_vid_renderer;/* Call's video renderer. */
+#endif /* PJMEDIA_HAS_VIDEO */
+
+/*
+ * 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 );
+
+
+
+
+/* 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. */
+ { "mod-simpleua", 12 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+
+/* Notification on incoming messages */
+static pj_bool_t logging_on_rx_msg(pjsip_rx_data *rdata)
+{
+ PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s %s:%d:\n"
+ "%.*s\n"
+ "--end msg--",
+ rdata->msg_info.len,
+ pjsip_rx_data_get_info(rdata),
+ rdata->tp_info.transport->type_name,
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port,
+ (int)rdata->msg_info.len,
+ rdata->msg_info.msg_buf));
+
+ /* Always return false, otherwise messages will not get processed! */
+ return PJ_FALSE;
+}
+
+/* Notification on outgoing messages */
+static pj_status_t logging_on_tx_msg(pjsip_tx_data *tdata)
+{
+
+ /* Important note:
+ * tp_info field is only valid after outgoing messages has passed
+ * transport layer. So don't try to access tp_info when the module
+ * has lower priority than transport layer.
+ */
+
+ PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s %s:%d:\n"
+ "%.*s\n"
+ "--end msg--",
+ (tdata->buf.cur - tdata->buf.start),
+ pjsip_tx_data_get_info(tdata),
+ tdata->tp_info.transport->type_name,
+ tdata->tp_info.dst_name,
+ tdata->tp_info.dst_port,
+ (int)(tdata->buf.cur - tdata->buf.start),
+ tdata->buf.start));
+
+ /* Always return success, otherwise message will not get sent! */
+ return PJ_SUCCESS;
+}
+
+/* The module instance. */
+static pjsip_module msg_logger =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-msg-log", 13 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &logging_on_rx_msg, /* on_rx_request() */
+ &logging_on_rx_msg, /* on_rx_response() */
+ &logging_on_tx_msg, /* on_tx_request. */
+ &logging_on_tx_msg, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+
+};
+
+
+/*
+ * 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_pool_t *pool = NULL;
+ pj_status_t status;
+ unsigned i;
+
+ /* Must init PJLIB first: */
+ status = pj_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ pj_log_set_level(5);
+
+ /* 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 global endpoint: */
+ {
+ const pj_str_t *hostname;
+ const char *endpt_name;
+
+ /* Endpoint MUST be assigned a globally unique name.
+ * The name will be used as the hostname in Warning header.
+ */
+
+ /* For this implementation, we'll use hostname for simplicity */
+ hostname = pj_gethostname();
+ endpt_name = hostname->ptr;
+
+ /* Create the endpoint: */
+
+ status = pjsip_endpt_create(&cp.factory, endpt_name,
+ &g_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ }
+
+
+ /*
+ * 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 addr;
+
+ pj_sockaddr_init(AF, &addr, NULL, (pj_uint16_t)SIP_PORT);
+
+ if (AF == pj_AF_INET()) {
+ status = pjsip_udp_transport_start( g_endpt, &addr.ipv4, NULL,
+ 1, NULL);
+ } else if (AF == pj_AF_INET6()) {
+ status = pjsip_udp_transport_start6(g_endpt, &addr.ipv6, NULL,
+ 1, NULL);
+ } else {
+ status = PJ_EAFNOTSUP;
+ }
+
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to start UDP transport", status);
+ return 1;
+ }
+ }
+
+
+ /*
+ * 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.
+ * 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;
+
+ /* Init the callback for INVITE session: */
+ pj_bzero(&inv_cb, sizeof(inv_cb));
+ inv_cb.on_state_changed = &call_on_state_changed;
+ inv_cb.on_new_session = &call_on_forked;
+ inv_cb.on_media_update = &call_on_media_update;
+
+ /* Initialize invite session module: */
+ status = pjsip_inv_usage_init(g_endpt, &inv_cb);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ }
+
+ /* Initialize 100rel support */
+ status = pjsip_100rel_init_module(g_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /*
+ * Register our module to receive incoming requests.
+ */
+ status = pjsip_endpt_register_module( g_endpt, &mod_simpleua);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /*
+ * Register message logger module.
+ */
+ status = pjsip_endpt_register_module( g_endpt, &msg_logger);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /*
+ * Initialize media endpoint.
+ * This will implicitly initialize PJMEDIA too.
+ */
+#if PJ_HAS_THREADS
+ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &g_med_endpt);
+#else
+ status = pjmedia_endpt_create(&cp.factory,
+ pjsip_endpt_get_ioqueue(g_endpt),
+ 0, &g_med_endpt);
+#endif
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /*
+ * Add PCMA/PCMU codec to the media endpoint.
+ */
+#if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
+ status = pjmedia_codec_g711_init(g_med_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+#endif
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+ /* Init video subsystem */
+ pool = pjmedia_endpt_create_pool(g_med_endpt, "Video subsystem", 512, 512);
+ status = pjmedia_video_format_mgr_create(pool, 64, 0, NULL);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ status = pjmedia_converter_mgr_create(pool, NULL);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ status = pjmedia_vid_codec_mgr_create(pool, NULL);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ status = pjmedia_vid_dev_subsys_init(&cp.factory);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+# if defined(PJMEDIA_HAS_FFMPEG_VID_CODEC) && PJMEDIA_HAS_FFMPEG_VID_CODEC!=0
+ /* Init ffmpeg video codecs */
+ status = pjmedia_codec_ffmpeg_vid_init(NULL, &cp.factory);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+# endif /* PJMEDIA_HAS_FFMPEG_VID_CODEC */
+
+#endif /* PJMEDIA_HAS_VIDEO */
+
+ /*
+ * Create media transport used to send/receive RTP/RTCP socket.
+ * One media transport is needed for each call. Application may
+ * opt to re-use the same media transport for subsequent calls.
+ */
+ for (i = 0; i < PJ_ARRAY_SIZE(g_med_transport); ++i) {
+ status = pjmedia_transport_udp_create3(g_med_endpt, AF, NULL, NULL,
+ RTP_PORT + i*2, 0,
+ &g_med_transport[i]);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create media transport", status);
+ return 1;
+ }
+
+ /*
+ * Get socket info (address, port) of the media transport. We will
+ * need this info to create SDP (i.e. the address and port info in
+ * the SDP).
+ */
+ pjmedia_transport_info_init(&g_med_tpinfo[i]);
+ pjmedia_transport_get_info(g_med_transport[i], &g_med_tpinfo[i]);
+
+ pj_memcpy(&g_sock_info[i], &g_med_tpinfo[i].sock_info,
+ sizeof(pjmedia_sock_info));
+ }
+
+ /*
+ * If URL is specified, then make call immediately.
+ */
+ if (argc > 1) {
+ pj_sockaddr hostaddr;
+ char hostip[PJ_INET6_ADDRSTRLEN+2];
+ char temp[80];
+ pj_str_t dst_uri = pj_str(argv[1]);
+ pj_str_t local_uri;
+ pjsip_dialog *dlg;
+ pjmedia_sdp_session *local_sdp;
+ pjsip_tx_data *tdata;
+
+ if (pj_gethostip(AF, &hostaddr) != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to retrieve local host IP", status);
+ return 1;
+ }
+ pj_sockaddr_print(&hostaddr, hostip, sizeof(hostip), 2);
+
+ pj_ansi_sprintf(temp, "<sip:simpleuac@%s:%d>",
+ hostip, SIP_PORT);
+ local_uri = pj_str(temp);
+
+ /* Create UAC dialog */
+ status = pjsip_dlg_create_uac( pjsip_ua_instance(),
+ &local_uri, /* local URI */
+ &local_uri, /* local Contact */
+ &dst_uri, /* remote URI */
+ &dst_uri, /* remote target */
+ &dlg); /* dialog */
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create UAC dialog", status);
+ return 1;
+ }
+
+ /* If we expect the outgoing INVITE to be challenged, then we should
+ * put the credentials in the dialog here, with something like this:
+ *
+ {
+ pjsip_cred_info cred[1];
+
+ cred[0].realm = pj_str("sip.server.realm");
+ cred[0].scheme = pj_str("digest");
+ cred[0].username = pj_str("theuser");
+ cred[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
+ cred[0].data = pj_str("thepassword");
+
+ pjsip_auth_clt_set_credentials( &dlg->auth_sess, 1, cred);
+ }
+ *
+ */
+
+
+ /* Get the SDP body to be put in the outgoing INVITE, by asking
+ * media endpoint to create one for us.
+ */
+ status = pjmedia_endpt_create_sdp( g_med_endpt, /* the media endpt */
+ dlg->pool, /* pool. */
+ MAX_MEDIA_CNT, /* # of streams */
+ g_sock_info, /* RTP sock info */
+ &local_sdp); /* the SDP result */
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+
+ /* 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);
+
+ /* If we want the initial INVITE to travel to specific SIP proxies,
+ * then we should put the initial dialog's route set here. The final
+ * route set will be updated once a dialog has been established.
+ * To set the dialog's initial route set, we do it with something
+ * like this:
+ *
+ {
+ pjsip_route_hdr route_set;
+ pjsip_route_hdr *route;
+ const pj_str_t hname = { "Route", 5 };
+ char *uri = "sip:proxy.server;lr";
+
+ pj_list_init(&route_set);
+
+ route = pjsip_parse_hdr( dlg->pool, &hname,
+ uri, strlen(uri),
+ NULL);
+ PJ_ASSERT_RETURN(route != NULL, 1);
+ pj_list_push_back(&route_set, route);
+
+ pjsip_dlg_set_route_set(dlg, &route_set);
+ }
+ *
+ * Note that Route URI SHOULD have an ";lr" parameter!
+ */
+
+ /* 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.
+ * 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..."));
+ }
+
+
+ /* Loop until one call is completed */
+ for (;!g_complete;) {
+ pj_time_val timeout = {0, 10};
+ pjsip_endpt_handle_events(g_endpt, &timeout);
+ }
+
+ /* On exit, dump current memory usage: */
+ dump_pool_usage(THIS_FILE, &cp);
+
+ /* Destroy audio ports. Destroy the audio port first
+ * before the stream since the audio port has threads
+ * that get/put frames to the stream.
+ */
+ if (g_snd_port)
+ pjmedia_snd_port_destroy(g_snd_port);
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+ /* Destroy video ports */
+ if (g_vid_capturer)
+ pjmedia_vid_port_destroy(g_vid_capturer);
+ if (g_vid_renderer)
+ pjmedia_vid_port_destroy(g_vid_renderer);
+#endif
+
+ /* Destroy streams */
+ if (g_med_stream)
+ pjmedia_stream_destroy(g_med_stream);
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+ if (g_med_vstream)
+ pjmedia_vid_stream_destroy(g_med_vstream);
+
+ /* Deinit ffmpeg codec */
+# if defined(PJMEDIA_HAS_FFMPEG_VID_CODEC) && PJMEDIA_HAS_FFMPEG_VID_CODEC!=0
+ pjmedia_codec_ffmpeg_vid_deinit();
+# endif
+
+#endif
+
+ /* Destroy media transports */
+ for (i = 0; i < MAX_MEDIA_CNT; ++i) {
+ if (g_med_transport[i])
+ pjmedia_transport_close(g_med_transport[i]);
+ }
+
+ /* Deinit pjmedia endpoint */
+ if (g_med_endpt)
+ pjmedia_endpt_destroy(g_med_endpt);
+
+ /* Deinit pjsip endpoint */
+ if (g_endpt)
+ pjsip_endpt_destroy(g_endpt);
+
+ /* Release pool */
+ if (pool)
+ pj_pool_release(pool);
+
+ return 0;
+}
+
+
+
+/*
+ * 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)
+{
+ PJ_UNUSED_ARG(e);
+
+ if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+
+ PJ_LOG(3,(THIS_FILE, "Call DISCONNECTED [reason=%d (%s)]",
+ inv->cause,
+ pjsip_get_status_text(inv->cause)->ptr));
+
+ PJ_LOG(3,(THIS_FILE, "One call completed, application quitting..."));
+ g_complete = 1;
+
+ } else {
+
+ PJ_LOG(3,(THIS_FILE, "Call state changed to %s",
+ pjsip_inv_state_name(inv->state)));
+
+ }
+}
+
+
+/* This callback is called when dialog has forked. */
+static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
+{
+ /* To be done... */
+ PJ_UNUSED_ARG(inv);
+ PJ_UNUSED_ARG(e);
+}
+
+
+/*
+ * Callback when incoming requests outside any transactions and any
+ * 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 )
+{
+ pj_sockaddr hostaddr;
+ char temp[80], hostip[PJ_INET6_ADDRSTRLEN];
+ pj_str_t local_uri;
+ pjsip_dialog *dlg;
+ pjmedia_sdp_session *local_sdp;
+ pjsip_tx_data *tdata;
+ unsigned options = 0;
+ pj_status_t status;
+
+
+ /*
+ * Respond (statelessly) any non-INVITE requests with 500
+ */
+ if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
+
+ if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
+ pj_str_t reason = pj_str("Simple UA unable to handle "
+ "this request");
+
+ pjsip_endpt_respond_stateless( g_endpt, rdata,
+ 500, &reason,
+ NULL, NULL);
+ }
+ return PJ_TRUE;
+ }
+
+
+ /*
+ * Reject INVITE if we already have an INVITE session in progress.
+ */
+ if (g_inv) {
+
+ pj_str_t reason = pj_str("Another call is in progress");
+
+ pjsip_endpt_respond_stateless( g_endpt, rdata,
+ 500, &reason,
+ NULL, NULL);
+ return PJ_TRUE;
+
+ }
+
+ /* Verify that we can handle the request. */
+ status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
+ g_endpt, NULL);
+ if (status != PJ_SUCCESS) {
+
+ pj_str_t reason = pj_str("Sorry Simple UA can not handle this INVITE");
+
+ pjsip_endpt_respond_stateless( g_endpt, rdata,
+ 500, &reason,
+ NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ /*
+ * Generate Contact URI
+ */
+ if (pj_gethostip(AF, &hostaddr) != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to retrieve local host IP", status);
+ return PJ_TRUE;
+ }
+ pj_sockaddr_print(&hostaddr, hostip, sizeof(hostip), 2);
+
+ pj_ansi_sprintf(temp, "<sip:simpleuas@%s:%d>",
+ hostip, SIP_PORT);
+ local_uri = pj_str(temp);
+
+ /*
+ * Create UAS dialog.
+ */
+ status = pjsip_dlg_create_uas( pjsip_ua_instance(),
+ rdata,
+ &local_uri, /* contact */
+ &dlg);
+ if (status != PJ_SUCCESS) {
+ pjsip_endpt_respond_stateless(g_endpt, rdata, 500, NULL,
+ NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ /*
+ * Get media capability from media endpoint:
+ */
+
+ status = pjmedia_endpt_create_sdp( g_med_endpt, rdata->tp_info.pool,
+ MAX_MEDIA_CNT, g_sock_info, &local_sdp);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, PJ_TRUE);
+
+
+ /*
+ * 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. */
+ status = pjsip_inv_send_msg(g_inv, tdata);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, PJ_TRUE);
+
+
+ /*
+ * Now create 200 response.
+ */
+ status = pjsip_inv_answer( g_inv,
+ 200, NULL, /* st_code and st_text */
+ NULL, /* SDP already specified */
+ &tdata);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, PJ_TRUE);
+
+ /*
+ * Send the 200 response.
+ */
+ 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)
+{
+ 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. */
+}
+
+
diff --git a/pjsip-apps/src/samples/sipecho.c b/pjsip-apps/src/samples/sipecho.c
new file mode 100644
index 0000000..6d1ab2d
--- /dev/null
+++ b/pjsip-apps/src/samples/sipecho.c
@@ -0,0 +1,688 @@
+/* $Id: sipecho.c 4148 2012-05-31 12:21:59Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+
+/**
+ * sipecho.c
+ *
+ * - Accepts incoming calls and echoes back SDP and any media.
+ * - Specify URI in cmdline argument to make call
+ * - Accepts registration too!
+ */
+
+/* Include all headers. */
+#include <pjsip.h>
+#include <pjmedia/sdp.h>
+#include <pjsip_ua.h>
+#include <pjlib-util.h>
+#include <pjlib.h>
+
+/* For logging purpose. */
+#define THIS_FILE "sipecho.c"
+
+#include "util.h"
+
+
+/* Settings */
+#define MAX_CALLS 8
+
+typedef struct call_t
+{
+ pjsip_inv_session *inv;
+} call_t;
+
+static struct app_t
+{
+ pj_caching_pool cp;
+ pj_pool_t *pool;
+
+ pjsip_endpoint *sip_endpt;
+ //pjmedia_endpt *med_endpt;
+
+ call_t call[MAX_CALLS];
+
+ pj_bool_t quit;
+ pj_thread_t *worker_thread;
+
+ pj_bool_t enable_msg_logging;
+} app;
+
+/*
+ * Prototypes:
+ */
+
+static void call_on_media_update(pjsip_inv_session *inv, pj_status_t status);
+static void call_on_state_changed(pjsip_inv_session *inv, pjsip_event *e);
+static void call_on_rx_offer(pjsip_inv_session *inv, const pjmedia_sdp_session *offer);
+static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
+static pj_bool_t on_rx_request( pjsip_rx_data *rdata );
+
+/* Globals */
+static int sip_af;
+static int sip_port = 5060;
+static pj_bool_t sip_tcp;
+
+/* 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_sipecho =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-sipecho", 11 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+/* Notification on incoming messages */
+static pj_bool_t logging_on_rx_msg(pjsip_rx_data *rdata)
+{
+ if (!app.enable_msg_logging)
+ return PJ_FALSE;
+
+ PJ_LOG(3,(THIS_FILE, "RX %d bytes %s from %s %s:%d:\n"
+ "%.*s\n"
+ "--end msg--",
+ rdata->msg_info.len,
+ pjsip_rx_data_get_info(rdata),
+ rdata->tp_info.transport->type_name,
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port,
+ (int)rdata->msg_info.len,
+ rdata->msg_info.msg_buf));
+ return PJ_FALSE;
+}
+
+/* Notification on outgoing messages */
+static pj_status_t logging_on_tx_msg(pjsip_tx_data *tdata)
+{
+ if (!app.enable_msg_logging)
+ return PJ_SUCCESS;
+
+ PJ_LOG(3,(THIS_FILE, "TX %d bytes %s to %s %s:%d:\n"
+ "%.*s\n"
+ "--end msg--",
+ (tdata->buf.cur - tdata->buf.start),
+ pjsip_tx_data_get_info(tdata),
+ tdata->tp_info.transport->type_name,
+ tdata->tp_info.dst_name,
+ tdata->tp_info.dst_port,
+ (int)(tdata->buf.cur - tdata->buf.start),
+ tdata->buf.start));
+ return PJ_SUCCESS;
+}
+
+/* The module instance. */
+static pjsip_module msg_logger =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-msg-log", 13 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &logging_on_rx_msg, /* on_rx_request() */
+ &logging_on_rx_msg, /* on_rx_response() */
+ &logging_on_tx_msg, /* on_tx_request. */
+ &logging_on_tx_msg, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+
+};
+
+static int worker_proc(void *arg)
+{
+ PJ_UNUSED_ARG(arg);
+
+ while (!app.quit) {
+ pj_time_val interval = { 0, 20 };
+ pjsip_endpt_handle_events(app.sip_endpt, &interval);
+ }
+
+ return 0;
+}
+
+static void hangup_all(void)
+{
+ unsigned i;
+ for (i=0; i<MAX_CALLS; ++i) {
+ call_t *call = &app.call[i];
+
+ if (call->inv && call->inv->state <= PJSIP_INV_STATE_CONFIRMED) {
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ status = pjsip_inv_end_session(call->inv, PJSIP_SC_BUSY_HERE, NULL, &tdata);
+ if (status==PJ_SUCCESS && tdata)
+ pjsip_inv_send_msg(call->inv, tdata);
+ }
+ }
+}
+
+static void destroy_stack(void)
+{
+ enum { WAIT_CLEAR = 5000, WAIT_INTERVAL = 500 };
+ unsigned i;
+
+ PJ_LOG(3,(THIS_FILE, "Shutting down.."));
+
+ /* Wait until all clear */
+ hangup_all();
+ for (i=0; i<WAIT_CLEAR/WAIT_INTERVAL; ++i) {
+ unsigned j;
+
+ for (j=0; j<MAX_CALLS; ++j) {
+ call_t *call = &app.call[j];
+ if (call->inv && call->inv->state <= PJSIP_INV_STATE_CONFIRMED)
+ break;
+ }
+
+ if (j==MAX_CALLS)
+ return;
+
+ pj_thread_sleep(WAIT_INTERVAL);
+ }
+
+ app.quit = PJ_TRUE;
+ if (app.worker_thread) {
+ pj_thread_join(app.worker_thread);
+ app.worker_thread = NULL;
+ }
+
+ //if (app.med_endpt)
+ //pjmedia_endpt_destroy(app.med_endpt);
+
+ if (app.sip_endpt)
+ pjsip_endpt_destroy(app.sip_endpt);
+
+ if (app.pool)
+ pj_pool_release(app.pool);
+
+ dump_pool_usage(THIS_FILE, &app.cp);
+ pj_caching_pool_destroy(&app.cp);
+}
+
+#define CHECK_STATUS() do { if (status != PJ_SUCCESS) return status; } while (0)
+
+static pj_status_t init_stack()
+{
+ pj_sockaddr addr;
+ pjsip_inv_callback inv_cb;
+ pj_status_t status;
+
+ pj_log_set_level(3);
+
+ status = pjlib_util_init();
+ CHECK_STATUS();
+
+ pj_caching_pool_init(&app.cp, NULL, 0);
+ app.pool = pj_pool_create( &app.cp.factory, "sipecho", 512, 512, 0);
+
+ status = pjsip_endpt_create(&app.cp.factory, NULL, &app.sip_endpt);
+ CHECK_STATUS();
+
+ pj_log_set_level(4);
+ pj_sockaddr_init((pj_uint16_t)sip_af, &addr, NULL, (pj_uint16_t)sip_port);
+ if (sip_af == pj_AF_INET()) {
+ if (sip_tcp) {
+ status = pjsip_tcp_transport_start( app.sip_endpt, &addr.ipv4, 1,
+ NULL);
+ } else {
+ status = pjsip_udp_transport_start( app.sip_endpt, &addr.ipv4,
+ NULL, 1, NULL);
+ }
+ } else if (sip_af == pj_AF_INET6()) {
+ status = pjsip_udp_transport_start6(app.sip_endpt, &addr.ipv6,
+ NULL, 1, NULL);
+ } else {
+ status = PJ_EAFNOTSUP;
+ }
+
+ pj_log_set_level(3);
+ CHECK_STATUS();
+
+ status = pjsip_tsx_layer_init_module(app.sip_endpt) ||
+ pjsip_ua_init_module( app.sip_endpt, NULL );
+ CHECK_STATUS();
+
+ pj_bzero(&inv_cb, sizeof(inv_cb));
+ inv_cb.on_state_changed = &call_on_state_changed;
+ inv_cb.on_new_session = &call_on_forked;
+ inv_cb.on_media_update = &call_on_media_update;
+ inv_cb.on_rx_offer = &call_on_rx_offer;
+
+ status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb) ||
+ pjsip_100rel_init_module(app.sip_endpt) ||
+ pjsip_endpt_register_module( app.sip_endpt, &mod_sipecho) ||
+ pjsip_endpt_register_module( app.sip_endpt, &msg_logger) ||
+ //pjmedia_endpt_create(&app.cp.factory,
+ // pjsip_endpt_get_ioqueue(app.sip_endpt),
+ // 0, &app.med_endpt) ||
+ pj_thread_create(app.pool, "sipecho", &worker_proc, NULL, 0, 0,
+ &app.worker_thread);
+ CHECK_STATUS();
+
+ return PJ_SUCCESS;
+}
+
+static void destroy_call(call_t *call)
+{
+ call->inv = NULL;
+}
+
+static pjmedia_sdp_attr * find_remove_sdp_attrs(unsigned *cnt,
+ pjmedia_sdp_attr *attr[],
+ unsigned cnt_attr_to_remove,
+ const char* attr_to_remove[])
+{
+ pjmedia_sdp_attr *found_attr = NULL;
+ int i;
+
+ for (i=0; i<(int)*cnt; ++i) {
+ unsigned j;
+ for (j=0; j<cnt_attr_to_remove; ++j) {
+ if (pj_strcmp2(&attr[i]->name, attr_to_remove[j])==0) {
+ if (!found_attr) found_attr = attr[i];
+ pj_array_erase(attr, sizeof(attr[0]), *cnt, i);
+ --(*cnt);
+ --i;
+ break;
+ }
+ }
+ }
+
+ return found_attr;
+}
+
+static pjmedia_sdp_session *create_answer(int call_num, pj_pool_t *pool,
+ const pjmedia_sdp_session *offer)
+{
+ const char* dir_attrs[] = { "sendrecv", "sendonly", "recvonly", "inactive" };
+ const char *ice_attrs[] = {"ice-pwd", "ice-ufrag", "candidate"};
+ pjmedia_sdp_session *answer = pjmedia_sdp_session_clone(pool, offer);
+ pjmedia_sdp_attr *sess_dir_attr = NULL;
+ unsigned mi;
+
+ PJ_LOG(3,(THIS_FILE, "Call %d: creating answer:", call_num));
+
+ answer->name = pj_str("sipecho");
+ sess_dir_attr = find_remove_sdp_attrs(&answer->attr_count, answer->attr,
+ PJ_ARRAY_SIZE(dir_attrs),
+ dir_attrs);
+
+ for (mi=0; mi<answer->media_count; ++mi) {
+ pjmedia_sdp_media *m = answer->media[mi];
+ pjmedia_sdp_attr *m_dir_attr;
+ pjmedia_sdp_attr *dir_attr;
+ const char *our_dir = NULL;
+ pjmedia_sdp_conn *c;
+
+ /* Match direction */
+ m_dir_attr = find_remove_sdp_attrs(&m->attr_count, m->attr,
+ PJ_ARRAY_SIZE(dir_attrs),
+ dir_attrs);
+ dir_attr = m_dir_attr ? m_dir_attr : sess_dir_attr;
+
+ if (dir_attr) {
+ if (pj_strcmp2(&dir_attr->name, "sendonly")==0)
+ our_dir = "recvonly";
+ else if (pj_strcmp2(&dir_attr->name, "inactive")==0)
+ our_dir = "inactive";
+ else if (pj_strcmp2(&dir_attr->name, "recvonly")==0)
+ our_dir = "inactive";
+
+ if (our_dir) {
+ dir_attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr);
+ dir_attr->name = pj_str((char*)our_dir);
+ m->attr[m->attr_count++] = dir_attr;
+ }
+ }
+
+ /* Remove ICE attributes */
+ find_remove_sdp_attrs(&m->attr_count, m->attr, PJ_ARRAY_SIZE(ice_attrs), ice_attrs);
+
+ /* Done */
+ c = m->conn ? m->conn : answer->conn;
+ PJ_LOG(3,(THIS_FILE, " Media %d, %.*s: %s <--> %.*s:%d",
+ mi, (int)m->desc.media.slen, m->desc.media.ptr,
+ (our_dir ? our_dir : "sendrecv"),
+ (int)c->addr.slen, c->addr.ptr, m->desc.port));
+ }
+
+ return answer;
+}
+
+static void call_on_state_changed( pjsip_inv_session *inv,
+ pjsip_event *e)
+{
+ call_t *call = (call_t*)inv->mod_data[mod_sipecho.id];
+ if (!call)
+ return;
+
+ PJ_UNUSED_ARG(e);
+ if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+ PJ_LOG(3,(THIS_FILE, "Call %d: DISCONNECTED [reason=%d (%s)]",
+ call - app.call, inv->cause,
+ pjsip_get_status_text(inv->cause)->ptr));
+ destroy_call(call);
+ } else {
+ PJ_LOG(3,(THIS_FILE, "Call %d: state changed to %s",
+ call - app.call, pjsip_inv_state_name(inv->state)));
+ }
+}
+
+static void call_on_rx_offer(pjsip_inv_session *inv, const pjmedia_sdp_session *offer)
+{
+ call_t *call = (call_t*) inv->mod_data[mod_sipecho.id];
+ pjsip_inv_set_sdp_answer(inv, create_answer(call - app.call, inv->pool_prov, offer));
+}
+
+static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
+{
+ PJ_UNUSED_ARG(inv);
+ PJ_UNUSED_ARG(e);
+}
+
+static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
+{
+ pj_sockaddr hostaddr;
+ char temp[80], hostip[PJ_INET6_ADDRSTRLEN];
+ pj_str_t local_uri;
+ pjsip_dialog *dlg;
+ pjsip_rdata_sdp_info *sdp_info;
+ pjmedia_sdp_session *answer = NULL;
+ pjsip_tx_data *tdata = NULL;
+ call_t *call = NULL;
+ unsigned i;
+ pj_status_t status;
+
+ PJ_LOG(3,(THIS_FILE, "RX %.*s from %s",
+ (int)rdata->msg_info.msg->line.req.method.name.slen,
+ rdata->msg_info.msg->line.req.method.name.ptr,
+ rdata->pkt_info.src_name));
+
+ if (rdata->msg_info.msg->line.req.method.id == PJSIP_REGISTER_METHOD) {
+ /* Let me be a registrar! */
+ pjsip_hdr hdr_list, *h;
+ pjsip_msg *msg;
+ int expires = -1;
+
+ pj_list_init(&hdr_list);
+ msg = rdata->msg_info.msg;
+ h = (pjsip_hdr*)pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
+ if (h) {
+ expires = ((pjsip_expires_hdr*)h)->ivalue;
+ pj_list_push_back(&hdr_list, pjsip_hdr_clone(rdata->tp_info.pool, h));
+ PJ_LOG(3,(THIS_FILE, " Expires=%d", expires));
+ }
+ if (expires != 0) {
+ h = (pjsip_hdr*)pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL);
+ if (h)
+ pj_list_push_back(&hdr_list, pjsip_hdr_clone(rdata->tp_info.pool, h));
+ }
+
+ pjsip_endpt_respond(app.sip_endpt, &mod_sipecho, rdata, 200, NULL,
+ &hdr_list, NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
+ if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
+ pj_str_t reason = pj_str("Go away");
+ pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
+ 400, &reason,
+ NULL, NULL);
+ }
+ return PJ_TRUE;
+ }
+
+ sdp_info = pjsip_rdata_get_sdp_info(rdata);
+ if (!sdp_info || !sdp_info->sdp) {
+ pj_str_t reason = pj_str("Require valid offer");
+ pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
+ 400, &reason,
+ NULL, NULL);
+ }
+
+ for (i=0; i<MAX_CALLS; ++i) {
+ if (app.call[i].inv == NULL) {
+ call = &app.call[i];
+ break;
+ }
+ }
+
+ if (i==MAX_CALLS) {
+ pj_str_t reason = pj_str("We're full");
+ pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
+ PJSIP_SC_BUSY_HERE, &reason,
+ NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ /* Generate Contact URI */
+ status = pj_gethostip(sip_af, &hostaddr);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to retrieve local host IP", status);
+ return PJ_TRUE;
+ }
+ pj_sockaddr_print(&hostaddr, hostip, sizeof(hostip), 2);
+ pj_ansi_sprintf(temp, "<sip:sipecho@%s:%d>", hostip, sip_port);
+ local_uri = pj_str(temp);
+
+ status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
+ &local_uri, &dlg);
+
+ if (status == PJ_SUCCESS)
+ answer = create_answer(call-app.call, dlg->pool, sdp_info->sdp);
+ if (status == PJ_SUCCESS)
+ status = pjsip_inv_create_uas( dlg, rdata, answer, 0, &call->inv);
+ if (status == PJ_SUCCESS)
+ status = pjsip_inv_initial_answer(call->inv, rdata, 100,
+ NULL, NULL, &tdata);
+ if (status == PJ_SUCCESS)
+ status = pjsip_inv_send_msg(call->inv, tdata);
+
+ if (status == PJ_SUCCESS)
+ status = pjsip_inv_answer(call->inv, 180, NULL,
+ NULL, &tdata);
+ if (status == PJ_SUCCESS)
+ status = pjsip_inv_send_msg(call->inv, tdata);
+
+ if (status == PJ_SUCCESS)
+ status = pjsip_inv_answer(call->inv, 200, NULL,
+ NULL, &tdata);
+ if (status == PJ_SUCCESS)
+ status = pjsip_inv_send_msg(call->inv, tdata);
+
+ if (status != PJ_SUCCESS) {
+ pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
+ 500, NULL, NULL, NULL);
+ destroy_call(call);
+ } else {
+ call->inv->mod_data[mod_sipecho.id] = call;
+ }
+
+ return PJ_TRUE;
+}
+
+static void call_on_media_update( pjsip_inv_session *inv,
+ pj_status_t status)
+{
+ PJ_UNUSED_ARG(inv);
+ PJ_UNUSED_ARG(status);
+}
+
+
+static void usage()
+{
+ printf("\nUsage: sipecho OPTIONS\n");
+ printf("\n");
+ printf("where OPTIONS:\n");
+ printf(" --local-port, -p PORT Bind to port PORT.\n");
+ printf(" --tcp, -t Listen to TCP instead.\n");
+ printf(" --ipv6, -6 Use IPv6 instead.\n");
+ printf(" --help, -h Show this help page.\n");
+}
+
+/* main()
+ *
+ * If called with argument, treat argument as SIP URL to be called.
+ * Otherwise wait for incoming calls.
+ */
+int main(int argc, char *argv[])
+{
+ struct pj_getopt_option long_options[] = {
+ { "local-port", 1, 0, 'p' },
+ { "tcp", 0, 0, 't' },
+ { "ipv6", 0, 0, '6' },
+ { "help", 0, 0, 'h' }
+ };
+ int c, option_index;
+
+ pj_log_set_level(5);
+
+ pj_init();
+
+ sip_af = pj_AF_INET();
+
+ pj_optind = 0;
+ while ((c = pj_getopt_long(argc, argv, "p:t6h", long_options,
+ &option_index)) != -1)
+ {
+ switch (c) {
+ case 'p':
+ sip_port = atoi(pj_optarg);
+ break;
+ case 't':
+ sip_tcp = PJ_TRUE;
+ break;
+ case 'h':
+ usage();
+ return 0;
+ case '6':
+ sip_af = pj_AF_INET6();
+ break;
+ default:
+ PJ_LOG(1,(THIS_FILE,
+ "Argument \"%s\" is not valid. Use --help to see help",
+ argv[pj_optind-1]));
+ return -1;
+ }
+ }
+
+ if (init_stack())
+ goto on_error;
+
+ /* If URL is specified, then make call immediately. */
+ if (pj_optind != argc) {
+ pj_sockaddr hostaddr;
+ char hostip[PJ_INET6_ADDRSTRLEN+2];
+ char temp[80];
+ call_t *call;
+ pj_str_t dst_uri = pj_str(argv[pj_optind]);
+ pj_str_t local_uri;
+ pjsip_dialog *dlg;
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ if (pj_gethostip(sip_af, &hostaddr) != PJ_SUCCESS) {
+ PJ_LOG(1,(THIS_FILE, "Unable to retrieve local host IP"));
+ goto on_error;
+ }
+ pj_sockaddr_print(&hostaddr, hostip, sizeof(hostip), 2);
+
+ pj_ansi_sprintf(temp, "<sip:sipecho@%s:%d>",
+ hostip, sip_port);
+ local_uri = pj_str(temp);
+
+ call = &app.call[0];
+
+ status = pjsip_dlg_create_uac( pjsip_ua_instance(),
+ &local_uri, /* local URI */
+ &local_uri, /* local Contact */
+ &dst_uri, /* remote URI */
+ &dst_uri, /* remote target */
+ &dlg); /* dialog */
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create UAC dialog", status);
+ return 1;
+ }
+
+ status = pjsip_inv_create_uac( dlg, NULL, 0, &call->inv);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ call->inv->mod_data[mod_sipecho.id] = call;
+
+ status = pjsip_inv_invite(call->inv, &tdata);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ status = pjsip_inv_send_msg(call->inv, tdata);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ puts("Press ENTER to quit...");
+ } else {
+ puts("Ready for incoming calls. Press ENTER to quit...");
+ }
+
+ for (;;) {
+ char s[10];
+
+ printf("\nMenu:\n"
+ " h Hangup all calls\n"
+ " l %s message logging\n"
+ " q Quit\n",
+ (app.enable_msg_logging? "Disable" : "Enable"));
+
+ if (fgets(s, sizeof(s), stdin) == NULL)
+ continue;
+
+ if (s[0]=='q')
+ break;
+ switch (s[0]) {
+ case 'l':
+ app.enable_msg_logging = !app.enable_msg_logging;
+ break;
+ case 'h':
+ hangup_all();
+ break;
+ }
+ }
+
+ destroy_stack();
+
+ puts("Bye bye..");
+ return 0;
+
+on_error:
+ puts("An error has occurred. run a debugger..");
+ return 1;
+}
+
diff --git a/pjsip-apps/src/samples/siprtp.c b/pjsip-apps/src/samples/siprtp.c
new file mode 100644
index 0000000..2bd478a
--- /dev/null
+++ b/pjsip-apps/src/samples/siprtp.c
@@ -0,0 +1,2189 @@
+/* $Id: siprtp.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+
+
+
+
+/* Usage */
+static const char *USAGE =
+" PURPOSE: \n"
+" This program establishes SIP INVITE session and media, and calculate \n"
+" the media quality (packet lost, jitter, rtt, etc.). Unlike normal \n"
+" pjmedia applications, this program bypasses all pjmedia stream \n"
+" framework and transmit encoded RTP packets manually using own thread. \n"
+"\n"
+" USAGE:\n"
+" siprtp [options] => to start in server mode\n"
+" siprtp [options] URL => to start in client mode\n"
+"\n"
+" Program options:\n"
+" --count=N, -c Set number of calls to create (default:1) \n"
+" --gap=N -g Set call gapping to N msec (default:0)\n"
+" --duration=SEC, -d Set maximum call duration (default:unlimited) \n"
+" --auto-quit, -q Quit when calls have been completed (default:no)\n"
+" --call-report -R Display report on call termination (default:yes)\n"
+"\n"
+" Address and ports options:\n"
+" --local-port=PORT,-p Set local SIP port (default: 5060)\n"
+" --rtp-port=PORT, -r Set start of RTP port (default: 4000)\n"
+" --ip-addr=IP, -i Set local IP address to use (otherwise it will\n"
+" try to determine local IP address from hostname)\n"
+"\n"
+" Logging Options:\n"
+" --log-level=N, -l Set log verbosity level (default=5)\n"
+" --app-log-level=N Set app screen log verbosity (default=3)\n"
+" --log-file=FILE Write log to file FILE\n"
+" --report-file=FILE Write report to file FILE\n"
+"\n"
+/* Don't support this anymore, because codec is properly examined in
+ pjmedia_session_info_from_sdp() function.
+
+" Codec Options:\n"
+" --a-pt=PT Set audio payload type to PT (default=0)\n"
+" --a-name=NAME Set audio codec name to NAME (default=pcmu)\n"
+" --a-clock=RATE Set audio codec rate to RATE Hz (default=8000Hz)\n"
+" --a-bitrate=BPS Set audio codec bitrate to BPS (default=64000bps)\n"
+" --a-ptime=MS Set audio frame time to MS msec (default=20ms)\n"
+*/
+;
+
+
+/* Include all headers. */
+#include <pjsip.h>
+#include <pjmedia.h>
+#include <pjmedia-codec.h>
+#include <pjsip_ua.h>
+#include <pjsip_simple.h>
+#include <pjlib-util.h>
+#include <pjlib.h>
+
+#include <stdlib.h>
+
+/* Uncomment these to disable threads.
+ * NOTE:
+ * when threading is disabled, siprtp won't transmit any
+ * RTP packets.
+ */
+/*
+#undef PJ_HAS_THREADS
+#define PJ_HAS_THREADS 0
+*/
+
+
+#if PJ_HAS_HIGH_RES_TIMER==0
+# error "High resolution timer is needed for this sample"
+#endif
+
+#define THIS_FILE "siprtp.c"
+#define MAX_CALLS 1024
+#define RTP_START_PORT 4000
+
+
+/* Codec descriptor: */
+struct codec
+{
+ unsigned pt;
+ char* name;
+ unsigned clock_rate;
+ unsigned bit_rate;
+ unsigned ptime;
+ char* description;
+};
+
+
+/* A bidirectional media stream created when the call is active. */
+struct media_stream
+{
+ /* Static: */
+ unsigned call_index; /* Call owner. */
+ unsigned media_index; /* Media index in call. */
+ pjmedia_transport *transport; /* To send/recv RTP/RTCP */
+
+ /* Active? */
+ pj_bool_t active; /* Non-zero if is in call. */
+
+ /* Current stream info: */
+ pjmedia_stream_info si; /* Current stream info. */
+
+ /* More info: */
+ unsigned clock_rate; /* clock rate */
+ unsigned samples_per_frame; /* samples per frame */
+ unsigned bytes_per_frame; /* frame size. */
+
+ /* RTP session: */
+ pjmedia_rtp_session out_sess; /* outgoing RTP session */
+ pjmedia_rtp_session in_sess; /* incoming RTP session */
+
+ /* RTCP stats: */
+ pjmedia_rtcp_session rtcp; /* incoming RTCP session. */
+
+ /* Thread: */
+ pj_bool_t thread_quit_flag; /* Stop media thread. */
+ pj_thread_t *thread; /* Media thread. */
+};
+
+
+/* This is a call structure that is created when the application starts
+ * and only destroyed when the application quits.
+ */
+struct call
+{
+ unsigned index;
+ pjsip_inv_session *inv;
+ unsigned media_count;
+ struct media_stream media[1];
+ pj_time_val start_time;
+ pj_time_val response_time;
+ pj_time_val connect_time;
+
+ pj_timer_entry d_timer; /**< Disconnect timer. */
+};
+
+
+/* Application's global variables */
+static struct app
+{
+ unsigned max_calls;
+ unsigned call_gap;
+ pj_bool_t call_report;
+ unsigned uac_calls;
+ unsigned duration;
+ pj_bool_t auto_quit;
+ unsigned thread_count;
+ int sip_port;
+ int rtp_start_port;
+ pj_str_t local_addr;
+ pj_str_t local_uri;
+ pj_str_t local_contact;
+
+ int app_log_level;
+ int log_level;
+ char *log_filename;
+ char *report_filename;
+
+ struct codec audio_codec;
+
+ pj_str_t uri_to_call;
+
+ pj_caching_pool cp;
+ pj_pool_t *pool;
+
+ pjsip_endpoint *sip_endpt;
+ pj_bool_t thread_quit;
+ pj_thread_t *sip_thread[1];
+
+ pjmedia_endpt *med_endpt;
+ struct call call[MAX_CALLS];
+} app;
+
+
+
+/*
+ * 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 );
+
+/* Worker thread prototype */
+static int sip_worker_thread(void *arg);
+
+/* Create SDP for call */
+static pj_status_t create_sdp( pj_pool_t *pool,
+ struct call *call,
+ pjmedia_sdp_session **p_sdp);
+
+/* Hangup call */
+static void hangup_call(unsigned index);
+
+/* Destroy the call's media */
+static void destroy_call_media(unsigned call_index);
+
+/* Destroy media. */
+static void destroy_media();
+
+/* This callback is called by media transport on receipt of RTP packet. */
+static void on_rx_rtp(void *user_data, void *pkt, pj_ssize_t size);
+
+/* This callback is called by media transport on receipt of RTCP packet. */
+static void on_rx_rtcp(void *user_data, void *pkt, pj_ssize_t size);
+
+/* Display error */
+static void app_perror(const char *sender, const char *title,
+ pj_status_t status);
+
+/* Print call */
+static void print_call(int call_index);
+
+
+/* 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_siprtp =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-siprtpapp", 13 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+
+/* Codec constants */
+struct codec audio_codecs[] =
+{
+ { 0, "PCMU", 8000, 64000, 20, "G.711 ULaw" },
+ { 3, "GSM", 8000, 13200, 20, "GSM" },
+ { 4, "G723", 8000, 6400, 30, "G.723.1" },
+ { 8, "PCMA", 8000, 64000, 20, "G.711 ALaw" },
+ { 18, "G729", 8000, 8000, 20, "G.729" },
+};
+
+
+/*
+ * Init SIP stack
+ */
+static pj_status_t init_sip()
+{
+ unsigned i;
+ pj_status_t status;
+
+ /* init PJLIB-UTIL: */
+ status = pjlib_util_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Must create a pool factory before we can allocate any memory. */
+ pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy, 0);
+
+ /* Create application pool for misc. */
+ app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
+
+ /* Create the endpoint: */
+ status = pjsip_endpt_create(&app.cp.factory, pj_gethostname()->ptr,
+ &app.sip_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+ /* Add UDP transport. */
+ {
+ pj_sockaddr_in addr;
+ pjsip_host_port addrname;
+ pjsip_transport *tp;
+
+ pj_bzero(&addr, sizeof(addr));
+ addr.sin_family = pj_AF_INET();
+ addr.sin_addr.s_addr = 0;
+ addr.sin_port = pj_htons((pj_uint16_t)app.sip_port);
+
+ if (app.local_addr.slen) {
+
+ addrname.host = app.local_addr;
+ addrname.port = app.sip_port;
+
+ status = pj_sockaddr_in_init(&addr, &app.local_addr,
+ (pj_uint16_t)app.sip_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to resolve IP interface", status);
+ return status;
+ }
+ }
+
+ status = pjsip_udp_transport_start( app.sip_endpt, &addr,
+ (app.local_addr.slen ? &addrname:NULL),
+ 1, &tp);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to start UDP transport", status);
+ return status;
+ }
+
+ PJ_LOG(3,(THIS_FILE, "SIP UDP listening on %.*s:%d",
+ (int)tp->local_name.host.slen, tp->local_name.host.ptr,
+ tp->local_name.port));
+ }
+
+ /*
+ * Init transaction layer.
+ * This will create/initialize transaction hash tables etc.
+ */
+ status = pjsip_tsx_layer_init_module(app.sip_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Initialize UA layer. */
+ status = pjsip_ua_init_module( app.sip_endpt, NULL );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Initialize 100rel support */
+ status = pjsip_100rel_init_module(app.sip_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Init invite session module. */
+ {
+ pjsip_inv_callback inv_cb;
+
+ /* Init the callback for INVITE session: */
+ pj_bzero(&inv_cb, sizeof(inv_cb));
+ inv_cb.on_state_changed = &call_on_state_changed;
+ inv_cb.on_new_session = &call_on_forked;
+ inv_cb.on_media_update = &call_on_media_update;
+
+ /* Initialize invite session module: */
+ status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ }
+
+ /* Register our module to receive incoming requests. */
+ status = pjsip_endpt_register_module( app.sip_endpt, &mod_siprtp);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Init calls */
+ for (i=0; i<app.max_calls; ++i)
+ app.call[i].index = i;
+
+ /* Done */
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy SIP
+ */
+static void destroy_sip()
+{
+ unsigned i;
+
+ app.thread_quit = 1;
+ for (i=0; i<app.thread_count; ++i) {
+ if (app.sip_thread[i]) {
+ pj_thread_join(app.sip_thread[i]);
+ pj_thread_destroy(app.sip_thread[i]);
+ app.sip_thread[i] = NULL;
+ }
+ }
+
+ if (app.sip_endpt) {
+ pjsip_endpt_destroy(app.sip_endpt);
+ app.sip_endpt = NULL;
+ }
+
+}
+
+
+/*
+ * Init media stack.
+ */
+static pj_status_t init_media()
+{
+ unsigned i, count;
+ pj_uint16_t rtp_port;
+ pj_status_t status;
+
+
+ /* Initialize media endpoint so that at least error subsystem is properly
+ * initialized.
+ */
+#if PJ_HAS_THREADS
+ status = pjmedia_endpt_create(&app.cp.factory, NULL, 1, &app.med_endpt);
+#else
+ status = pjmedia_endpt_create(&app.cp.factory,
+ pjsip_endpt_get_ioqueue(app.sip_endpt),
+ 0, &app.med_endpt);
+#endif
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+ /* Must register codecs to be supported */
+#if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
+ pjmedia_codec_g711_init(app.med_endpt);
+#endif
+
+ /* RTP port counter */
+ rtp_port = (pj_uint16_t)(app.rtp_start_port & 0xFFFE);
+
+ /* Init media transport for all calls. */
+ for (i=0, count=0; i<app.max_calls; ++i, ++count) {
+
+ unsigned j;
+
+ /* Create transport for each media in the call */
+ for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
+ /* Repeat binding media socket to next port when fails to bind
+ * to current port number.
+ */
+ int retry;
+
+ app.call[i].media[j].call_index = i;
+ app.call[i].media[j].media_index = j;
+
+ status = -1;
+ for (retry=0; retry<100; ++retry,rtp_port+=2) {
+ struct media_stream *m = &app.call[i].media[j];
+
+ status = pjmedia_transport_udp_create2(app.med_endpt,
+ "siprtp",
+ &app.local_addr,
+ rtp_port, 0,
+ &m->transport);
+ if (status == PJ_SUCCESS) {
+ rtp_port += 2;
+ break;
+ }
+ }
+ }
+
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ /* Done */
+ return PJ_SUCCESS;
+
+on_error:
+ destroy_media();
+ return status;
+}
+
+
+/*
+ * Destroy media.
+ */
+static void destroy_media()
+{
+ unsigned i;
+
+ for (i=0; i<app.max_calls; ++i) {
+ unsigned j;
+ for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
+ struct media_stream *m = &app.call[i].media[j];
+
+ if (m->transport) {
+ pjmedia_transport_close(m->transport);
+ m->transport = NULL;
+ }
+ }
+ }
+
+ if (app.med_endpt) {
+ pjmedia_endpt_destroy(app.med_endpt);
+ app.med_endpt = NULL;
+ }
+}
+
+
+/*
+ * Make outgoing call.
+ */
+static pj_status_t make_call(const pj_str_t *dst_uri)
+{
+ unsigned i;
+ struct call *call;
+ pjsip_dialog *dlg;
+ pjmedia_sdp_session *sdp;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+
+ /* Find unused call slot */
+ for (i=0; i<app.max_calls; ++i) {
+ if (app.call[i].inv == NULL)
+ break;
+ }
+
+ if (i == app.max_calls)
+ return PJ_ETOOMANY;
+
+ call = &app.call[i];
+
+ /* Create UAC dialog */
+ status = pjsip_dlg_create_uac( pjsip_ua_instance(),
+ &app.local_uri, /* local URI */
+ &app.local_contact, /* local Contact */
+ dst_uri, /* remote URI */
+ dst_uri, /* remote target */
+ &dlg); /* dialog */
+ if (status != PJ_SUCCESS) {
+ ++app.uac_calls;
+ return status;
+ }
+
+ /* Create SDP */
+ create_sdp( dlg->pool, call, &sdp);
+
+ /* Create the INVITE session. */
+ status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
+ if (status != PJ_SUCCESS) {
+ pjsip_dlg_terminate(dlg);
+ ++app.uac_calls;
+ return status;
+ }
+
+
+ /* Attach call data to invite session */
+ call->inv->mod_data[mod_siprtp.id] = call;
+
+ /* Mark start of call */
+ pj_gettimeofday(&call->start_time);
+
+
+ /* Create initial INVITE request.
+ * This INVITE request will contain a perfectly good request and
+ * an SDP body as well.
+ */
+ status = pjsip_inv_invite(call->inv, &tdata);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+ /* 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(call->inv, tdata);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Receive incoming call
+ */
+static void process_incoming_call(pjsip_rx_data *rdata)
+{
+ unsigned i, options;
+ struct call *call;
+ pjsip_dialog *dlg;
+ pjmedia_sdp_session *sdp;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ /* Find free call slot */
+ for (i=0; i<app.max_calls; ++i) {
+ if (app.call[i].inv == NULL)
+ break;
+ }
+
+ if (i == app.max_calls) {
+ const pj_str_t reason = pj_str("Too many calls");
+ pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
+ 500, &reason,
+ NULL, NULL);
+ return;
+ }
+
+ call = &app.call[i];
+
+ /* Verify that we can handle the request. */
+ options = 0;
+ status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
+ app.sip_endpt, &tdata);
+ if (status != PJ_SUCCESS) {
+ /*
+ * No we can't handle the incoming INVITE request.
+ */
+ if (tdata) {
+ pjsip_response_addr res_addr;
+
+ pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
+ pjsip_endpt_send_response(app.sip_endpt, &res_addr, tdata,
+ NULL, NULL);
+
+ } else {
+
+ /* Respond with 500 (Internal Server Error) */
+ pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, NULL,
+ NULL, NULL);
+ }
+
+ return;
+ }
+
+ /* Create UAS dialog */
+ status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
+ &app.local_contact, &dlg);
+ if (status != PJ_SUCCESS) {
+ const pj_str_t reason = pj_str("Unable to create dialog");
+ pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
+ 500, &reason,
+ NULL, NULL);
+ return;
+ }
+
+ /* Create SDP */
+ create_sdp( dlg->pool, call, &sdp);
+
+ /* Create UAS invite session */
+ status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
+ if (status != PJ_SUCCESS) {
+ pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
+ pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
+ return;
+ }
+
+
+ /* Attach call data to invite session */
+ call->inv->mod_data[mod_siprtp.id] = call;
+
+ /* Mark start of call */
+ pj_gettimeofday(&call->start_time);
+
+
+
+ /* Create 200 response .*/
+ status = pjsip_inv_initial_answer(call->inv, rdata, 200,
+ NULL, NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ status = pjsip_inv_initial_answer(call->inv, rdata,
+ PJSIP_SC_NOT_ACCEPTABLE,
+ NULL, NULL, &tdata);
+ if (status == PJ_SUCCESS)
+ pjsip_inv_send_msg(call->inv, tdata);
+ else
+ pjsip_inv_terminate(call->inv, 500, PJ_FALSE);
+ return;
+ }
+
+
+ /* Send the 200 response. */
+ status = pjsip_inv_send_msg(call->inv, tdata);
+ PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return);
+
+
+ /* Done */
+}
+
+
+/* Callback to be called when dialog has forked: */
+static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
+{
+ PJ_UNUSED_ARG(inv);
+ PJ_UNUSED_ARG(e);
+
+ PJ_TODO( HANDLE_FORKING );
+}
+
+
+/* Callback to be called to handle incoming requests outside dialogs: */
+static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
+{
+ /* Ignore strandled ACKs (must not send respone */
+ if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
+ return PJ_FALSE;
+
+ /* Respond (statelessly) any non-INVITE requests with 500 */
+ if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
+ pj_str_t reason = pj_str("Unsupported Operation");
+ pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
+ 500, &reason,
+ NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ /* Handle incoming INVITE */
+ process_incoming_call(rdata);
+
+ /* Done */
+ return PJ_TRUE;
+}
+
+
+/* Callback timer to disconnect call (limiting call duration) */
+static void timer_disconnect_call( pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ struct call *call = entry->user_data;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ entry->id = 0;
+ hangup_call(call->index);
+}
+
+
+/* Callback to be called when invite session's state has changed: */
+static void call_on_state_changed( pjsip_inv_session *inv,
+ pjsip_event *e)
+{
+ struct call *call = inv->mod_data[mod_siprtp.id];
+
+ PJ_UNUSED_ARG(e);
+
+ if (!call)
+ return;
+
+ if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+
+ pj_time_val null_time = {0, 0};
+
+ if (call->d_timer.id != 0) {
+ pjsip_endpt_cancel_timer(app.sip_endpt, &call->d_timer);
+ call->d_timer.id = 0;
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Call #%d disconnected. Reason=%d (%.*s)",
+ call->index,
+ inv->cause,
+ (int)inv->cause_text.slen,
+ inv->cause_text.ptr));
+
+ if (app.call_report) {
+ PJ_LOG(3,(THIS_FILE, "Call #%d statistics:", call->index));
+ print_call(call->index);
+ }
+
+
+ call->inv = NULL;
+ inv->mod_data[mod_siprtp.id] = NULL;
+
+ destroy_call_media(call->index);
+
+ call->start_time = null_time;
+ call->response_time = null_time;
+ call->connect_time = null_time;
+
+ ++app.uac_calls;
+
+ } else if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
+
+ pj_time_val t;
+
+ pj_gettimeofday(&call->connect_time);
+ if (call->response_time.sec == 0)
+ call->response_time = call->connect_time;
+
+ t = call->connect_time;
+ PJ_TIME_VAL_SUB(t, call->start_time);
+
+ PJ_LOG(3,(THIS_FILE, "Call #%d connected in %d ms", call->index,
+ PJ_TIME_VAL_MSEC(t)));
+
+ if (app.duration != 0) {
+ call->d_timer.id = 1;
+ call->d_timer.user_data = call;
+ call->d_timer.cb = &timer_disconnect_call;
+
+ t.sec = app.duration;
+ t.msec = 0;
+
+ pjsip_endpt_schedule_timer(app.sip_endpt, &call->d_timer, &t);
+ }
+
+ } else if ( inv->state == PJSIP_INV_STATE_EARLY ||
+ inv->state == PJSIP_INV_STATE_CONNECTING) {
+
+ if (call->response_time.sec == 0)
+ pj_gettimeofday(&call->response_time);
+
+ }
+}
+
+
+/* Utility */
+static void app_perror(const char *sender, const char *title,
+ pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(3,(sender, "%s: %s [status=%d]", title, errmsg, status));
+}
+
+
+/* Worker thread for SIP */
+static int sip_worker_thread(void *arg)
+{
+ PJ_UNUSED_ARG(arg);
+
+ while (!app.thread_quit) {
+ pj_time_val timeout = {0, 10};
+ pjsip_endpt_handle_events(app.sip_endpt, &timeout);
+ }
+
+ return 0;
+}
+
+
+/* Init application options */
+static pj_status_t init_options(int argc, char *argv[])
+{
+ static char ip_addr[32];
+ static char local_uri[64];
+
+ enum { OPT_START,
+ OPT_APP_LOG_LEVEL, OPT_LOG_FILE,
+ OPT_A_PT, OPT_A_NAME, OPT_A_CLOCK, OPT_A_BITRATE, OPT_A_PTIME,
+ OPT_REPORT_FILE };
+
+ struct pj_getopt_option long_options[] = {
+ { "count", 1, 0, 'c' },
+ { "gap", 1, 0, 'g' },
+ { "call-report", 0, 0, 'R' },
+ { "duration", 1, 0, 'd' },
+ { "auto-quit", 0, 0, 'q' },
+ { "local-port", 1, 0, 'p' },
+ { "rtp-port", 1, 0, 'r' },
+ { "ip-addr", 1, 0, 'i' },
+
+ { "log-level", 1, 0, 'l' },
+ { "app-log-level", 1, 0, OPT_APP_LOG_LEVEL },
+ { "log-file", 1, 0, OPT_LOG_FILE },
+
+ { "report-file", 1, 0, OPT_REPORT_FILE },
+
+ /* Don't support this anymore, see comments in USAGE above.
+ { "a-pt", 1, 0, OPT_A_PT },
+ { "a-name", 1, 0, OPT_A_NAME },
+ { "a-clock", 1, 0, OPT_A_CLOCK },
+ { "a-bitrate", 1, 0, OPT_A_BITRATE },
+ { "a-ptime", 1, 0, OPT_A_PTIME },
+ */
+
+ { NULL, 0, 0, 0 },
+ };
+ int c;
+ int option_index;
+
+ /* Get local IP address for the default IP address */
+ {
+ const pj_str_t *hostname;
+ pj_sockaddr_in tmp_addr;
+ char *addr;
+
+ hostname = pj_gethostname();
+ pj_sockaddr_in_init(&tmp_addr, hostname, 0);
+ addr = pj_inet_ntoa(tmp_addr.sin_addr);
+ pj_ansi_strcpy(ip_addr, addr);
+ }
+
+ /* Init defaults */
+ app.max_calls = 1;
+ app.thread_count = 1;
+ app.sip_port = 5060;
+ app.rtp_start_port = RTP_START_PORT;
+ app.local_addr = pj_str(ip_addr);
+ app.log_level = 5;
+ app.app_log_level = 3;
+ app.log_filename = NULL;
+
+ /* Default codecs: */
+ app.audio_codec = audio_codecs[0];
+
+ /* Parse options */
+ pj_optind = 0;
+ while((c=pj_getopt_long(argc,argv, "c:d:p:r:i:l:g:qR",
+ long_options, &option_index))!=-1)
+ {
+ switch (c) {
+ case 'c':
+ app.max_calls = atoi(pj_optarg);
+ if (app.max_calls < 0 || app.max_calls > MAX_CALLS) {
+ PJ_LOG(3,(THIS_FILE, "Invalid max calls value %s", pj_optarg));
+ return 1;
+ }
+ break;
+ case 'g':
+ app.call_gap = atoi(pj_optarg);
+ break;
+ case 'R':
+ app.call_report = PJ_TRUE;
+ break;
+ case 'd':
+ app.duration = atoi(pj_optarg);
+ break;
+ case 'q':
+ app.auto_quit = 1;
+ break;
+
+ case 'p':
+ app.sip_port = atoi(pj_optarg);
+ break;
+ case 'r':
+ app.rtp_start_port = atoi(pj_optarg);
+ break;
+ case 'i':
+ app.local_addr = pj_str(pj_optarg);
+ break;
+
+ case 'l':
+ app.log_level = atoi(pj_optarg);
+ break;
+ case OPT_APP_LOG_LEVEL:
+ app.app_log_level = atoi(pj_optarg);
+ break;
+ case OPT_LOG_FILE:
+ app.log_filename = pj_optarg;
+ break;
+
+ case OPT_A_PT:
+ app.audio_codec.pt = atoi(pj_optarg);
+ break;
+ case OPT_A_NAME:
+ app.audio_codec.name = pj_optarg;
+ break;
+ case OPT_A_CLOCK:
+ app.audio_codec.clock_rate = atoi(pj_optarg);
+ break;
+ case OPT_A_BITRATE:
+ app.audio_codec.bit_rate = atoi(pj_optarg);
+ break;
+ case OPT_A_PTIME:
+ app.audio_codec.ptime = atoi(pj_optarg);
+ break;
+ case OPT_REPORT_FILE:
+ app.report_filename = pj_optarg;
+ break;
+
+ default:
+ puts(USAGE);
+ return 1;
+ }
+ }
+
+ /* Check if URL is specified */
+ if (pj_optind < argc)
+ app.uri_to_call = pj_str(argv[pj_optind]);
+
+ /* Build local URI and contact */
+ pj_ansi_sprintf( local_uri, "sip:%s:%d", app.local_addr.ptr, app.sip_port);
+ app.local_uri = pj_str(local_uri);
+ app.local_contact = app.local_uri;
+
+
+ return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * MEDIA STUFFS
+ */
+
+/*
+ * Create SDP session for a call.
+ */
+static pj_status_t create_sdp( pj_pool_t *pool,
+ struct call *call,
+ pjmedia_sdp_session **p_sdp)
+{
+ pj_time_val tv;
+ pjmedia_sdp_session *sdp;
+ pjmedia_sdp_media *m;
+ pjmedia_sdp_attr *attr;
+ pjmedia_transport_info tpinfo;
+ struct media_stream *audio = &call->media[0];
+
+ PJ_ASSERT_RETURN(pool && p_sdp, PJ_EINVAL);
+
+
+ /* Get transport info */
+ pjmedia_transport_info_init(&tpinfo);
+ pjmedia_transport_get_info(audio->transport, &tpinfo);
+
+ /* Create and initialize basic SDP session */
+ sdp = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_session));
+
+ pj_gettimeofday(&tv);
+ sdp->origin.user = pj_str("pjsip-siprtp");
+ sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL;
+ sdp->origin.net_type = pj_str("IN");
+ sdp->origin.addr_type = pj_str("IP4");
+ sdp->origin.addr = *pj_gethostname();
+ sdp->name = pj_str("pjsip");
+
+ /* Since we only support one media stream at present, put the
+ * SDP connection line in the session level.
+ */
+ sdp->conn = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_conn));
+ sdp->conn->net_type = pj_str("IN");
+ sdp->conn->addr_type = pj_str("IP4");
+ sdp->conn->addr = app.local_addr;
+
+
+ /* SDP time and attributes. */
+ sdp->time.start = sdp->time.stop = 0;
+ sdp->attr_count = 0;
+
+ /* Create media stream 0: */
+
+ sdp->media_count = 1;
+ m = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_media));
+ sdp->media[0] = m;
+
+ /* Standard media info: */
+ m->desc.media = pj_str("audio");
+ m->desc.port = pj_ntohs(tpinfo.sock_info.rtp_addr_name.ipv4.sin_port);
+ m->desc.port_count = 1;
+ m->desc.transport = pj_str("RTP/AVP");
+
+ /* Add format and rtpmap for each codec. */
+ m->desc.fmt_count = 1;
+ m->attr_count = 0;
+
+ {
+ pjmedia_sdp_rtpmap rtpmap;
+ pjmedia_sdp_attr *attr;
+ char ptstr[10];
+
+ sprintf(ptstr, "%d", app.audio_codec.pt);
+ pj_strdup2(pool, &m->desc.fmt[0], ptstr);
+ rtpmap.pt = m->desc.fmt[0];
+ rtpmap.clock_rate = app.audio_codec.clock_rate;
+ rtpmap.enc_name = pj_str(app.audio_codec.name);
+ rtpmap.param.slen = 0;
+
+ pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
+ m->attr[m->attr_count++] = attr;
+ }
+
+ /* Add sendrecv attribute. */
+ attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
+ attr->name = pj_str("sendrecv");
+ m->attr[m->attr_count++] = attr;
+
+#if 1
+ /*
+ * Add support telephony event
+ */
+ m->desc.fmt[m->desc.fmt_count++] = pj_str("121");
+ /* Add rtpmap. */
+ attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
+ attr->name = pj_str("rtpmap");
+ attr->value = pj_str("121 telephone-event/8000");
+ m->attr[m->attr_count++] = attr;
+ /* Add fmtp */
+ attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
+ attr->name = pj_str("fmtp");
+ attr->value = pj_str("121 0-15");
+ m->attr[m->attr_count++] = attr;
+#endif
+
+ /* Done */
+ *p_sdp = sdp;
+
+ return PJ_SUCCESS;
+}
+
+
+#if defined(PJ_WIN32) && PJ_WIN32 != 0
+#include <windows.h>
+static void boost_priority(void)
+{
+ SetPriorityClass( GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
+}
+
+#elif defined(PJ_LINUX) && PJ_LINUX != 0
+#include <pthread.h>
+static void boost_priority(void)
+{
+#define POLICY SCHED_FIFO
+ struct sched_param tp;
+ int max_prio;
+ int policy;
+ int rc;
+
+ if (sched_get_priority_min(POLICY) < sched_get_priority_max(POLICY))
+ max_prio = sched_get_priority_max(POLICY)-1;
+ else
+ max_prio = sched_get_priority_max(POLICY)+1;
+
+ /*
+ * Adjust process scheduling algorithm and priority
+ */
+ rc = sched_getparam(0, &tp);
+ if (rc != 0) {
+ app_perror( THIS_FILE, "sched_getparam error",
+ PJ_RETURN_OS_ERROR(rc));
+ return;
+ }
+ tp.__sched_priority = max_prio;
+
+ rc = sched_setscheduler(0, POLICY, &tp);
+ if (rc != 0) {
+ app_perror( THIS_FILE, "sched_setscheduler error",
+ PJ_RETURN_OS_ERROR(rc));
+ }
+
+ PJ_LOG(4, (THIS_FILE, "New process policy=%d, priority=%d",
+ policy, tp.__sched_priority));
+
+ /*
+ * Adjust thread scheduling algorithm and priority
+ */
+ rc = pthread_getschedparam(pthread_self(), &policy, &tp);
+ if (rc != 0) {
+ app_perror( THIS_FILE, "pthread_getschedparam error",
+ PJ_RETURN_OS_ERROR(rc));
+ return;
+ }
+
+ PJ_LOG(4, (THIS_FILE, "Old thread policy=%d, priority=%d",
+ policy, tp.__sched_priority));
+
+ policy = POLICY;
+ tp.__sched_priority = max_prio;
+
+ rc = pthread_setschedparam(pthread_self(), policy, &tp);
+ if (rc != 0) {
+ app_perror( THIS_FILE, "pthread_setschedparam error",
+ PJ_RETURN_OS_ERROR(rc));
+ return;
+ }
+
+ PJ_LOG(4, (THIS_FILE, "New thread policy=%d, priority=%d",
+ policy, tp.__sched_priority));
+}
+
+#else
+# define boost_priority()
+#endif
+
+
+/*
+ * This callback is called by media transport on receipt of RTP packet.
+ */
+static void on_rx_rtp(void *user_data, void *pkt, pj_ssize_t size)
+{
+ struct media_stream *strm;
+ pj_status_t status;
+ const pjmedia_rtp_hdr *hdr;
+ const void *payload;
+ unsigned payload_len;
+
+ strm = user_data;
+
+ /* Discard packet if media is inactive */
+ if (!strm->active)
+ return;
+
+ /* Check for errors */
+ if (size < 0) {
+ app_perror(THIS_FILE, "RTP recv() error", -size);
+ return;
+ }
+
+ /* Decode RTP packet. */
+ status = pjmedia_rtp_decode_rtp(&strm->in_sess,
+ pkt, size,
+ &hdr, &payload, &payload_len);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "RTP decode error", status);
+ return;
+ }
+
+ //PJ_LOG(4,(THIS_FILE, "Rx seq=%d", pj_ntohs(hdr->seq)));
+
+ /* Update the RTCP session. */
+ pjmedia_rtcp_rx_rtp(&strm->rtcp, pj_ntohs(hdr->seq),
+ pj_ntohl(hdr->ts), payload_len);
+
+ /* Update RTP session */
+ pjmedia_rtp_session_update(&strm->in_sess, hdr, NULL);
+
+}
+
+/*
+ * This callback is called by media transport on receipt of RTCP packet.
+ */
+static void on_rx_rtcp(void *user_data, void *pkt, pj_ssize_t size)
+{
+ struct media_stream *strm;
+
+ strm = user_data;
+
+ /* Discard packet if media is inactive */
+ if (!strm->active)
+ return;
+
+ /* Check for errors */
+ if (size < 0) {
+ app_perror(THIS_FILE, "Error receiving RTCP packet", -size);
+ return;
+ }
+
+ /* Update RTCP session */
+ pjmedia_rtcp_rx_rtcp(&strm->rtcp, pkt, size);
+}
+
+
+/*
+ * Media thread
+ *
+ * This is the thread to send and receive both RTP and RTCP packets.
+ */
+static int media_thread(void *arg)
+{
+ enum { RTCP_INTERVAL = 5000, RTCP_RAND = 2000 };
+ struct media_stream *strm = arg;
+ char packet[1500];
+ unsigned msec_interval;
+ pj_timestamp freq, next_rtp, next_rtcp;
+
+
+ /* Boost thread priority if necessary */
+ boost_priority();
+
+ /* Let things settle */
+ pj_thread_sleep(100);
+
+ msec_interval = strm->samples_per_frame * 1000 / strm->clock_rate;
+ pj_get_timestamp_freq(&freq);
+
+ pj_get_timestamp(&next_rtp);
+ next_rtp.u64 += (freq.u64 * msec_interval / 1000);
+
+ next_rtcp = next_rtp;
+ next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) / 1000);
+
+
+ while (!strm->thread_quit_flag) {
+ pj_timestamp now, lesser;
+ pj_time_val timeout;
+ pj_bool_t send_rtp, send_rtcp;
+
+ send_rtp = send_rtcp = PJ_FALSE;
+
+ /* Determine how long to sleep */
+ if (next_rtp.u64 < next_rtcp.u64) {
+ lesser = next_rtp;
+ send_rtp = PJ_TRUE;
+ } else {
+ lesser = next_rtcp;
+ send_rtcp = PJ_TRUE;
+ }
+
+ pj_get_timestamp(&now);
+ if (lesser.u64 <= now.u64) {
+ timeout.sec = timeout.msec = 0;
+ //printf("immediate "); fflush(stdout);
+ } else {
+ pj_uint64_t tick_delay;
+ tick_delay = lesser.u64 - now.u64;
+ timeout.sec = 0;
+ timeout.msec = (pj_uint32_t)(tick_delay * 1000 / freq.u64);
+ pj_time_val_normalize(&timeout);
+
+ //printf("%d:%03d ", timeout.sec, timeout.msec); fflush(stdout);
+ }
+
+ /* Wait for next interval */
+ //if (timeout.sec!=0 && timeout.msec!=0) {
+ pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
+ if (strm->thread_quit_flag)
+ break;
+ //}
+
+ pj_get_timestamp(&now);
+
+ if (send_rtp || next_rtp.u64 <= now.u64) {
+ /*
+ * Time to send RTP packet.
+ */
+ pj_status_t status;
+ const void *p_hdr;
+ const pjmedia_rtp_hdr *hdr;
+ pj_ssize_t size;
+ int hdrlen;
+
+ /* Format RTP header */
+ status = pjmedia_rtp_encode_rtp( &strm->out_sess, strm->si.tx_pt,
+ 0, /* marker bit */
+ strm->bytes_per_frame,
+ strm->samples_per_frame,
+ &p_hdr, &hdrlen);
+ if (status == PJ_SUCCESS) {
+
+ //PJ_LOG(4,(THIS_FILE, "\t\tTx seq=%d", pj_ntohs(hdr->seq)));
+
+ hdr = (const pjmedia_rtp_hdr*) p_hdr;
+
+ /* Copy RTP header to packet */
+ pj_memcpy(packet, hdr, hdrlen);
+
+ /* Zero the payload */
+ pj_bzero(packet+hdrlen, strm->bytes_per_frame);
+
+ /* Send RTP packet */
+ size = hdrlen + strm->bytes_per_frame;
+ status = pjmedia_transport_send_rtp(strm->transport,
+ packet, size);
+ if (status != PJ_SUCCESS)
+ app_perror(THIS_FILE, "Error sending RTP packet", status);
+
+ } else {
+ pj_assert(!"RTP encode() error");
+ }
+
+ /* Update RTCP SR */
+ pjmedia_rtcp_tx_rtp( &strm->rtcp, (pj_uint16_t)strm->bytes_per_frame);
+
+ /* Schedule next send */
+ next_rtp.u64 += (msec_interval * freq.u64 / 1000);
+ }
+
+
+ if (send_rtcp || next_rtcp.u64 <= now.u64) {
+ /*
+ * Time to send RTCP packet.
+ */
+ void *rtcp_pkt;
+ int rtcp_len;
+ pj_ssize_t size;
+ pj_status_t status;
+
+ /* Build RTCP packet */
+ pjmedia_rtcp_build_rtcp(&strm->rtcp, &rtcp_pkt, &rtcp_len);
+
+
+ /* Send packet */
+ size = rtcp_len;
+ status = pjmedia_transport_send_rtcp(strm->transport,
+ rtcp_pkt, size);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error sending RTCP packet", status);
+ }
+
+ /* Schedule next send */
+ next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) /
+ 1000);
+ }
+ }
+
+ return 0;
+}
+
+
+/* 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)
+{
+ struct call *call;
+ pj_pool_t *pool;
+ struct media_stream *audio;
+ const pjmedia_sdp_session *local_sdp, *remote_sdp;
+ struct codec *codec_desc = NULL;
+ unsigned i;
+
+ call = inv->mod_data[mod_siprtp.id];
+ pool = inv->dlg->pool;
+ audio = &call->media[0];
+
+ /* If this is a mid-call media update, then destroy existing media */
+ if (audio->thread != NULL)
+ destroy_call_media(call->index);
+
+
+ /* Do nothing if media negotiation has failed */
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "SDP negotiation failed", status);
+ return;
+ }
+
+
+ /* Capture stream definition from the SDP */
+ pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);
+ pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp);
+
+ status = pjmedia_stream_info_from_sdp(&audio->si, inv->pool, app.med_endpt,
+ local_sdp, remote_sdp, 0);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error creating stream info from SDP", status);
+ return;
+ }
+
+ /* Get the remainder of codec information from codec descriptor */
+ if (audio->si.fmt.pt == app.audio_codec.pt)
+ codec_desc = &app.audio_codec;
+ else {
+ /* Find the codec description in codec array */
+ for (i=0; i<PJ_ARRAY_SIZE(audio_codecs); ++i) {
+ if (audio_codecs[i].pt == audio->si.fmt.pt) {
+ codec_desc = &audio_codecs[i];
+ break;
+ }
+ }
+
+ if (codec_desc == NULL) {
+ PJ_LOG(3, (THIS_FILE, "Error: Invalid codec payload type"));
+ return;
+ }
+ }
+
+ audio->clock_rate = audio->si.fmt.clock_rate;
+ audio->samples_per_frame = audio->clock_rate * codec_desc->ptime / 1000;
+ audio->bytes_per_frame = codec_desc->bit_rate * codec_desc->ptime / 1000 / 8;
+
+
+ pjmedia_rtp_session_init(&audio->out_sess, audio->si.tx_pt,
+ pj_rand());
+ pjmedia_rtp_session_init(&audio->in_sess, audio->si.fmt.pt, 0);
+ pjmedia_rtcp_init(&audio->rtcp, "rtcp", audio->clock_rate,
+ audio->samples_per_frame, 0);
+
+
+ /* Attach media to transport */
+ status = pjmedia_transport_attach(audio->transport, audio,
+ &audio->si.rem_addr,
+ &audio->si.rem_rtcp,
+ sizeof(pj_sockaddr_in),
+ &on_rx_rtp,
+ &on_rx_rtcp);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error on pjmedia_transport_attach()", status);
+ return;
+ }
+
+ /* Start media thread. */
+ audio->thread_quit_flag = 0;
+#if PJ_HAS_THREADS
+ status = pj_thread_create( inv->pool, "media", &media_thread, audio,
+ 0, 0, &audio->thread);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error creating media thread", status);
+ return;
+ }
+#endif
+
+ /* Set the media as active */
+ audio->active = PJ_TRUE;
+}
+
+
+
+/* Destroy call's media */
+static void destroy_call_media(unsigned call_index)
+{
+ struct media_stream *audio = &app.call[call_index].media[0];
+
+ if (audio) {
+ audio->active = PJ_FALSE;
+
+ if (audio->thread) {
+ audio->thread_quit_flag = 1;
+ pj_thread_join(audio->thread);
+ pj_thread_destroy(audio->thread);
+ audio->thread = NULL;
+ audio->thread_quit_flag = 0;
+ }
+
+ pjmedia_transport_detach(audio->transport, audio);
+ }
+}
+
+
+/*****************************************************************************
+ * USER INTERFACE STUFFS
+ */
+
+static void call_get_duration(int call_index, pj_time_val *dur)
+{
+ struct call *call = &app.call[call_index];
+ pjsip_inv_session *inv;
+
+ dur->sec = dur->msec = 0;
+
+ if (!call)
+ return;
+
+ inv = call->inv;
+ if (!inv)
+ return;
+
+ if (inv->state >= PJSIP_INV_STATE_CONFIRMED && call->connect_time.sec) {
+
+ pj_gettimeofday(dur);
+ PJ_TIME_VAL_SUB((*dur), call->connect_time);
+ }
+}
+
+
+static const char *good_number(char *buf, pj_int32_t val)
+{
+ if (val < 1000) {
+ pj_ansi_sprintf(buf, "%d", val);
+ } else if (val < 1000000) {
+ pj_ansi_sprintf(buf, "%d.%02dK",
+ val / 1000,
+ (val % 1000) / 100);
+ } else {
+ pj_ansi_sprintf(buf, "%d.%02dM",
+ val / 1000000,
+ (val % 1000000) / 10000);
+ }
+
+ return buf;
+}
+
+
+
+static void print_avg_stat(void)
+{
+#define MIN_(var,val) if ((int)val < (int)var) var = val
+#define MAX_(var,val) if ((int)val > (int)var) var = val
+#define AVG_(var,val) var = ( ((var * count) + val) / (count+1) )
+#define BIGVAL 0x7FFFFFFFL
+ struct stat_entry
+ {
+ int min, avg, max;
+ };
+
+ struct stat_entry call_dur, call_pdd;
+ pjmedia_rtcp_stat min_stat, avg_stat, max_stat;
+
+ char srx_min[16], srx_avg[16], srx_max[16];
+ char brx_min[16], brx_avg[16], brx_max[16];
+ char stx_min[16], stx_avg[16], stx_max[16];
+ char btx_min[16], btx_avg[16], btx_max[16];
+
+
+ unsigned i, count;
+
+ pj_bzero(&call_dur, sizeof(call_dur));
+ call_dur.min = BIGVAL;
+
+ pj_bzero(&call_pdd, sizeof(call_pdd));
+ call_pdd.min = BIGVAL;
+
+ pj_bzero(&min_stat, sizeof(min_stat));
+ min_stat.rx.pkt = min_stat.tx.pkt = BIGVAL;
+ min_stat.rx.bytes = min_stat.tx.bytes = BIGVAL;
+ min_stat.rx.loss = min_stat.tx.loss = BIGVAL;
+ min_stat.rx.dup = min_stat.tx.dup = BIGVAL;
+ min_stat.rx.reorder = min_stat.tx.reorder = BIGVAL;
+ min_stat.rx.jitter.min = min_stat.tx.jitter.min = BIGVAL;
+ min_stat.rtt.min = BIGVAL;
+
+ pj_bzero(&avg_stat, sizeof(avg_stat));
+ pj_bzero(&max_stat, sizeof(max_stat));
+
+
+ for (i=0, count=0; i<app.max_calls; ++i) {
+
+ struct call *call = &app.call[i];
+ struct media_stream *audio = &call->media[0];
+ pj_time_val dur;
+ unsigned msec_dur;
+
+ if (call->inv == NULL ||
+ call->inv->state < PJSIP_INV_STATE_CONFIRMED ||
+ call->connect_time.sec == 0)
+ {
+ continue;
+ }
+
+ /* Duration */
+ call_get_duration(i, &dur);
+ msec_dur = PJ_TIME_VAL_MSEC(dur);
+
+ MIN_(call_dur.min, msec_dur);
+ MAX_(call_dur.max, msec_dur);
+ AVG_(call_dur.avg, msec_dur);
+
+ /* Connect delay */
+ if (call->connect_time.sec) {
+ pj_time_val t = call->connect_time;
+ PJ_TIME_VAL_SUB(t, call->start_time);
+ msec_dur = PJ_TIME_VAL_MSEC(t);
+ } else {
+ msec_dur = 10;
+ }
+
+ MIN_(call_pdd.min, msec_dur);
+ MAX_(call_pdd.max, msec_dur);
+ AVG_(call_pdd.avg, msec_dur);
+
+ /* RX Statistisc: */
+
+ /* Packets */
+ MIN_(min_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
+ MAX_(max_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
+ AVG_(avg_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
+
+ /* Bytes */
+ MIN_(min_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
+ MAX_(max_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
+ AVG_(avg_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
+
+
+ /* Packet loss */
+ MIN_(min_stat.rx.loss, audio->rtcp.stat.rx.loss);
+ MAX_(max_stat.rx.loss, audio->rtcp.stat.rx.loss);
+ AVG_(avg_stat.rx.loss, audio->rtcp.stat.rx.loss);
+
+ /* Packet dup */
+ MIN_(min_stat.rx.dup, audio->rtcp.stat.rx.dup);
+ MAX_(max_stat.rx.dup, audio->rtcp.stat.rx.dup);
+ AVG_(avg_stat.rx.dup, audio->rtcp.stat.rx.dup);
+
+ /* Packet reorder */
+ MIN_(min_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
+ MAX_(max_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
+ AVG_(avg_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
+
+ /* Jitter */
+ MIN_(min_stat.rx.jitter.min, audio->rtcp.stat.rx.jitter.min);
+ MAX_(max_stat.rx.jitter.max, audio->rtcp.stat.rx.jitter.max);
+ AVG_(avg_stat.rx.jitter.mean, audio->rtcp.stat.rx.jitter.mean);
+
+
+ /* TX Statistisc: */
+
+ /* Packets */
+ MIN_(min_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
+ MAX_(max_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
+ AVG_(avg_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
+
+ /* Bytes */
+ MIN_(min_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
+ MAX_(max_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
+ AVG_(avg_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
+
+ /* Packet loss */
+ MIN_(min_stat.tx.loss, audio->rtcp.stat.tx.loss);
+ MAX_(max_stat.tx.loss, audio->rtcp.stat.tx.loss);
+ AVG_(avg_stat.tx.loss, audio->rtcp.stat.tx.loss);
+
+ /* Packet dup */
+ MIN_(min_stat.tx.dup, audio->rtcp.stat.tx.dup);
+ MAX_(max_stat.tx.dup, audio->rtcp.stat.tx.dup);
+ AVG_(avg_stat.tx.dup, audio->rtcp.stat.tx.dup);
+
+ /* Packet reorder */
+ MIN_(min_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
+ MAX_(max_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
+ AVG_(avg_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
+
+ /* Jitter */
+ MIN_(min_stat.tx.jitter.min, audio->rtcp.stat.tx.jitter.min);
+ MAX_(max_stat.tx.jitter.max, audio->rtcp.stat.tx.jitter.max);
+ AVG_(avg_stat.tx.jitter.mean, audio->rtcp.stat.tx.jitter.mean);
+
+
+ /* RTT */
+ MIN_(min_stat.rtt.min, audio->rtcp.stat.rtt.min);
+ MAX_(max_stat.rtt.max, audio->rtcp.stat.rtt.max);
+ AVG_(avg_stat.rtt.mean, audio->rtcp.stat.rtt.mean);
+
+ ++count;
+ }
+
+ if (count == 0) {
+ puts("No active calls");
+ return;
+ }
+
+ printf("Total %d call(s) active.\n"
+ " Average Statistics\n"
+ " min avg max \n"
+ " -----------------------\n"
+ " call duration: %7d %7d %7d %s\n"
+ " connect delay: %7d %7d %7d %s\n"
+ " RX stat:\n"
+ " packets: %7s %7s %7s %s\n"
+ " payload: %7s %7s %7s %s\n"
+ " loss: %7d %7d %7d %s\n"
+ " percent loss: %7.3f %7.3f %7.3f %s\n"
+ " dup: %7d %7d %7d %s\n"
+ " reorder: %7d %7d %7d %s\n"
+ " jitter: %7.3f %7.3f %7.3f %s\n"
+ " TX stat:\n"
+ " packets: %7s %7s %7s %s\n"
+ " payload: %7s %7s %7s %s\n"
+ " loss: %7d %7d %7d %s\n"
+ " percent loss: %7.3f %7.3f %7.3f %s\n"
+ " dup: %7d %7d %7d %s\n"
+ " reorder: %7d %7d %7d %s\n"
+ " jitter: %7.3f %7.3f %7.3f %s\n"
+ " RTT : %7.3f %7.3f %7.3f %s\n"
+ ,
+ count,
+ call_dur.min/1000, call_dur.avg/1000, call_dur.max/1000,
+ "seconds",
+
+ call_pdd.min, call_pdd.avg, call_pdd.max,
+ "ms",
+
+ /* rx */
+
+ good_number(srx_min, min_stat.rx.pkt),
+ good_number(srx_avg, avg_stat.rx.pkt),
+ good_number(srx_max, max_stat.rx.pkt),
+ "packets",
+
+ good_number(brx_min, min_stat.rx.bytes),
+ good_number(brx_avg, avg_stat.rx.bytes),
+ good_number(brx_max, max_stat.rx.bytes),
+ "bytes",
+
+ min_stat.rx.loss, avg_stat.rx.loss, max_stat.rx.loss,
+ "packets",
+
+ min_stat.rx.loss*100.0/(min_stat.rx.pkt+min_stat.rx.loss),
+ avg_stat.rx.loss*100.0/(avg_stat.rx.pkt+avg_stat.rx.loss),
+ max_stat.rx.loss*100.0/(max_stat.rx.pkt+max_stat.rx.loss),
+ "%",
+
+
+ min_stat.rx.dup, avg_stat.rx.dup, max_stat.rx.dup,
+ "packets",
+
+ min_stat.rx.reorder, avg_stat.rx.reorder, max_stat.rx.reorder,
+ "packets",
+
+ min_stat.rx.jitter.min/1000.0,
+ avg_stat.rx.jitter.mean/1000.0,
+ max_stat.rx.jitter.max/1000.0,
+ "ms",
+
+ /* tx */
+
+ good_number(stx_min, min_stat.tx.pkt),
+ good_number(stx_avg, avg_stat.tx.pkt),
+ good_number(stx_max, max_stat.tx.pkt),
+ "packets",
+
+ good_number(btx_min, min_stat.tx.bytes),
+ good_number(btx_avg, avg_stat.tx.bytes),
+ good_number(btx_max, max_stat.tx.bytes),
+ "bytes",
+
+ min_stat.tx.loss, avg_stat.tx.loss, max_stat.tx.loss,
+ "packets",
+
+ min_stat.tx.loss*100.0/(min_stat.tx.pkt+min_stat.tx.loss),
+ avg_stat.tx.loss*100.0/(avg_stat.tx.pkt+avg_stat.tx.loss),
+ max_stat.tx.loss*100.0/(max_stat.tx.pkt+max_stat.tx.loss),
+ "%",
+
+ min_stat.tx.dup, avg_stat.tx.dup, max_stat.tx.dup,
+ "packets",
+
+ min_stat.tx.reorder, avg_stat.tx.reorder, max_stat.tx.reorder,
+ "packets",
+
+ min_stat.tx.jitter.min/1000.0,
+ avg_stat.tx.jitter.mean/1000.0,
+ max_stat.tx.jitter.max/1000.0,
+ "ms",
+
+ /* rtt */
+ min_stat.rtt.min/1000.0,
+ avg_stat.rtt.mean/1000.0,
+ max_stat.rtt.max/1000.0,
+ "ms"
+ );
+
+}
+
+
+#include "siprtp_report.c"
+
+
+static void list_calls()
+{
+ unsigned i;
+ puts("List all calls:");
+ for (i=0; i<app.max_calls; ++i) {
+ if (!app.call[i].inv)
+ continue;
+ print_call(i);
+ }
+}
+
+static void hangup_call(unsigned index)
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ if (app.call[index].inv == NULL)
+ return;
+
+ status = pjsip_inv_end_session(app.call[index].inv, 603, NULL, &tdata);
+ if (status==PJ_SUCCESS && tdata!=NULL)
+ pjsip_inv_send_msg(app.call[index].inv, tdata);
+}
+
+static void hangup_all_calls()
+{
+ unsigned i;
+ for (i=0; i<app.max_calls; ++i) {
+ if (!app.call[i].inv)
+ continue;
+ hangup_call(i);
+ pj_thread_sleep(app.call_gap);
+ }
+
+ /* Wait until all calls are terminated */
+ for (i=0; i<app.max_calls; ++i) {
+ while (app.call[i].inv)
+ pj_thread_sleep(10);
+ }
+}
+
+static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
+{
+ char *p;
+
+ printf("%s (empty to cancel): ", title); fflush(stdout);
+ if (fgets(buf, len, stdin) == NULL)
+ return PJ_FALSE;
+
+ /* Remove trailing newlines. */
+ for (p=buf; ; ++p) {
+ if (*p=='\r' || *p=='\n') *p='\0';
+ else if (!*p) break;
+ }
+
+ if (!*buf)
+ return PJ_FALSE;
+
+ return PJ_TRUE;
+}
+
+
+static const char *MENU =
+"\n"
+"Enter menu character:\n"
+" s Summary\n"
+" l List all calls\n"
+" h Hangup a call\n"
+" H Hangup all calls\n"
+" q Quit\n"
+"\n";
+
+
+/* Main screen menu */
+static void console_main()
+{
+ char input1[10];
+ unsigned i;
+
+ printf("%s", MENU);
+
+ for (;;) {
+ printf(">>> "); fflush(stdout);
+ if (fgets(input1, sizeof(input1), stdin) == NULL) {
+ puts("EOF while reading stdin, will quit now..");
+ break;
+ }
+
+ switch (input1[0]) {
+
+ case 's':
+ print_avg_stat();
+ break;
+
+ case 'l':
+ list_calls();
+ break;
+
+ case 'h':
+ if (!simple_input("Call number to hangup", input1, sizeof(input1)))
+ break;
+
+ i = atoi(input1);
+ hangup_call(i);
+ break;
+
+ case 'H':
+ hangup_all_calls();
+ break;
+
+ case 'q':
+ goto on_exit;
+
+ default:
+ puts("Invalid command");
+ printf("%s", MENU);
+ break;
+ }
+
+ fflush(stdout);
+ }
+
+on_exit:
+ hangup_all_calls();
+}
+
+
+/*****************************************************************************
+ * Below is a simple module to log all incoming and outgoing SIP messages
+ */
+
+
+/* Notification on incoming messages */
+static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
+{
+ PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
+ "%s\n"
+ "--end msg--",
+ rdata->msg_info.len,
+ pjsip_rx_data_get_info(rdata),
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port,
+ rdata->msg_info.msg_buf));
+
+ /* Always return false, otherwise messages will not get processed! */
+ return PJ_FALSE;
+}
+
+/* Notification on outgoing messages */
+static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
+{
+
+ /* Important note:
+ * tp_info field is only valid after outgoing messages has passed
+ * transport layer. So don't try to access tp_info when the module
+ * has lower priority than transport layer.
+ */
+
+ PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
+ "%s\n"
+ "--end msg--",
+ (tdata->buf.cur - tdata->buf.start),
+ pjsip_tx_data_get_info(tdata),
+ tdata->tp_info.dst_name,
+ tdata->tp_info.dst_port,
+ tdata->buf.start));
+
+ /* Always return success, otherwise message will not get sent! */
+ return PJ_SUCCESS;
+}
+
+/* The module instance. */
+static pjsip_module msg_logger =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-siprtp-log", 14 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &logger_on_rx_msg, /* on_rx_request() */
+ &logger_on_rx_msg, /* on_rx_response() */
+ &logger_on_tx_msg, /* on_tx_request. */
+ &logger_on_tx_msg, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+
+};
+
+
+
+/*****************************************************************************
+ * Console application custom logging:
+ */
+
+
+static FILE *log_file;
+
+
+static void app_log_writer(int level, const char *buffer, int len)
+{
+ /* Write to both stdout and file. */
+
+ if (level <= app.app_log_level)
+ pj_log_write(level, buffer, len);
+
+ if (log_file) {
+ int count = fwrite(buffer, len, 1, log_file);
+ PJ_UNUSED_ARG(count);
+ fflush(log_file);
+ }
+}
+
+
+pj_status_t app_logging_init(void)
+{
+ /* Redirect log function to ours */
+
+ pj_log_set_log_func( &app_log_writer );
+
+ /* If output log file is desired, create the file: */
+
+ if (app.log_filename) {
+ log_file = fopen(app.log_filename, "wt");
+ if (log_file == NULL) {
+ PJ_LOG(1,(THIS_FILE, "Unable to open log file %s",
+ app.log_filename));
+ return -1;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+void app_logging_shutdown(void)
+{
+ /* Close logging file, if any: */
+
+ if (log_file) {
+ fclose(log_file);
+ log_file = NULL;
+ }
+}
+
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+ unsigned i;
+ pj_status_t status;
+
+ /* Must init PJLIB first */
+ status = pj_init();
+ if (status != PJ_SUCCESS)
+ return 1;
+
+ /* Get command line options */
+ status = init_options(argc, argv);
+ if (status != PJ_SUCCESS)
+ return 1;
+
+ /* Verify options: */
+
+ /* Auto-quit can not be specified for UAS */
+ if (app.auto_quit && app.uri_to_call.slen == 0) {
+ printf("Error: --auto-quit option only valid for outgoing "
+ "mode (UAC) only\n");
+ return 1;
+ }
+
+ /* Init logging */
+ status = app_logging_init();
+ if (status != PJ_SUCCESS)
+ return 1;
+
+ /* Init SIP etc */
+ status = init_sip();
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Initialization has failed", status);
+ destroy_sip();
+ return 1;
+ }
+
+ /* Register module to log incoming/outgoing messages */
+ pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
+
+ /* Init media */
+ status = init_media();
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Media initialization failed", status);
+ destroy_sip();
+ return 1;
+ }
+
+ /* Start worker threads */
+#if PJ_HAS_THREADS
+ for (i=0; i<app.thread_count; ++i) {
+ pj_thread_create( app.pool, "app", &sip_worker_thread, NULL,
+ 0, 0, &app.sip_thread[i]);
+ }
+#endif
+
+ /* If URL is specified, then make call immediately */
+ if (app.uri_to_call.slen) {
+ unsigned i;
+
+ PJ_LOG(3,(THIS_FILE, "Making %d calls to %s..", app.max_calls,
+ app.uri_to_call.ptr));
+
+ for (i=0; i<app.max_calls; ++i) {
+ status = make_call(&app.uri_to_call);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error making call", status);
+ break;
+ }
+ pj_thread_sleep(app.call_gap);
+ }
+
+ if (app.auto_quit) {
+ /* Wait for calls to complete */
+ while (app.uac_calls < app.max_calls)
+ pj_thread_sleep(100);
+ pj_thread_sleep(200);
+ } else {
+#if PJ_HAS_THREADS
+ /* Start user interface loop */
+ console_main();
+#endif
+ }
+
+ } else {
+
+ PJ_LOG(3,(THIS_FILE, "Ready for incoming calls (max=%d)",
+ app.max_calls));
+
+#if PJ_HAS_THREADS
+ /* Start user interface loop */
+ console_main();
+#endif
+ }
+
+#if !PJ_HAS_THREADS
+ PJ_LOG(3,(THIS_FILE, "Press Ctrl-C to quit"));
+ for (;;) {
+ pj_time_val t = {0, 10};
+ pjsip_endpt_handle_events(app.sip_endpt, &t);
+ }
+#endif
+
+ /* Shutting down... */
+ destroy_sip();
+ destroy_media();
+
+ if (app.pool) {
+ pj_pool_release(app.pool);
+ app.pool = NULL;
+ pj_caching_pool_destroy(&app.cp);
+ }
+
+ app_logging_shutdown();
+
+ /* Shutdown PJLIB */
+ pj_shutdown();
+
+ return 0;
+}
+
diff --git a/pjsip-apps/src/samples/siprtp_report.c b/pjsip-apps/src/samples/siprtp_report.c
new file mode 100644
index 0000000..6643f9d
--- /dev/null
+++ b/pjsip-apps/src/samples/siprtp_report.c
@@ -0,0 +1,231 @@
+/* $Id: siprtp_report.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+
+/*
+ * DO NOT COMPILE THIS FILE ON ITS OWN!
+ *
+ * This file is included by siprtp.c to implement the reporting capability
+ * to a separate file, so that user can implement different reporting
+ * functionality (such as writing to XML file).
+ */
+
+
+static void print_call(int call_index)
+{
+ struct call *call = &app.call[call_index];
+ int len;
+ pjsip_inv_session *inv = call->inv;
+ pjsip_dialog *dlg = inv->dlg;
+ struct media_stream *audio = &call->media[0];
+ char userinfo[128];
+ char duration[80], last_update[80];
+ char bps[16], ipbps[16], packets[16], bytes[16], ipbytes[16];
+ unsigned decor;
+ pj_time_val now;
+
+
+ decor = pj_log_get_decor();
+ pj_log_set_decor(PJ_LOG_HAS_NEWLINE);
+
+ pj_gettimeofday(&now);
+
+ if (app.report_filename)
+ puts(app.report_filename);
+
+ /* Print duration */
+ if (inv->state >= PJSIP_INV_STATE_CONFIRMED && call->connect_time.sec) {
+
+ PJ_TIME_VAL_SUB(now, call->connect_time);
+
+ sprintf(duration, " [duration: %02ld:%02ld:%02ld.%03ld]",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ (now.sec % 60),
+ now.msec);
+
+ } else {
+ duration[0] = '\0';
+ }
+
+
+
+ /* Call number and state */
+ PJ_LOG(3, (THIS_FILE,
+ "Call #%d: %s%s",
+ call_index, pjsip_inv_state_name(inv->state),
+ duration));
+
+
+
+ /* Call identification */
+ len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
+ if (len < 0)
+ pj_ansi_strcpy(userinfo, "<--uri too long-->");
+ else
+ userinfo[len] = '\0';
+
+ PJ_LOG(3, (THIS_FILE, " %s", userinfo));
+
+
+ if (call->inv == NULL || call->inv->state < PJSIP_INV_STATE_CONFIRMED ||
+ call->connect_time.sec == 0)
+ {
+ pj_log_set_decor(decor);
+ return;
+ }
+
+
+ /* Signaling quality */
+ {
+ char pdd[64], connectdelay[64];
+ pj_time_val t;
+
+ if (call->response_time.sec) {
+ t = call->response_time;
+ PJ_TIME_VAL_SUB(t, call->start_time);
+ sprintf(pdd, "got 1st response in %ld ms", PJ_TIME_VAL_MSEC(t));
+ } else {
+ pdd[0] = '\0';
+ }
+
+ if (call->connect_time.sec) {
+ t = call->connect_time;
+ PJ_TIME_VAL_SUB(t, call->start_time);
+ sprintf(connectdelay, ", connected after: %ld ms",
+ PJ_TIME_VAL_MSEC(t));
+ } else {
+ connectdelay[0] = '\0';
+ }
+
+ PJ_LOG(3, (THIS_FILE,
+ " Signaling quality: %s%s", pdd, connectdelay));
+ }
+
+
+ PJ_LOG(3, (THIS_FILE,
+ " Stream #0: audio %.*s@%dHz, %dms/frame, %sB/s (%sB/s +IP hdr)",
+ (int)audio->si.fmt.encoding_name.slen,
+ audio->si.fmt.encoding_name.ptr,
+ audio->clock_rate,
+ audio->samples_per_frame * 1000 / audio->clock_rate,
+ good_number(bps, audio->bytes_per_frame * audio->clock_rate / audio->samples_per_frame),
+ good_number(ipbps, (audio->bytes_per_frame+32) * audio->clock_rate / audio->samples_per_frame)));
+
+ if (audio->rtcp.stat.rx.update_cnt == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, audio->rtcp.stat.rx.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ PJ_LOG(3, (THIS_FILE,
+ " RX stat last update: %s\n"
+ " total %s packets %sB received (%sB +IP hdr)%s\n"
+ " pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
+ " (msec) min avg max last\n"
+ " loss period: %7.3f %7.3f %7.3f %7.3f%s\n"
+ " jitter : %7.3f %7.3f %7.3f %7.3f%s",
+ last_update,
+ good_number(packets, audio->rtcp.stat.rx.pkt),
+ good_number(bytes, audio->rtcp.stat.rx.bytes),
+ good_number(ipbytes, audio->rtcp.stat.rx.bytes + audio->rtcp.stat.rx.pkt * 32),
+ "",
+ audio->rtcp.stat.rx.loss,
+ audio->rtcp.stat.rx.loss * 100.0 / (audio->rtcp.stat.rx.pkt + audio->rtcp.stat.rx.loss),
+ audio->rtcp.stat.rx.dup,
+ audio->rtcp.stat.rx.dup * 100.0 / (audio->rtcp.stat.rx.pkt + audio->rtcp.stat.rx.loss),
+ audio->rtcp.stat.rx.reorder,
+ audio->rtcp.stat.rx.reorder * 100.0 / (audio->rtcp.stat.rx.pkt + audio->rtcp.stat.rx.loss),
+ "",
+ audio->rtcp.stat.rx.loss_period.min / 1000.0,
+ audio->rtcp.stat.rx.loss_period.mean / 1000.0,
+ audio->rtcp.stat.rx.loss_period.max / 1000.0,
+ audio->rtcp.stat.rx.loss_period.last / 1000.0,
+ "",
+ audio->rtcp.stat.rx.jitter.min / 1000.0,
+ audio->rtcp.stat.rx.jitter.mean / 1000.0,
+ audio->rtcp.stat.rx.jitter.max / 1000.0,
+ audio->rtcp.stat.rx.jitter.last / 1000.0,
+ ""
+ ));
+
+
+ if (audio->rtcp.stat.tx.update_cnt == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, audio->rtcp.stat.tx.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ PJ_LOG(3, (THIS_FILE,
+ " TX stat last update: %s\n"
+ " total %s packets %sB sent (%sB +IP hdr)%s\n"
+ " pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
+ " (msec) min avg max last\n"
+ " loss period: %7.3f %7.3f %7.3f %7.3f%s\n"
+ " jitter : %7.3f %7.3f %7.3f %7.3f%s",
+ last_update,
+ good_number(packets, audio->rtcp.stat.tx.pkt),
+ good_number(bytes, audio->rtcp.stat.tx.bytes),
+ good_number(ipbytes, audio->rtcp.stat.tx.bytes + audio->rtcp.stat.tx.pkt * 32),
+ "",
+ audio->rtcp.stat.tx.loss,
+ audio->rtcp.stat.tx.loss * 100.0 / (audio->rtcp.stat.tx.pkt + audio->rtcp.stat.tx.loss),
+ audio->rtcp.stat.tx.dup,
+ audio->rtcp.stat.tx.dup * 100.0 / (audio->rtcp.stat.tx.pkt + audio->rtcp.stat.tx.loss),
+ audio->rtcp.stat.tx.reorder,
+ audio->rtcp.stat.tx.reorder * 100.0 / (audio->rtcp.stat.tx.pkt + audio->rtcp.stat.tx.loss),
+ "",
+ audio->rtcp.stat.tx.loss_period.min / 1000.0,
+ audio->rtcp.stat.tx.loss_period.mean / 1000.0,
+ audio->rtcp.stat.tx.loss_period.max / 1000.0,
+ audio->rtcp.stat.tx.loss_period.last / 1000.0,
+ "",
+ audio->rtcp.stat.tx.jitter.min / 1000.0,
+ audio->rtcp.stat.tx.jitter.mean / 1000.0,
+ audio->rtcp.stat.tx.jitter.max / 1000.0,
+ audio->rtcp.stat.tx.jitter.last / 1000.0,
+ ""
+ ));
+
+
+ PJ_LOG(3, (THIS_FILE,
+ " RTT delay : %7.3f %7.3f %7.3f %7.3f%s\n",
+ audio->rtcp.stat.rtt.min / 1000.0,
+ audio->rtcp.stat.rtt.mean / 1000.0,
+ audio->rtcp.stat.rtt.max / 1000.0,
+ audio->rtcp.stat.rtt.last / 1000.0,
+ ""
+ ));
+
+ pj_log_set_decor(decor);
+}
+
diff --git a/pjsip-apps/src/samples/sipstateless.c b/pjsip-apps/src/samples/sipstateless.c
new file mode 100644
index 0000000..0de89a6
--- /dev/null
+++ b/pjsip-apps/src/samples/sipstateless.c
@@ -0,0 +1,243 @@
+/* $Id: sipstateless.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+/**
+ * sipcore.c
+ *
+ * A simple program to respond any incoming requests (except ACK, of course!)
+ * with any status code (taken from command line argument, with the default
+ * is 501/Not Implemented).
+ */
+
+
+/* Include all headers. */
+#include <pjsip.h>
+#include <pjlib-util.h>
+#include <pjlib.h>
+
+
+/* If this macro is set, UDP transport will be initialized at port 5060 */
+#define HAS_UDP_TRANSPORT
+
+/* If this macro is set, TCP transport will be initialized at port 5060 */
+#define HAS_TCP_TRANSPORT (1 && PJ_HAS_TCP)
+
+/* Log identification */
+#define THIS_FILE "sipstateless.c"
+
+
+/* Global SIP endpoint */
+static pjsip_endpoint *sip_endpt;
+
+/* What response code to be sent (default is 501/Not Implemented) */
+static int code = PJSIP_SC_NOT_IMPLEMENTED;
+
+/* Additional header list */
+struct pjsip_hdr hdr_list;
+
+/* usage() */
+static void usage(void)
+{
+ puts("Usage:");
+ puts(" sipstateless [code] [-H HDR] ..");
+ puts("");
+ puts("Options:");
+ puts(" code SIP status code to send (default: 501/Not Implemented");
+ puts(" -H HDR Specify additional headers to send with response");
+ puts(" This option may be specified more than once.");
+ puts(" Example:");
+ puts(" -H 'Expires: 300' -H 'Contact: <sip:localhost>'");
+}
+
+
+/* Callback to handle incoming requests. */
+static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
+{
+ /* Respond (statelessly) all incoming requests (except ACK!)
+ * with 501 (Not Implemented)
+ */
+ if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
+ pjsip_endpt_respond_stateless( sip_endpt, rdata,
+ code, NULL,
+ &hdr_list, NULL);
+ }
+ return PJ_TRUE;
+}
+
+
+
+/*
+ * main()
+ *
+ */
+int main(int argc, char *argv[])
+{
+ pj_caching_pool cp;
+ pj_pool_t *pool = NULL;
+ 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() */
+ &on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+ };
+ int c;
+ pj_status_t status;
+
+ /* 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 global endpoint: */
+ {
+ /* Endpoint MUST be assigned a globally unique name.
+ * Ideally we should put hostname or public IP address, but
+ * we'll just use an arbitrary name here.
+ */
+
+ /* Create the endpoint: */
+ status = pjsip_endpt_create(&cp.factory, "sipstateless",
+ &sip_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ }
+
+ /* Parse arguments */
+ pj_optind = 0;
+ pj_list_init(&hdr_list);
+ while ((c=pj_getopt(argc, argv , "H:")) != -1) {
+ switch (c) {
+ case 'H':
+ if (pool == NULL) {
+ pool = pj_pool_create(&cp.factory, "sipstateless", 1000,
+ 1000, NULL);
+ }
+
+ if (pool) {
+ char *name;
+ name = strtok(pj_optarg, ":");
+ if (name == NULL) {
+ puts("Error: invalid header format");
+ return 1;
+ } else {
+ char *val = strtok(NULL, "\r\n");
+ pjsip_generic_string_hdr *h;
+ pj_str_t hname, hvalue;
+
+ hname = pj_str(name);
+ hvalue = pj_str(val);
+
+ h = pjsip_generic_string_hdr_create(pool, &hname, &hvalue);
+
+ pj_list_push_back(&hdr_list, h);
+
+ PJ_LOG(4,(THIS_FILE, "Header %s: %s added", name, val));
+ }
+ }
+ break;
+ default:
+ puts("Error: invalid argument");
+ usage();
+ return 1;
+ }
+ }
+
+ if (pj_optind != argc) {
+ code = atoi(argv[pj_optind]);
+ if (code < 200 || code > 699) {
+ puts("Error: invalid status code");
+ usage();
+ return 1;
+ }
+ }
+
+ PJ_LOG(4,(THIS_FILE, "Returning %d to incoming requests", code));
+
+
+ /*
+ * Add UDP transport, with hard-coded port
+ */
+#ifdef HAS_UDP_TRANSPORT
+ {
+ pj_sockaddr_in addr;
+
+ addr.sin_family = pj_AF_INET();
+ addr.sin_addr.s_addr = 0;
+ addr.sin_port = pj_htons(5060);
+
+ 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;
+ }
+ }
+#endif
+
+#if HAS_TCP_TRANSPORT
+ /*
+ * 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(5060);
+
+ status = pjsip_tcp_transport_start(sip_endpt, &addr, 1, NULL);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3,(THIS_FILE,
+ "Error starting TCP transport (port in use?)"));
+ return 1;
+ }
+ }
+#endif
+
+ /*
+ * Register our module to receive incoming requests.
+ */
+ status = pjsip_endpt_register_module( sip_endpt, &mod_app);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Done. Loop forever to handle incoming events. */
+ PJ_LOG(3,(THIS_FILE, "Press Ctrl-C to quit.."));
+
+ for (;;) {
+ pjsip_endpt_handle_events(sip_endpt, NULL);
+ }
+}
diff --git a/pjsip-apps/src/samples/stateful_proxy.c b/pjsip-apps/src/samples/stateful_proxy.c
new file mode 100644
index 0000000..e8e2c25
--- /dev/null
+++ b/pjsip-apps/src/samples/stateful_proxy.c
@@ -0,0 +1,587 @@
+/* $Id: stateful_proxy.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+#define THIS_FILE "stateful_proxy.c"
+
+/* Common proxy functions */
+#define STATEFUL 1
+#include "proxy.h"
+
+
+/*
+ * mod_stateful_proxy is the module to receive SIP request and
+ * response message that is outside any transaction context.
+ */
+static pj_bool_t proxy_on_rx_request(pjsip_rx_data *rdata );
+static pj_bool_t proxy_on_rx_response(pjsip_rx_data *rdata );
+
+static pjsip_module mod_stateful_proxy =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-stateful-proxy", 18 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_UA_PROXY_LAYER, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &proxy_on_rx_request, /* on_rx_request() */
+ &proxy_on_rx_response, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+
+/*
+ * mod_tu (tu=Transaction User) is the module to receive notification
+ * from transaction when the transaction state has changed.
+ */
+static void tu_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event);
+
+static pjsip_module mod_tu =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-transaction-user", 20 }, /* 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() */
+ &tu_on_tsx_state, /* on_tsx_state() */
+};
+
+
+/* This is the data that is attached to the UAC transaction */
+struct uac_data
+{
+ pjsip_transaction *uas_tsx;
+ pj_timer_entry timer;
+};
+
+
+/* This is the data that is attached to the UAS transaction */
+struct uas_data
+{
+ pjsip_transaction *uac_tsx;
+};
+
+
+
+static pj_status_t init_stateful_proxy(void)
+{
+ pj_status_t status;
+
+ status = pjsip_endpt_register_module( global.endpt, &mod_stateful_proxy);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ status = pjsip_endpt_register_module( global.endpt, &mod_tu);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ return PJ_SUCCESS;
+}
+
+
+/* Callback to be called to handle new incoming requests. */
+static pj_bool_t proxy_on_rx_request( pjsip_rx_data *rdata )
+{
+ pjsip_transaction *uas_tsx, *uac_tsx;
+ struct uac_data *uac_data;
+ struct uas_data *uas_data;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ if (rdata->msg_info.msg->line.req.method.id != PJSIP_CANCEL_METHOD) {
+
+ /* Verify incoming request */
+ status = proxy_verify_request(rdata);
+ if (status != PJ_SUCCESS) {
+ app_perror("RX invalid request", status);
+ return PJ_TRUE;
+ }
+
+ /*
+ * Request looks sane, next clone the request to create transmit data.
+ */
+ status = pjsip_endpt_create_request_fwd(global.endpt, rdata, NULL,
+ NULL, 0, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsip_endpt_respond_stateless(global.endpt, rdata,
+ PJSIP_SC_INTERNAL_SERVER_ERROR,
+ NULL, NULL, NULL);
+ return PJ_TRUE;
+ }
+
+
+ /* Process routing */
+ status = proxy_process_routing(tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error processing route", status);
+ return PJ_TRUE;
+ }
+
+ /* Calculate target */
+ status = proxy_calculate_target(rdata, tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error calculating target", status);
+ return PJ_TRUE;
+ }
+
+ /* Everything is set to forward the request. */
+
+ /* If this is an ACK request, forward statelessly.
+ * This happens if the proxy records route and this ACK
+ * is sent for 2xx response. An ACK that is sent for non-2xx
+ * final response will be absorbed by transaction layer, and
+ * it will not be received by on_rx_request() callback.
+ */
+ if (tdata->msg->line.req.method.id == PJSIP_ACK_METHOD) {
+ status = pjsip_endpt_send_request_stateless(global.endpt, tdata,
+ NULL, NULL);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error forwarding request", status);
+ return PJ_TRUE;
+ }
+
+ return PJ_TRUE;
+ }
+
+ /* Create UAC transaction for forwarding the request.
+ * Set our module as the transaction user to receive further
+ * events from this transaction.
+ */
+ status = pjsip_tsx_create_uac(&mod_tu, tdata, &uac_tsx);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ pjsip_endpt_respond_stateless(global.endpt, rdata,
+ PJSIP_SC_INTERNAL_SERVER_ERROR,
+ NULL, NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ /* Create UAS transaction to handle incoming request */
+ status = pjsip_tsx_create_uas(&mod_tu, rdata, &uas_tsx);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ pjsip_endpt_respond_stateless(global.endpt, rdata,
+ PJSIP_SC_INTERNAL_SERVER_ERROR,
+ NULL, NULL, NULL);
+ pjsip_tsx_terminate(uac_tsx, PJSIP_SC_INTERNAL_SERVER_ERROR);
+ return PJ_TRUE;
+ }
+
+ /* Feed the request to the UAS transaction to drive it's state
+ * out of NULL state.
+ */
+ pjsip_tsx_recv_msg(uas_tsx, rdata);
+
+ /* Attach a data to the UAC transaction, to be used to find the
+ * UAS transaction when we receive response in the UAC side.
+ */
+ uac_data = (struct uac_data*)
+ pj_pool_alloc(uac_tsx->pool, sizeof(struct uac_data));
+ uac_data->uas_tsx = uas_tsx;
+ uac_tsx->mod_data[mod_tu.id] = (void*)uac_data;
+
+ /* Attach data to the UAS transaction, to find the UAC transaction
+ * when cancelling INVITE request.
+ */
+ uas_data = (struct uas_data*)
+ pj_pool_alloc(uas_tsx->pool, sizeof(struct uas_data));
+ uas_data->uac_tsx = uac_tsx;
+ uas_tsx->mod_data[mod_tu.id] = (void*)uas_data;
+
+ /* Everything is setup, forward the request */
+ status = pjsip_tsx_send_msg(uac_tsx, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data *err_res;
+
+ /* Fail to send request, for some reason */
+
+ /* Destroy transmit data */
+ pjsip_tx_data_dec_ref(tdata);
+
+ /* I think UAC transaction should have been destroyed when
+ * it fails to send request, so no need to destroy it.
+ pjsip_tsx_terminate(uac_tsx, PJSIP_SC_INTERNAL_SERVER_ERROR);
+ */
+
+ /* Send 500/Internal Server Error to UAS transaction */
+ pjsip_endpt_create_response(global.endpt, rdata,
+ 500, NULL, &err_res);
+ pjsip_tsx_send_msg(uas_tsx, err_res);
+
+ return PJ_TRUE;
+ }
+
+ /* Send 100/Trying if this is an INVITE */
+ if (rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD) {
+ pjsip_tx_data *res100;
+
+ pjsip_endpt_create_response(global.endpt, rdata, 100, NULL,
+ &res100);
+ pjsip_tsx_send_msg(uas_tsx, res100);
+ }
+
+ } else {
+ /* This is CANCEL request */
+ pjsip_transaction *invite_uas;
+ struct uas_data *uas_data;
+ pj_str_t key;
+
+ /* Find the UAS INVITE transaction */
+ pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_UAS_ROLE,
+ pjsip_get_invite_method(), rdata);
+ invite_uas = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE);
+ if (!invite_uas) {
+ /* Invite transaction not found, respond CANCEL with 481 */
+ pjsip_endpt_respond_stateless(global.endpt, rdata, 481, NULL,
+ NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ /* Respond 200 OK to CANCEL */
+ pjsip_endpt_respond(global.endpt, NULL, rdata, 200, NULL, NULL,
+ NULL, NULL);
+
+ /* Send CANCEL to cancel the UAC transaction.
+ * The UAS INVITE transaction will get final response when
+ * we receive final response from the UAC INVITE transaction.
+ */
+ uas_data = (struct uas_data*) invite_uas->mod_data[mod_tu.id];
+ if (uas_data->uac_tsx && uas_data->uac_tsx->status_code < 200) {
+ pjsip_tx_data *cancel;
+
+ pj_mutex_lock(uas_data->uac_tsx->mutex);
+
+ pjsip_endpt_create_cancel(global.endpt, uas_data->uac_tsx->last_tx,
+ &cancel);
+ pjsip_endpt_send_request(global.endpt, cancel, -1, NULL, NULL);
+
+ pj_mutex_unlock(uas_data->uac_tsx->mutex);
+ }
+
+ /* Unlock UAS tsx because it is locked in find_tsx() */
+ pj_mutex_unlock(invite_uas->mutex);
+ }
+
+ return PJ_TRUE;
+}
+
+
+/* Callback to be called to handle incoming response outside
+ * any transactions. This happens for example when 2xx/OK
+ * for INVITE is received and transaction will be destroyed
+ * immediately, so we need to forward the subsequent 2xx/OK
+ * retransmission statelessly.
+ */
+static pj_bool_t proxy_on_rx_response( pjsip_rx_data *rdata )
+{
+ pjsip_tx_data *tdata;
+ pjsip_response_addr res_addr;
+ pjsip_via_hdr *hvia;
+ pj_status_t status;
+
+ /* Create response to be forwarded upstream (Via will be stripped here) */
+ status = pjsip_endpt_create_response_fwd(global.endpt, rdata, 0, &tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error creating response", status);
+ return PJ_TRUE;
+ }
+
+ /* Get topmost Via header */
+ hvia = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+ if (hvia == NULL) {
+ /* Invalid response! Just drop it */
+ pjsip_tx_data_dec_ref(tdata);
+ return PJ_TRUE;
+ }
+
+ /* Calculate the address to forward the response */
+ pj_bzero(&res_addr, sizeof(res_addr));
+ res_addr.dst_host.type = PJSIP_TRANSPORT_UDP;
+ res_addr.dst_host.flag =
+ pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_UDP);
+
+ /* Destination address is Via's received param */
+ res_addr.dst_host.addr.host = hvia->recvd_param;
+ if (res_addr.dst_host.addr.host.slen == 0) {
+ /* Someone has messed up our Via header! */
+ res_addr.dst_host.addr.host = hvia->sent_by.host;
+ }
+
+ /* Destination port is the rport */
+ if (hvia->rport_param != 0 && hvia->rport_param != -1)
+ res_addr.dst_host.addr.port = hvia->rport_param;
+
+ if (res_addr.dst_host.addr.port == 0) {
+ /* Ugh, original sender didn't put rport!
+ * At best, can only send the response to the port in Via.
+ */
+ res_addr.dst_host.addr.port = hvia->sent_by.port;
+ }
+
+ /* Forward response */
+ status = pjsip_endpt_send_response(global.endpt, &res_addr, tdata,
+ NULL, NULL);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error forwarding response", status);
+ return PJ_TRUE;
+ }
+
+ return PJ_TRUE;
+}
+
+
+/* Callback to be called to handle transaction state changed. */
+static void tu_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event)
+{
+ struct uac_data *uac_data;
+ pj_status_t status;
+
+ if (tsx->role == PJSIP_ROLE_UAS) {
+ if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+ struct uas_data *uas_data;
+
+ uas_data = (struct uas_data*) tsx->mod_data[mod_tu.id];
+ if (uas_data->uac_tsx) {
+ uac_data = (struct uac_data*)
+ uas_data->uac_tsx->mod_data[mod_tu.id];
+ uac_data->uas_tsx = NULL;
+ }
+
+ }
+ return;
+ }
+
+ /* Get the data that we attached to the UAC transaction previously */
+ uac_data = (struct uac_data*) tsx->mod_data[mod_tu.id];
+
+
+ /* Handle incoming response */
+ if (event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+
+ pjsip_rx_data *rdata;
+ pjsip_response_addr res_addr;
+ pjsip_via_hdr *hvia;
+ pjsip_tx_data *tdata;
+
+ rdata = event->body.tsx_state.src.rdata;
+
+ /* Do not forward 100 response for INVITE (we already responded
+ * INVITE with 100)
+ */
+ if (tsx->method.id == PJSIP_INVITE_METHOD &&
+ rdata->msg_info.msg->line.status.code == 100)
+ {
+ return;
+ }
+
+ /* Create response to be forwarded upstream
+ * (Via will be stripped here)
+ */
+ status = pjsip_endpt_create_response_fwd(global.endpt, rdata, 0,
+ &tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error creating response", status);
+ return;
+ }
+
+ /* Get topmost Via header of the new response */
+ hvia = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA,
+ NULL);
+ if (hvia == NULL) {
+ /* Invalid response! Just drop it */
+ pjsip_tx_data_dec_ref(tdata);
+ return;
+ }
+
+ /* Calculate the address to forward the response */
+ pj_bzero(&res_addr, sizeof(res_addr));
+ res_addr.dst_host.type = PJSIP_TRANSPORT_UDP;
+ res_addr.dst_host.flag =
+ pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_UDP);
+
+ /* Destination address is Via's received param */
+ res_addr.dst_host.addr.host = hvia->recvd_param;
+ if (res_addr.dst_host.addr.host.slen == 0) {
+ /* Someone has messed up our Via header! */
+ res_addr.dst_host.addr.host = hvia->sent_by.host;
+ }
+
+ /* Destination port is the rport */
+ if (hvia->rport_param != 0 && hvia->rport_param != -1)
+ res_addr.dst_host.addr.port = hvia->rport_param;
+
+ if (res_addr.dst_host.addr.port == 0) {
+ /* Ugh, original sender didn't put rport!
+ * At best, can only send the response to the port in Via.
+ */
+ res_addr.dst_host.addr.port = hvia->sent_by.port;
+ }
+
+ /* Forward response with the UAS transaction */
+ pjsip_tsx_send_msg(uac_data->uas_tsx, tdata);
+
+ }
+
+ /* If UAC transaction is terminated, terminate the UAS as well.
+ * This could happen because of:
+ * - timeout on the UAC side
+ * - receipt of 2xx response to INVITE
+ */
+ if (tsx->state == PJSIP_TSX_STATE_TERMINATED && uac_data &&
+ uac_data->uas_tsx)
+ {
+
+ pjsip_transaction *uas_tsx;
+ struct uas_data *uas_data;
+
+ uas_tsx = uac_data->uas_tsx;
+ uas_data = (struct uas_data*) uas_tsx->mod_data[mod_tu.id];
+ uas_data->uac_tsx = NULL;
+
+ if (event->body.tsx_state.type == PJSIP_EVENT_TIMER) {
+
+ /* Send 408/Timeout if this is an INVITE transaction, since
+ * we must have sent provisional response before. For non
+ * INVITE transaction, just destroy it.
+ */
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+
+ pjsip_tx_data *tdata = uas_tsx->last_tx;
+
+ tdata->msg->line.status.code = PJSIP_SC_REQUEST_TIMEOUT;
+ tdata->msg->line.status.reason = pj_str("Request timed out");
+ tdata->msg->body = NULL;
+
+ pjsip_tx_data_add_ref(tdata);
+ pjsip_tx_data_invalidate_msg(tdata);
+
+ pjsip_tsx_send_msg(uas_tsx, tdata);
+
+ } else {
+ /* For non-INVITE, just destroy the UAS transaction */
+ pjsip_tsx_terminate(uas_tsx, PJSIP_SC_REQUEST_TIMEOUT);
+ }
+
+ } else if (event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+
+ if (uas_tsx->state < PJSIP_TSX_STATE_TERMINATED) {
+ pjsip_msg *msg;
+ int code;
+
+ msg = event->body.tsx_state.src.rdata->msg_info.msg;
+ code = msg->line.status.code;
+
+ uac_data->uas_tsx = NULL;
+ pjsip_tsx_terminate(uas_tsx, code);
+ }
+ }
+ }
+}
+
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+ pj_status_t status;
+
+ global.port = 5060;
+ global.record_route = 0;
+
+ pj_log_set_level(4);
+
+ status = init_options(argc, argv);
+ if (status != PJ_SUCCESS)
+ return 1;
+
+ status = init_stack();
+ if (status != PJ_SUCCESS) {
+ app_perror("Error initializing stack", status);
+ return 1;
+ }
+
+ status = init_proxy();
+ if (status != PJ_SUCCESS) {
+ app_perror("Error initializing proxy", status);
+ return 1;
+ }
+
+ status = init_stateful_proxy();
+ if (status != PJ_SUCCESS) {
+ app_perror("Error initializing stateful proxy", status);
+ return 1;
+ }
+
+#if PJ_HAS_THREADS
+ status = pj_thread_create(global.pool, "sproxy", &worker_thread,
+ NULL, 0, 0, &global.thread);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error creating thread", status);
+ return 1;
+ }
+
+ while (!global.quit_flag) {
+ char line[10];
+
+ puts("\n"
+ "Menu:\n"
+ " q quit\n"
+ " d dump status\n"
+ " dd dump detailed status\n"
+ "");
+
+ if (fgets(line, sizeof(line), stdin) == NULL) {
+ puts("EOF while reading stdin, will quit now..");
+ global.quit_flag = PJ_TRUE;
+ break;
+ }
+
+ if (line[0] == 'q') {
+ global.quit_flag = PJ_TRUE;
+ } else if (line[0] == 'd') {
+ pj_bool_t detail = (line[1] == 'd');
+ pjsip_endpt_dump(global.endpt, detail);
+ pjsip_tsx_layer_dump(detail);
+ }
+ }
+
+ pj_thread_join(global.thread);
+
+#else
+ puts("\nPress Ctrl-C to quit\n");
+ for (;;) {
+ pj_time_val delay = {0, 0};
+ pjsip_endpt_handle_events(global.endpt, &delay);
+ }
+#endif
+
+ destroy_stack();
+
+ return 0;
+}
+
diff --git a/pjsip-apps/src/samples/stateless_proxy.c b/pjsip-apps/src/samples/stateless_proxy.c
new file mode 100644
index 0000000..826385c
--- /dev/null
+++ b/pjsip-apps/src/samples/stateless_proxy.c
@@ -0,0 +1,255 @@
+/* $Id: stateless_proxy.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+#define THIS_FILE "stateless_proxy.c"
+
+/* Common proxy functions */
+#define STATEFUL 0
+#include "proxy.h"
+
+
+/* Callback to be called to handle incoming requests. */
+static pj_bool_t on_rx_request( pjsip_rx_data *rdata );
+
+/* Callback to be called to handle incoming response. */
+static pj_bool_t on_rx_response( pjsip_rx_data *rdata );
+
+
+static pj_status_t init_stateless_proxy(void)
+{
+ static pjsip_module mod_stateless_proxy =
+ {
+ NULL, NULL, /* prev, next. */
+ { "mod-stateless-proxy", 19 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_UA_PROXY_LAYER, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &on_rx_request, /* on_rx_request() */
+ &on_rx_response, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+ };
+
+ pj_status_t status;
+
+ /* Register our module to receive incoming requests. */
+ status = pjsip_endpt_register_module( global.endpt, &mod_stateless_proxy);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ return PJ_SUCCESS;
+}
+
+
+/* Callback to be called to handle incoming requests. */
+static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+
+ /* Verify incoming request */
+ status = proxy_verify_request(rdata);
+ if (status != PJ_SUCCESS) {
+ app_perror("RX invalid request", status);
+ return PJ_TRUE;
+ }
+
+ /*
+ * Request looks sane, next clone the request to create transmit data.
+ */
+ status = pjsip_endpt_create_request_fwd(global.endpt, rdata, NULL,
+ NULL, 0, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsip_endpt_respond_stateless(global.endpt, rdata,
+ PJSIP_SC_INTERNAL_SERVER_ERROR, NULL,
+ NULL, NULL);
+ return PJ_TRUE;
+ }
+
+
+ /* Process routing */
+ status = proxy_process_routing(tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error processing route", status);
+ return PJ_TRUE;
+ }
+
+ /* Calculate target */
+ status = proxy_calculate_target(rdata, tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error calculating target", status);
+ return PJ_TRUE;
+ }
+
+ /* Target is set, forward the request */
+ status = pjsip_endpt_send_request_stateless(global.endpt, tdata,
+ NULL, NULL);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error forwarding request", status);
+ return PJ_TRUE;
+ }
+
+ return PJ_TRUE;
+}
+
+
+/* Callback to be called to handle incoming response. */
+static pj_bool_t on_rx_response( pjsip_rx_data *rdata )
+{
+ pjsip_tx_data *tdata;
+ pjsip_response_addr res_addr;
+ pjsip_via_hdr *hvia;
+ pj_status_t status;
+
+ /* Create response to be forwarded upstream (Via will be stripped here) */
+ status = pjsip_endpt_create_response_fwd(global.endpt, rdata, 0, &tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error creating response", status);
+ return PJ_TRUE;
+ }
+
+ /* Get topmost Via header */
+ hvia = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+ if (hvia == NULL) {
+ /* Invalid response! Just drop it */
+ pjsip_tx_data_dec_ref(tdata);
+ return PJ_TRUE;
+ }
+
+ /* Calculate the address to forward the response */
+ pj_bzero(&res_addr, sizeof(res_addr));
+ res_addr.dst_host.type = PJSIP_TRANSPORT_UDP;
+ res_addr.dst_host.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_UDP);
+
+ /* Destination address is Via's received param */
+ res_addr.dst_host.addr.host = hvia->recvd_param;
+ if (res_addr.dst_host.addr.host.slen == 0) {
+ /* Someone has messed up our Via header! */
+ res_addr.dst_host.addr.host = hvia->sent_by.host;
+ }
+
+ /* Destination port is the rpot */
+ if (hvia->rport_param != 0 && hvia->rport_param != -1)
+ res_addr.dst_host.addr.port = hvia->rport_param;
+
+ if (res_addr.dst_host.addr.port == 0) {
+ /* Ugh, original sender didn't put rport!
+ * At best, can only send the response to the port in Via.
+ */
+ res_addr.dst_host.addr.port = hvia->sent_by.port;
+ }
+
+ /* Forward response */
+ status = pjsip_endpt_send_response(global.endpt, &res_addr, tdata,
+ NULL, NULL);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error forwarding response", status);
+ return PJ_TRUE;
+ }
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+ pj_status_t status;
+
+ global.port = 5060;
+ pj_log_set_level(4);
+
+ status = init_options(argc, argv);
+ if (status != PJ_SUCCESS)
+ return 1;
+
+ status = init_stack();
+ if (status != PJ_SUCCESS) {
+ app_perror("Error initializing stack", status);
+ return 1;
+ }
+
+ status = init_proxy();
+ if (status != PJ_SUCCESS) {
+ app_perror("Error initializing proxy", status);
+ return 1;
+ }
+
+ status = init_stateless_proxy();
+ if (status != PJ_SUCCESS) {
+ app_perror("Error initializing stateless proxy", status);
+ return 1;
+ }
+
+#if PJ_HAS_THREADS
+ status = pj_thread_create(global.pool, "sproxy", &worker_thread,
+ NULL, 0, 0, &global.thread);
+ if (status != PJ_SUCCESS) {
+ app_perror("Error creating thread", status);
+ return 1;
+ }
+
+ while (!global.quit_flag) {
+ char line[10];
+
+ puts("\n"
+ "Menu:\n"
+ " q quit\n"
+ " d dump status\n"
+ " dd dump detailed status\n"
+ "");
+
+ if (fgets(line, sizeof(line), stdin) == NULL) {
+ puts("EOF while reading stdin, will quit now..");
+ global.quit_flag = PJ_TRUE;
+ break;
+ }
+
+ if (line[0] == 'q') {
+ global.quit_flag = PJ_TRUE;
+ } else if (line[0] == 'd') {
+ pj_bool_t detail = (line[1] == 'd');
+ pjsip_endpt_dump(global.endpt, detail);
+#if STATEFUL
+ pjsip_tsx_layer_dump(detail);
+#endif
+ }
+ }
+
+ pj_thread_join(global.thread);
+
+#else
+ puts("\nPress Ctrl-C to quit\n");
+ for (;;) {
+ pj_time_val delay = {0, 0};
+ pjsip_endpt_handle_events(global.endpt, &delay);
+ }
+#endif
+
+ destroy_stack();
+
+ return 0;
+}
+
diff --git a/pjsip-apps/src/samples/stereotest.c b/pjsip-apps/src/samples/stereotest.c
new file mode 100644
index 0000000..8931270
--- /dev/null
+++ b/pjsip-apps/src/samples/stereotest.c
@@ -0,0 +1,336 @@
+/* $Id: stereotest.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+/**
+ * \page page_pjmedia_samples_stereo_c Samples: Using Stereo Port
+ *
+ * This example demonstrates how to use @ref PJMEDIA_STEREO_PORT to
+ * change the channel count of the media streams.
+ *
+ * This file is pjsip-apps/src/samples/stereotest.c
+ *
+ * \includelineno stereotest.c
+ */
+
+#include <pjmedia.h>
+#include <pjlib-util.h>
+#include <pjlib.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "util.h"
+
+#define REC_CLOCK_RATE 16000
+#define PTIME 20
+
+#define MODE_PLAY 1
+#define MODE_RECORD 2
+
+
+/* For logging purpose. */
+#define THIS_FILE "stereotest.c"
+
+
+static const char *desc =
+" FILE \n"
+" \n"
+" stereotest.c \n"
+" \n"
+" PURPOSE \n"
+" \n"
+" Demonstrate how use stereo port to play a WAV file to sound \n"
+" device or record to a WAV file from sound device with different \n"
+" channel count. \n"
+" \n"
+" USAGE \n"
+" \n"
+" stereotest [options] WAV \n"
+" \n"
+" Options: \n"
+" -m, --mode=N Operation mode: 1 = playing, 2 = recording.\n"
+" -C, --rec-ch-cnt=N Number of channel for recording file. \n"
+" -c, --snd-ch-cnt=N Number of channel for opening sound device.\n"
+" \n";
+
+int main(int argc, char *argv[])
+{
+ pj_caching_pool cp;
+ pjmedia_endpt *med_endpt;
+ pj_pool_t *pool;
+
+ pjmedia_port *file_port = NULL;
+ pjmedia_port *stereo_port = NULL;
+ pjmedia_snd_port *snd_port = NULL;
+
+ int dev_id = -1;
+ char tmp[10];
+ pj_status_t status;
+
+ char *wav_file = NULL;
+ unsigned mode = 0;
+ unsigned rec_ch_cnt = 1;
+ unsigned snd_ch_cnt = 2;
+
+ enum {
+ OPT_MODE = 'm',
+ OPT_REC_CHANNEL = 'C',
+ OPT_SND_CHANNEL = 'c',
+ };
+
+ struct pj_getopt_option long_options[] = {
+ { "mode", 1, 0, OPT_MODE },
+ { "rec-ch-cnt", 1, 0, OPT_REC_CHANNEL },
+ { "snd-ch-cnt", 1, 0, OPT_SND_CHANNEL },
+ { NULL, 0, 0, 0 },
+ };
+
+ int c;
+ int option_index;
+
+ /* Must init PJLIB first: */
+ status = pj_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Parse arguments */
+ pj_optind = 0;
+ while((c=pj_getopt_long(argc,argv, "m:C:c:", long_options, &option_index))!=-1) {
+
+ switch (c) {
+ case OPT_MODE:
+ if (mode) {
+ app_perror(THIS_FILE, "Cannot record and play at once!",
+ PJ_EINVAL);
+ return 1;
+ }
+ mode = atoi(pj_optarg);
+ break;
+
+ case OPT_REC_CHANNEL:
+ rec_ch_cnt = atoi(pj_optarg);
+ break;
+
+ case OPT_SND_CHANNEL:
+ snd_ch_cnt = atoi(pj_optarg);
+ break;
+
+ default:
+ printf("Invalid options %s\n", argv[pj_optind]);
+ puts(desc);
+ return 1;
+ }
+
+ }
+
+ wav_file = argv[pj_optind];
+
+ /* Verify arguments. */
+ if (!wav_file) {
+ app_perror(THIS_FILE, "WAV file not specified!", PJ_EINVAL);
+ puts(desc);
+ return 1;
+ }
+ if (!snd_ch_cnt || !rec_ch_cnt || rec_ch_cnt > 6) {
+ app_perror(THIS_FILE, "Invalid or too many channel count!", PJ_EINVAL);
+ puts(desc);
+ return 1;
+ }
+ if (mode != MODE_RECORD && mode != MODE_PLAY) {
+ app_perror(THIS_FILE, "Invalid operation mode!", PJ_EINVAL);
+ puts(desc);
+ return 1;
+ }
+
+ /* Must create a pool factory before we can allocate any memory. */
+ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+
+ /*
+ * Initialize media endpoint.
+ * This will implicitly initialize PJMEDIA too.
+ */
+ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Create memory pool for our file player */
+ pool = pj_pool_create( &cp.factory, /* pool factory */
+ "app", /* pool name. */
+ 4000, /* init size */
+ 4000, /* increment size */
+ NULL /* callback on error */
+ );
+
+ if (mode == MODE_PLAY) {
+ /* Create WAVE file player port. */
+ status = pjmedia_wav_player_port_create( pool, wav_file, PTIME, 0,
+ 0, &file_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to open file", status);
+ return 1;
+ }
+
+ /* Create sound player port. */
+ status = pjmedia_snd_port_create_player(
+ pool, /* pool */
+ dev_id, /* device id. */
+ PJMEDIA_PIA_SRATE(&file_port->info),/* clock rate. */
+ snd_ch_cnt, /* # of channels. */
+ snd_ch_cnt * PTIME * /* samples per frame. */
+ PJMEDIA_PIA_SRATE(&file_port->info) / 1000,
+ PJMEDIA_PIA_BITS(&file_port->info),/* bits per sample. */
+ 0, /* options */
+ &snd_port /* returned port */
+ );
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to open sound device", status);
+ return 1;
+ }
+
+ if (snd_ch_cnt != PJMEDIA_PIA_CCNT(&file_port->info)) {
+ status = pjmedia_stereo_port_create( pool,
+ file_port,
+ snd_ch_cnt,
+ 0,
+ &stereo_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create stereo port", status);
+ return 1;
+ }
+
+ status = pjmedia_snd_port_connect(snd_port, stereo_port);
+ } else {
+ status = pjmedia_snd_port_connect(snd_port, file_port);
+ }
+
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to connect sound port", status);
+ return 1;
+ }
+
+ } else {
+ /* Create WAVE file writer port. */
+ status = pjmedia_wav_writer_port_create(pool, wav_file,
+ REC_CLOCK_RATE,
+ rec_ch_cnt,
+ rec_ch_cnt * PTIME *
+ REC_CLOCK_RATE / 1000,
+ NBITS,
+ 0, 0,
+ &file_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to open file", status);
+ return 1;
+ }
+
+ /* Create sound player port. */
+ status = pjmedia_snd_port_create_rec(
+ pool, /* pool */
+ dev_id, /* device id. */
+ REC_CLOCK_RATE, /* clock rate. */
+ snd_ch_cnt, /* # of channels. */
+ snd_ch_cnt * PTIME *
+ REC_CLOCK_RATE / 1000, /* samples per frame. */
+ NBITS, /* bits per sample. */
+ 0, /* options */
+ &snd_port /* returned port */
+ );
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to open sound device", status);
+ return 1;
+ }
+
+ if (rec_ch_cnt != snd_ch_cnt) {
+ status = pjmedia_stereo_port_create( pool,
+ file_port,
+ snd_ch_cnt,
+ 0,
+ &stereo_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create stereo port", status);
+ return 1;
+ }
+
+ status = pjmedia_snd_port_connect(snd_port, stereo_port);
+ } else {
+ status = pjmedia_snd_port_connect(snd_port, file_port);
+ }
+
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to connect sound port", status);
+ return 1;
+ }
+ }
+
+ /* Dump memory usage */
+ dump_pool_usage(THIS_FILE, &cp);
+
+ /*
+ * File should be playing and looping now, using sound device's thread.
+ */
+
+
+ /* Sleep to allow log messages to flush */
+ pj_thread_sleep(100);
+
+ printf("Mode = %s\n", (mode == MODE_PLAY? "playing" : "recording") );
+ printf("File port channel count = %d\n", PJMEDIA_PIA_CCNT(&file_port->info));
+ printf("Sound port channel count = %d\n",
+ PJMEDIA_PIA_CCNT(&pjmedia_snd_port_get_port(snd_port)->info));
+ puts("");
+ puts("Press <ENTER> to stop and quit");
+
+ if (fgets(tmp, sizeof(tmp), stdin) == NULL) {
+ puts("EOF while reading stdin, will quit now..");
+ }
+
+ /* Start deinitialization: */
+
+
+ /* Destroy sound device */
+ status = pjmedia_snd_port_destroy( snd_port );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Destroy stereo port and file_port.
+ * Stereo port will destroy all downstream ports (e.g. the file port)
+ */
+ status = pjmedia_port_destroy( stereo_port? stereo_port : file_port);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Release application pool */
+ pj_pool_release( pool );
+
+ /* Destroy media endpoint. */
+ pjmedia_endpt_destroy( med_endpt );
+
+ /* Destroy pool factory */
+ pj_caching_pool_destroy( &cp );
+
+ /* Shutdown PJLIB */
+ pj_shutdown();
+
+
+ /* Done. */
+ return 0;
+
+}
+
+
+
diff --git a/pjsip-apps/src/samples/streamutil.c b/pjsip-apps/src/samples/streamutil.c
new file mode 100644
index 0000000..a59621b
--- /dev/null
+++ b/pjsip-apps/src/samples/streamutil.c
@@ -0,0 +1,1174 @@
+/* $Id: streamutil.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+
+/**
+ * \page page_pjmedia_samples_streamutil_c Samples: Remote Streaming
+ *
+ * This example mainly demonstrates how to stream media file to remote
+ * peer using RTP.
+ *
+ * This file is pjsip-apps/src/samples/streamutil.c
+ *
+ * \includelineno streamutil.c
+ */
+
+#include <pjlib.h>
+#include <pjlib-util.h>
+#include <pjmedia.h>
+#include <pjmedia-codec.h>
+#include <pjmedia/transport_srtp.h>
+
+#include <stdlib.h> /* atoi() */
+#include <stdio.h>
+
+#include "util.h"
+
+
+static const char *desc =
+ " streamutil \n"
+ " \n"
+ " PURPOSE: \n"
+ " Demonstrate how to use pjmedia stream component to transmit/receive \n"
+ " RTP packets to/from sound device. \n"
+ "\n"
+ "\n"
+ " USAGE: \n"
+ " streamutil [options] \n"
+ "\n"
+ "\n"
+ " Options:\n"
+ " --codec=CODEC Set the codec name. \n"
+ " --local-port=PORT Set local RTP port (default=4000) \n"
+ " --remote=IP:PORT Set the remote peer. If this option is set, \n"
+ " the program will transmit RTP audio to the \n"
+ " specified address. (default: recv only) \n"
+ " --play-file=WAV Send audio from the WAV file instead of from \n"
+ " the sound device. \n"
+ " --record-file=WAV Record incoming audio to WAV file instead of \n"
+ " playing it to sound device. \n"
+ " --send-recv Set stream direction to bidirectional. \n"
+ " --send-only Set stream direction to send only \n"
+ " --recv-only Set stream direction to recv only (default) \n"
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ " --use-srtp[=NAME] Enable SRTP with crypto suite NAME \n"
+ " e.g: AES_CM_128_HMAC_SHA1_80 (default), \n"
+ " AES_CM_128_HMAC_SHA1_32 \n"
+ " Use this option along with the TX & RX keys, \n"
+ " formated of 60 hex digits (e.g: E148DA..) \n"
+ " --srtp-tx-key SRTP key for transmiting \n"
+ " --srtp-rx-key SRTP key for receiving \n"
+#endif
+
+ "\n"
+;
+
+
+
+
+#define THIS_FILE "stream.c"
+
+
+
+/* Prototype */
+static void print_stream_stat(pjmedia_stream *stream,
+ const pjmedia_codec_param *codec_param);
+
+/* Prototype for LIBSRTP utility in file datatypes.c */
+int hex_string_to_octet_string(char *raw, char *hex, int len);
+
+/*
+ * Register all codecs.
+ */
+static pj_status_t init_codecs(pjmedia_endpt *med_endpt)
+{
+ return pjmedia_codec_register_audio_codecs(med_endpt, NULL);
+}
+
+
+/*
+ * Create stream based on the codec, dir, remote address, etc.
+ */
+static pj_status_t create_stream( pj_pool_t *pool,
+ pjmedia_endpt *med_endpt,
+ const pjmedia_codec_info *codec_info,
+ pjmedia_dir dir,
+ pj_uint16_t local_port,
+ const pj_sockaddr_in *rem_addr,
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ pj_bool_t use_srtp,
+ const pj_str_t *crypto_suite,
+ const pj_str_t *srtp_tx_key,
+ const pj_str_t *srtp_rx_key,
+#endif
+ pjmedia_stream **p_stream )
+{
+ pjmedia_stream_info info;
+ pjmedia_transport *transport = NULL;
+ pj_status_t status;
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ pjmedia_transport *srtp_tp = NULL;
+#endif
+
+
+ /* Reset stream info. */
+ pj_bzero(&info, sizeof(info));
+
+
+ /* Initialize stream info formats */
+ info.type = PJMEDIA_TYPE_AUDIO;
+ info.dir = dir;
+ pj_memcpy(&info.fmt, codec_info, sizeof(pjmedia_codec_info));
+ info.tx_pt = codec_info->pt;
+ info.ssrc = pj_rand();
+
+#if PJMEDIA_HAS_RTCP_XR && PJMEDIA_STREAM_ENABLE_XR
+ /* Set default RTCP XR enabled/disabled */
+ info.rtcp_xr_enabled = PJ_TRUE;
+#endif
+
+ /* Copy remote address */
+ pj_memcpy(&info.rem_addr, rem_addr, sizeof(pj_sockaddr_in));
+
+ /* If remote address is not set, set to an arbitrary address
+ * (otherwise stream will assert).
+ */
+ if (info.rem_addr.addr.sa_family == 0) {
+ const pj_str_t addr = pj_str("127.0.0.1");
+ pj_sockaddr_in_init(&info.rem_addr.ipv4, &addr, 0);
+ }
+
+ /* Create media transport */
+ status = pjmedia_transport_udp_create(med_endpt, NULL, local_port,
+ 0, &transport);
+ if (status != PJ_SUCCESS)
+ return status;
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ /* Check if SRTP enabled */
+ if (use_srtp) {
+ pjmedia_srtp_crypto tx_plc, rx_plc;
+
+ status = pjmedia_transport_srtp_create(med_endpt, transport,
+ NULL, &srtp_tp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_bzero(&tx_plc, sizeof(pjmedia_srtp_crypto));
+ pj_bzero(&rx_plc, sizeof(pjmedia_srtp_crypto));
+
+ tx_plc.key = *srtp_tx_key;
+ tx_plc.name = *crypto_suite;
+ rx_plc.key = *srtp_rx_key;
+ rx_plc.name = *crypto_suite;
+
+ status = pjmedia_transport_srtp_start(srtp_tp, &tx_plc, &rx_plc);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ transport = srtp_tp;
+ }
+#endif
+
+ /* Now that the stream info is initialized, we can create the
+ * stream.
+ */
+
+ status = pjmedia_stream_create( med_endpt, pool, &info,
+ transport,
+ NULL, p_stream);
+
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error creating stream", status);
+ pjmedia_transport_close(transport);
+ return status;
+ }
+
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * usage()
+ */
+static void usage()
+{
+ puts(desc);
+}
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+ pj_caching_pool cp;
+ pjmedia_endpt *med_endpt;
+ pj_pool_t *pool;
+ pjmedia_port *rec_file_port = NULL, *play_file_port = NULL;
+ pjmedia_master_port *master_port = NULL;
+ pjmedia_snd_port *snd_port = NULL;
+ pjmedia_stream *stream = NULL;
+ pjmedia_port *stream_port;
+ char tmp[10];
+ pj_status_t status;
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ /* SRTP variables */
+ pj_bool_t use_srtp = PJ_FALSE;
+ char tmp_tx_key[64];
+ char tmp_rx_key[64];
+ pj_str_t srtp_tx_key = {NULL, 0};
+ pj_str_t srtp_rx_key = {NULL, 0};
+ pj_str_t srtp_crypto_suite = {NULL, 0};
+ int tmp_key_len;
+#endif
+
+ /* Default values */
+ const pjmedia_codec_info *codec_info;
+ pjmedia_codec_param codec_param;
+ pjmedia_dir dir = PJMEDIA_DIR_DECODING;
+ pj_sockaddr_in remote_addr;
+ pj_uint16_t local_port = 4000;
+ char *codec_id = NULL;
+ char *rec_file = NULL;
+ char *play_file = NULL;
+
+ enum {
+ OPT_CODEC = 'c',
+ OPT_LOCAL_PORT = 'p',
+ OPT_REMOTE = 'r',
+ OPT_PLAY_FILE = 'w',
+ OPT_RECORD_FILE = 'R',
+ OPT_SEND_RECV = 'b',
+ OPT_SEND_ONLY = 's',
+ OPT_RECV_ONLY = 'i',
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ OPT_USE_SRTP = 'S',
+#endif
+ OPT_SRTP_TX_KEY = 'x',
+ OPT_SRTP_RX_KEY = 'y',
+ OPT_HELP = 'h',
+ };
+
+ struct pj_getopt_option long_options[] = {
+ { "codec", 1, 0, OPT_CODEC },
+ { "local-port", 1, 0, OPT_LOCAL_PORT },
+ { "remote", 1, 0, OPT_REMOTE },
+ { "play-file", 1, 0, OPT_PLAY_FILE },
+ { "record-file", 1, 0, OPT_RECORD_FILE },
+ { "send-recv", 0, 0, OPT_SEND_RECV },
+ { "send-only", 0, 0, OPT_SEND_ONLY },
+ { "recv-only", 0, 0, OPT_RECV_ONLY },
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ { "use-srtp", 2, 0, OPT_USE_SRTP },
+ { "srtp-tx-key", 1, 0, OPT_SRTP_TX_KEY },
+ { "srtp-rx-key", 1, 0, OPT_SRTP_RX_KEY },
+#endif
+ { "help", 0, 0, OPT_HELP },
+ { NULL, 0, 0, 0 },
+ };
+
+ int c;
+ int option_index;
+
+
+ pj_bzero(&remote_addr, sizeof(remote_addr));
+
+
+ /* init PJLIB : */
+ status = pj_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Parse arguments */
+ pj_optind = 0;
+ while((c=pj_getopt_long(argc,argv, "h", long_options, &option_index))!=-1) {
+
+ switch (c) {
+ case OPT_CODEC:
+ codec_id = pj_optarg;
+ break;
+
+ case OPT_LOCAL_PORT:
+ local_port = (pj_uint16_t) atoi(pj_optarg);
+ if (local_port < 1) {
+ printf("Error: invalid local port %s\n", pj_optarg);
+ return 1;
+ }
+ break;
+
+ case OPT_REMOTE:
+ {
+ pj_str_t ip = pj_str(strtok(pj_optarg, ":"));
+ pj_uint16_t port = (pj_uint16_t) atoi(strtok(NULL, ":"));
+
+ status = pj_sockaddr_in_init(&remote_addr, &ip, port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Invalid remote address", status);
+ return 1;
+ }
+ }
+ break;
+
+ case OPT_PLAY_FILE:
+ play_file = pj_optarg;
+ break;
+
+ case OPT_RECORD_FILE:
+ rec_file = pj_optarg;
+ break;
+
+ case OPT_SEND_RECV:
+ dir = PJMEDIA_DIR_ENCODING_DECODING;
+ break;
+
+ case OPT_SEND_ONLY:
+ dir = PJMEDIA_DIR_ENCODING;
+ break;
+
+ case OPT_RECV_ONLY:
+ dir = PJMEDIA_DIR_DECODING;
+ break;
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ case OPT_USE_SRTP:
+ use_srtp = PJ_TRUE;
+ if (pj_optarg) {
+ pj_strset(&srtp_crypto_suite, pj_optarg, strlen(pj_optarg));
+ } else {
+ srtp_crypto_suite = pj_str("AES_CM_128_HMAC_SHA1_80");
+ }
+ break;
+
+ case OPT_SRTP_TX_KEY:
+ tmp_key_len = hex_string_to_octet_string(tmp_tx_key, pj_optarg, strlen(pj_optarg));
+ pj_strset(&srtp_tx_key, tmp_tx_key, tmp_key_len/2);
+ break;
+
+ case OPT_SRTP_RX_KEY:
+ tmp_key_len = hex_string_to_octet_string(tmp_rx_key, pj_optarg, strlen(pj_optarg));
+ pj_strset(&srtp_rx_key, tmp_rx_key, tmp_key_len/2);
+ break;
+#endif
+
+ case OPT_HELP:
+ usage();
+ return 1;
+
+ default:
+ printf("Invalid options %s\n", argv[pj_optind]);
+ return 1;
+ }
+
+ }
+
+
+ /* Verify arguments. */
+ if (dir & PJMEDIA_DIR_ENCODING) {
+ if (remote_addr.sin_addr.s_addr == 0) {
+ printf("Error: remote address must be set\n");
+ return 1;
+ }
+ }
+
+ if (play_file != NULL && dir != PJMEDIA_DIR_ENCODING) {
+ printf("Direction is set to --send-only because of --play-file\n");
+ dir = PJMEDIA_DIR_ENCODING;
+ }
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ /* SRTP validation */
+ if (use_srtp) {
+ if (!srtp_tx_key.slen || !srtp_rx_key.slen)
+ {
+ printf("Error: Key for each SRTP stream direction must be set\n");
+ return 1;
+ }
+ }
+#endif
+
+ /* Must create a pool factory before we can allocate any memory. */
+ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+
+ /*
+ * Initialize media endpoint.
+ * This will implicitly initialize PJMEDIA too.
+ */
+ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Create memory pool for application purpose */
+ pool = pj_pool_create( &cp.factory, /* pool factory */
+ "app", /* pool name. */
+ 4000, /* init size */
+ 4000, /* increment size */
+ NULL /* callback on error */
+ );
+
+
+ /* Register all supported codecs */
+ status = init_codecs(med_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Find which codec to use. */
+ if (codec_id) {
+ unsigned count = 1;
+ pj_str_t str_codec_id = pj_str(codec_id);
+ pjmedia_codec_mgr *codec_mgr = pjmedia_endpt_get_codec_mgr(med_endpt);
+ status = pjmedia_codec_mgr_find_codecs_by_id( codec_mgr,
+ &str_codec_id, &count,
+ &codec_info, NULL);
+ if (status != PJ_SUCCESS) {
+ printf("Error: unable to find codec %s\n", codec_id);
+ return 1;
+ }
+ } else {
+ /* Default to pcmu */
+ pjmedia_codec_mgr_get_codec_info( pjmedia_endpt_get_codec_mgr(med_endpt),
+ 0, &codec_info);
+ }
+
+ /* Create stream based on program arguments */
+ status = create_stream(pool, med_endpt, codec_info, dir, local_port,
+ &remote_addr,
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ use_srtp, &srtp_crypto_suite,
+ &srtp_tx_key, &srtp_rx_key,
+#endif
+ &stream);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+
+ /* Get codec default param for info */
+ status = pjmedia_codec_mgr_get_default_param(
+ pjmedia_endpt_get_codec_mgr(med_endpt),
+ codec_info,
+ &codec_param);
+ /* Should be ok, as create_stream() above succeeded */
+ pj_assert(status == PJ_SUCCESS);
+
+ /* Get the port interface of the stream */
+ status = pjmedia_stream_get_port( stream, &stream_port);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ if (play_file) {
+ unsigned wav_ptime;
+
+ wav_ptime = PJMEDIA_PIA_PTIME(&stream_port->info);
+ status = pjmedia_wav_player_port_create(pool, play_file, wav_ptime,
+ 0, -1, &play_file_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to use file", status);
+ goto on_exit;
+ }
+
+ status = pjmedia_master_port_create(pool, play_file_port, stream_port,
+ 0, &master_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create master port", status);
+ goto on_exit;
+ }
+
+ status = pjmedia_master_port_start(master_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error starting master port", status);
+ goto on_exit;
+ }
+
+ printf("Playing from WAV file %s..\n", play_file);
+
+ } else if (rec_file) {
+
+ status = pjmedia_wav_writer_port_create(pool, rec_file,
+ PJMEDIA_PIA_SRATE(&stream_port->info),
+ PJMEDIA_PIA_CCNT(&stream_port->info),
+ PJMEDIA_PIA_SPF(&stream_port->info),
+ PJMEDIA_PIA_BITS(&stream_port->info),
+ 0, 0, &rec_file_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to use file", status);
+ goto on_exit;
+ }
+
+ status = pjmedia_master_port_create(pool, stream_port, rec_file_port,
+ 0, &master_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create master port", status);
+ goto on_exit;
+ }
+
+ status = pjmedia_master_port_start(master_port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error starting master port", status);
+ goto on_exit;
+ }
+
+ printf("Recording to WAV file %s..\n", rec_file);
+
+ } else {
+
+ /* Create sound device port. */
+ if (dir == PJMEDIA_DIR_ENCODING_DECODING)
+ status = pjmedia_snd_port_create(pool, -1, -1,
+ PJMEDIA_PIA_SRATE(&stream_port->info),
+ PJMEDIA_PIA_CCNT(&stream_port->info),
+ PJMEDIA_PIA_SPF(&stream_port->info),
+ PJMEDIA_PIA_BITS(&stream_port->info),
+ 0, &snd_port);
+ else if (dir == PJMEDIA_DIR_ENCODING)
+ status = pjmedia_snd_port_create_rec(pool, -1,
+ PJMEDIA_PIA_SRATE(&stream_port->info),
+ PJMEDIA_PIA_CCNT(&stream_port->info),
+ PJMEDIA_PIA_SPF(&stream_port->info),
+ PJMEDIA_PIA_BITS(&stream_port->info),
+ 0, &snd_port);
+ else
+ status = pjmedia_snd_port_create_player(pool, -1,
+ PJMEDIA_PIA_SRATE(&stream_port->info),
+ PJMEDIA_PIA_CCNT(&stream_port->info),
+ PJMEDIA_PIA_SPF(&stream_port->info),
+ PJMEDIA_PIA_BITS(&stream_port->info),
+ 0, &snd_port);
+
+
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Unable to create sound port", status);
+ goto on_exit;
+ }
+
+ /* Connect sound port to stream */
+ status = pjmedia_snd_port_connect( snd_port, stream_port );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ }
+
+ /* Start streaming */
+ pjmedia_stream_start(stream);
+
+
+ /* Done */
+
+ if (dir == PJMEDIA_DIR_DECODING)
+ printf("Stream is active, dir is recv-only, local port is %d\n",
+ local_port);
+ else if (dir == PJMEDIA_DIR_ENCODING)
+ printf("Stream is active, dir is send-only, sending to %s:%d\n",
+ pj_inet_ntoa(remote_addr.sin_addr),
+ pj_ntohs(remote_addr.sin_port));
+ else
+ printf("Stream is active, send/recv, local port is %d, "
+ "sending to %s:%d\n",
+ local_port,
+ pj_inet_ntoa(remote_addr.sin_addr),
+ pj_ntohs(remote_addr.sin_port));
+
+
+ for (;;) {
+
+ puts("");
+ puts("Commands:");
+ puts(" s Display media statistics");
+ puts(" q Quit");
+ puts("");
+
+ printf("Command: "); fflush(stdout);
+
+ if (fgets(tmp, sizeof(tmp), stdin) == NULL) {
+ puts("EOF while reading stdin, will quit now..");
+ break;
+ }
+
+ if (tmp[0] == 's')
+ print_stream_stat(stream, &codec_param);
+ else if (tmp[0] == 'q')
+ break;
+
+ }
+
+
+
+ /* Start deinitialization: */
+on_exit:
+
+ /* Destroy sound device */
+ if (snd_port) {
+ pjmedia_snd_port_destroy( snd_port );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+ }
+
+ /* If there is master port, then we just need to destroy master port
+ * (it will recursively destroy upstream and downstream ports, which
+ * in this case are file_port and stream_port).
+ */
+ if (master_port) {
+ pjmedia_master_port_destroy(master_port, PJ_TRUE);
+ play_file_port = NULL;
+ stream = NULL;
+ }
+
+ /* Destroy stream */
+ if (stream) {
+ pjmedia_transport *tp;
+
+ tp = pjmedia_stream_get_transport(stream);
+ pjmedia_stream_destroy(stream);
+
+ pjmedia_transport_close(tp);
+ }
+
+ /* Destroy file ports */
+ if (play_file_port)
+ pjmedia_port_destroy( play_file_port );
+ if (rec_file_port)
+ pjmedia_port_destroy( rec_file_port );
+
+
+ /* Release application pool */
+ pj_pool_release( pool );
+
+ /* Destroy media endpoint. */
+ pjmedia_endpt_destroy( med_endpt );
+
+ /* Destroy pool factory */
+ pj_caching_pool_destroy( &cp );
+
+ /* Shutdown PJLIB */
+ pj_shutdown();
+
+
+ return (status == PJ_SUCCESS) ? 0 : 1;
+}
+
+
+
+
+static const char *good_number(char *buf, pj_int32_t val)
+{
+ if (val < 1000) {
+ pj_ansi_sprintf(buf, "%d", val);
+ } else if (val < 1000000) {
+ pj_ansi_sprintf(buf, "%d.%dK",
+ val / 1000,
+ (val % 1000) / 100);
+ } else {
+ pj_ansi_sprintf(buf, "%d.%02dM",
+ val / 1000000,
+ (val % 1000000) / 10000);
+ }
+
+ return buf;
+}
+
+
+#define SAMPLES_TO_USEC(usec, samples, clock_rate) \
+ do { \
+ if (samples <= 4294) \
+ usec = samples * 1000000 / clock_rate; \
+ else { \
+ usec = samples * 1000 / clock_rate; \
+ usec *= 1000; \
+ } \
+ } while(0)
+
+#define PRINT_VOIP_MTC_VAL(s, v) \
+ if (v == 127) \
+ sprintf(s, "(na)"); \
+ else \
+ sprintf(s, "%d", v)
+
+
+/*
+ * Print stream statistics
+ */
+static void print_stream_stat(pjmedia_stream *stream,
+ const pjmedia_codec_param *codec_param)
+{
+ char duration[80], last_update[80];
+ char bps[16], ipbps[16], packets[16], bytes[16], ipbytes[16];
+ pjmedia_port *port;
+ pjmedia_rtcp_stat stat;
+ pj_time_val now;
+
+
+ pj_gettimeofday(&now);
+ pjmedia_stream_get_stat(stream, &stat);
+ pjmedia_stream_get_port(stream, &port);
+
+ puts("Stream statistics:");
+
+ /* Print duration */
+ PJ_TIME_VAL_SUB(now, stat.start);
+ sprintf(duration, " Duration: %02ld:%02ld:%02ld.%03ld",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ (now.sec % 60),
+ now.msec);
+
+
+ printf(" Info: audio %dHz, %dms/frame, %sB/s (%sB/s +IP hdr)\n",
+ PJMEDIA_PIA_SRATE(&port->info),
+ PJMEDIA_PIA_PTIME(&port->info),
+ good_number(bps, (codec_param->info.avg_bps+7)/8),
+ good_number(ipbps, ((codec_param->info.avg_bps+7)/8) +
+ (40 * 1000 /
+ codec_param->setting.frm_per_pkt /
+ codec_param->info.frm_ptime)));
+
+ if (stat.rx.update_cnt == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, stat.rx.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ printf(" RX stat last update: %s\n"
+ " total %s packets %sB received (%sB +IP hdr)%s\n"
+ " pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
+ " (msec) min avg max last dev\n"
+ " loss period: %7.3f %7.3f %7.3f %7.3f %7.3f%s\n"
+ " jitter : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
+ last_update,
+ good_number(packets, stat.rx.pkt),
+ good_number(bytes, stat.rx.bytes),
+ good_number(ipbytes, stat.rx.bytes + stat.rx.pkt * 32),
+ "",
+ stat.rx.loss,
+ stat.rx.loss * 100.0 / (stat.rx.pkt + stat.rx.loss),
+ stat.rx.dup,
+ stat.rx.dup * 100.0 / (stat.rx.pkt + stat.rx.loss),
+ stat.rx.reorder,
+ stat.rx.reorder * 100.0 / (stat.rx.pkt + stat.rx.loss),
+ "",
+ stat.rx.loss_period.min / 1000.0,
+ stat.rx.loss_period.mean / 1000.0,
+ stat.rx.loss_period.max / 1000.0,
+ stat.rx.loss_period.last / 1000.0,
+ pj_math_stat_get_stddev(&stat.rx.loss_period) / 1000.0,
+ "",
+ stat.rx.jitter.min / 1000.0,
+ stat.rx.jitter.mean / 1000.0,
+ stat.rx.jitter.max / 1000.0,
+ stat.rx.jitter.last / 1000.0,
+ pj_math_stat_get_stddev(&stat.rx.jitter) / 1000.0,
+ ""
+ );
+
+
+ if (stat.tx.update_cnt == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, stat.tx.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ printf(" TX stat last update: %s\n"
+ " total %s packets %sB sent (%sB +IP hdr)%s\n"
+ " pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
+ " (msec) min avg max last dev\n"
+ " loss period: %7.3f %7.3f %7.3f %7.3f %7.3f%s\n"
+ " jitter : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
+ last_update,
+ good_number(packets, stat.tx.pkt),
+ good_number(bytes, stat.tx.bytes),
+ good_number(ipbytes, stat.tx.bytes + stat.tx.pkt * 32),
+ "",
+ stat.tx.loss,
+ stat.tx.loss * 100.0 / (stat.tx.pkt + stat.tx.loss),
+ stat.tx.dup,
+ stat.tx.dup * 100.0 / (stat.tx.pkt + stat.tx.loss),
+ stat.tx.reorder,
+ stat.tx.reorder * 100.0 / (stat.tx.pkt + stat.tx.loss),
+ "",
+ stat.tx.loss_period.min / 1000.0,
+ stat.tx.loss_period.mean / 1000.0,
+ stat.tx.loss_period.max / 1000.0,
+ stat.tx.loss_period.last / 1000.0,
+ pj_math_stat_get_stddev(&stat.tx.loss_period) / 1000.0,
+ "",
+ stat.tx.jitter.min / 1000.0,
+ stat.tx.jitter.mean / 1000.0,
+ stat.tx.jitter.max / 1000.0,
+ stat.tx.jitter.last / 1000.0,
+ pj_math_stat_get_stddev(&stat.tx.jitter) / 1000.0,
+ ""
+ );
+
+
+ printf(" RTT delay : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
+ stat.rtt.min / 1000.0,
+ stat.rtt.mean / 1000.0,
+ stat.rtt.max / 1000.0,
+ stat.rtt.last / 1000.0,
+ pj_math_stat_get_stddev(&stat.rtt) / 1000.0,
+ ""
+ );
+
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+ /* RTCP XR Reports */
+ do {
+ char loss[16], dup[16];
+ char jitter[80];
+ char toh[80];
+ char plc[16], jba[16], jbr[16];
+ char signal_lvl[16], noise_lvl[16], rerl[16];
+ char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16];
+ pjmedia_rtcp_xr_stat xr_stat;
+
+ if (pjmedia_stream_get_stat_xr(stream, &xr_stat) != PJ_SUCCESS)
+ break;
+
+ puts("\nExtended reports:");
+
+ /* Statistics Summary */
+ puts(" Statistics Summary");
+
+ if (xr_stat.rx.stat_sum.l)
+ sprintf(loss, "%d", xr_stat.rx.stat_sum.lost);
+ else
+ sprintf(loss, "(na)");
+
+ if (xr_stat.rx.stat_sum.d)
+ sprintf(dup, "%d", xr_stat.rx.stat_sum.dup);
+ else
+ sprintf(dup, "(na)");
+
+ if (xr_stat.rx.stat_sum.j) {
+ unsigned jmin, jmax, jmean, jdev;
+
+ SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min,
+ port->info.fmt.det.aud.clock_rate);
+ SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max,
+ port->info.fmt.det.aud.clock_rate);
+ SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean,
+ port->info.fmt.det.aud.clock_rate);
+ SAMPLES_TO_USEC(jdev,
+ pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter),
+ port->info.fmt.det.aud.clock_rate);
+ sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
+ jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
+ } else
+ sprintf(jitter, "(report not available)");
+
+ if (xr_stat.rx.stat_sum.t) {
+ sprintf(toh, "%11d %11d %11d %11d",
+ xr_stat.rx.stat_sum.toh.min,
+ xr_stat.rx.stat_sum.toh.mean,
+ xr_stat.rx.stat_sum.toh.max,
+ pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
+ } else
+ sprintf(toh, "(report not available)");
+
+ if (xr_stat.rx.stat_sum.update.sec == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ printf(" RX last update: %s\n"
+ " begin seq=%d, end seq=%d%s\n"
+ " pkt loss=%s, dup=%s%s\n"
+ " (msec) min avg max dev\n"
+ " jitter : %s\n"
+ " toh : %s\n",
+ last_update,
+ xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq,
+ "",
+ loss, dup,
+ "",
+ jitter,
+ toh
+ );
+
+ if (xr_stat.tx.stat_sum.l)
+ sprintf(loss, "%d", xr_stat.tx.stat_sum.lost);
+ else
+ sprintf(loss, "(na)");
+
+ if (xr_stat.tx.stat_sum.d)
+ sprintf(dup, "%d", xr_stat.tx.stat_sum.dup);
+ else
+ sprintf(dup, "(na)");
+
+ if (xr_stat.tx.stat_sum.j) {
+ unsigned jmin, jmax, jmean, jdev;
+
+ SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min,
+ port->info.fmt.det.aud.clock_rate);
+ SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max,
+ port->info.fmt.det.aud.clock_rate);
+ SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean,
+ port->info.fmt.det.aud.clock_rate);
+ SAMPLES_TO_USEC(jdev,
+ pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter),
+ port->info.fmt.det.aud.clock_rate);
+ sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
+ jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
+ } else
+ sprintf(jitter, "(report not available)");
+
+ if (xr_stat.tx.stat_sum.t) {
+ sprintf(toh, "%11d %11d %11d %11d",
+ xr_stat.tx.stat_sum.toh.min,
+ xr_stat.tx.stat_sum.toh.mean,
+ xr_stat.tx.stat_sum.toh.max,
+ pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
+ } else
+ sprintf(toh, "(report not available)");
+
+ if (xr_stat.tx.stat_sum.update.sec == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ printf(" TX last update: %s\n"
+ " begin seq=%d, end seq=%d%s\n"
+ " pkt loss=%s, dup=%s%s\n"
+ " (msec) min avg max dev\n"
+ " jitter : %s\n"
+ " toh : %s\n",
+ last_update,
+ xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq,
+ "",
+ loss, dup,
+ "",
+ jitter,
+ toh
+ );
+
+ /* VoIP Metrics */
+ puts(" VoIP Metrics");
+
+ PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl);
+ PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl);
+ PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl);
+ PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor);
+ PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor);
+ PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq);
+ PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq);
+
+ switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) {
+ case PJMEDIA_RTCP_XR_PLC_DIS:
+ sprintf(plc, "DISABLED");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_ENH:
+ sprintf(plc, "ENHANCED");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_STD:
+ sprintf(plc, "STANDARD");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_UNK:
+ default:
+ sprintf(plc, "UNKNOWN");
+ break;
+ }
+
+ switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) {
+ case PJMEDIA_RTCP_XR_JB_FIXED:
+ sprintf(jba, "FIXED");
+ break;
+ case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
+ sprintf(jba, "ADAPTIVE");
+ break;
+ default:
+ sprintf(jba, "UNKNOWN");
+ break;
+ }
+
+ sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F);
+
+ if (xr_stat.rx.voip_mtc.update.sec == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ printf(" RX last update: %s\n"
+ " packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
+ " burst : density=%d (%.2f%%), duration=%d%s\n"
+ " gap : density=%d (%.2f%%), duration=%d%s\n"
+ " delay : round trip=%d%s, end system=%d%s\n"
+ " level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
+ " quality : R factor=%s, ext R factor=%s\n"
+ " MOS LQ=%s, MOS CQ=%s\n"
+ " config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
+ " JB delay : cur=%d%s, max=%d%s, abs max=%d%s\n",
+ last_update,
+ /* pakcets */
+ xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256,
+ xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256,
+ /* burst */
+ xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256,
+ xr_stat.rx.voip_mtc.burst_dur, "ms",
+ /* gap */
+ xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256,
+ xr_stat.rx.voip_mtc.gap_dur, "ms",
+ /* delay */
+ xr_stat.rx.voip_mtc.rnd_trip_delay, "ms",
+ xr_stat.rx.voip_mtc.end_sys_delay, "ms",
+ /* level */
+ signal_lvl, "dB",
+ noise_lvl, "dB",
+ rerl, "",
+ /* quality */
+ r_factor, ext_r_factor, mos_lq, mos_cq,
+ /* config */
+ plc, jba, jbr, xr_stat.rx.voip_mtc.gmin,
+ /* JB delay */
+ xr_stat.rx.voip_mtc.jb_nom, "ms",
+ xr_stat.rx.voip_mtc.jb_max, "ms",
+ xr_stat.rx.voip_mtc.jb_abs_max, "ms"
+ );
+
+ PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl);
+ PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl);
+ PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl);
+ PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor);
+ PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor);
+ PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq);
+ PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq);
+
+ switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) {
+ case PJMEDIA_RTCP_XR_PLC_DIS:
+ sprintf(plc, "DISABLED");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_ENH:
+ sprintf(plc, "ENHANCED");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_STD:
+ sprintf(plc, "STANDARD");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_UNK:
+ default:
+ sprintf(plc, "unknown");
+ break;
+ }
+
+ switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) {
+ case PJMEDIA_RTCP_XR_JB_FIXED:
+ sprintf(jba, "FIXED");
+ break;
+ case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
+ sprintf(jba, "ADAPTIVE");
+ break;
+ default:
+ sprintf(jba, "unknown");
+ break;
+ }
+
+ sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F);
+
+ if (xr_stat.tx.voip_mtc.update.sec == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ printf(" TX last update: %s\n"
+ " packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
+ " burst : density=%d (%.2f%%), duration=%d%s\n"
+ " gap : density=%d (%.2f%%), duration=%d%s\n"
+ " delay : round trip=%d%s, end system=%d%s\n"
+ " level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
+ " quality : R factor=%s, ext R factor=%s\n"
+ " MOS LQ=%s, MOS CQ=%s\n"
+ " config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
+ " JB delay : cur=%d%s, max=%d%s, abs max=%d%s\n",
+ last_update,
+ /* pakcets */
+ xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256,
+ xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256,
+ /* burst */
+ xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256,
+ xr_stat.tx.voip_mtc.burst_dur, "ms",
+ /* gap */
+ xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256,
+ xr_stat.tx.voip_mtc.gap_dur, "ms",
+ /* delay */
+ xr_stat.tx.voip_mtc.rnd_trip_delay, "ms",
+ xr_stat.tx.voip_mtc.end_sys_delay, "ms",
+ /* level */
+ signal_lvl, "dB",
+ noise_lvl, "dB",
+ rerl, "",
+ /* quality */
+ r_factor, ext_r_factor, mos_lq, mos_cq,
+ /* config */
+ plc, jba, jbr, xr_stat.tx.voip_mtc.gmin,
+ /* JB delay */
+ xr_stat.tx.voip_mtc.jb_nom, "ms",
+ xr_stat.tx.voip_mtc.jb_max, "ms",
+ xr_stat.tx.voip_mtc.jb_abs_max, "ms"
+ );
+
+
+ /* RTT delay (by receiver side) */
+ printf(" (msec) min avg max last dev\n");
+ printf(" RTT delay : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n",
+ xr_stat.rtt.min / 1000.0,
+ xr_stat.rtt.mean / 1000.0,
+ xr_stat.rtt.max / 1000.0,
+ xr_stat.rtt.last / 1000.0,
+ pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0,
+ ""
+ );
+ } while (0);
+#endif /* PJMEDIA_HAS_RTCP_XR */
+
+}
+
diff --git a/pjsip-apps/src/samples/strerror.c b/pjsip-apps/src/samples/strerror.c
new file mode 100644
index 0000000..b03e8c1
--- /dev/null
+++ b/pjsip-apps/src/samples/strerror.c
@@ -0,0 +1,71 @@
+/* $Id: strerror.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+/**
+ * \page page_strerror_c Samples: Print out error message
+ *
+ * This file is pjsip-apps/src/samples/strerror.c
+ *
+ * \includelineno strerror.c
+ */
+
+
+#include <pjlib.h>
+#include <pjlib-util.h>
+#include <pjsip.h>
+#include <pjmedia.h>
+#include <pjnath.h>
+#include <pjsip_simple.h>
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+ pj_caching_pool cp;
+ pjmedia_endpt *med_ept;
+ pjsip_endpoint *sip_ept;
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_status_t code;
+
+ if (argc != 2) {
+ puts("Usage: strerror ERRNUM");
+ return 1;
+ }
+
+ pj_log_set_level(3);
+
+ pj_init();
+ pj_caching_pool_init(&cp, NULL, 0);
+ pjlib_util_init();
+ pjnath_init();
+ pjmedia_endpt_create(&cp.factory, NULL, 0, &med_ept);
+ pjsip_endpt_create(&cp.factory, "localhost", &sip_ept);
+ pjsip_evsub_init_module(sip_ept);
+
+ code = atoi(argv[1]);
+ pj_strerror(code, errmsg, sizeof(errmsg));
+
+ printf("Status %d: %s\n", code, errmsg);
+
+ pj_shutdown();
+ return 0;
+}
+
diff --git a/pjsip-apps/src/samples/tonegen.c b/pjsip-apps/src/samples/tonegen.c
new file mode 100644
index 0000000..5190186
--- /dev/null
+++ b/pjsip-apps/src/samples/tonegen.c
@@ -0,0 +1,159 @@
+/* $Id: tonegen.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+/**
+ * \page page_pjmedia_samples_tonegen_c Samples: Sine Wave/Dual-Tone Generation
+ *
+ * This is a simple program to generate a tone and write the samples to
+ * a raw PCM file. The main purpose of this file is to analyze the
+ * quality of the tones/sine wave generated by PJMEDIA tone/sine wave
+ * generator.
+ *
+ * This file is pjsip-apps/src/samples/tonegen.c
+ *
+ * \includelineno tonegen.c
+ */
+
+
+#include <pjmedia.h>
+#include <pjlib.h>
+
+#define SAMPLES_PER_FRAME 64
+#define ON_DURATION 100
+#define OFF_DURATION 100
+
+
+/*
+ * main()
+ */
+int main()
+{
+ pj_caching_pool cp;
+ pjmedia_endpt *med_endpt;
+ pj_pool_t *pool;
+ pjmedia_port *port;
+ unsigned i;
+ pj_status_t status;
+
+
+ /* Must init PJLIB first: */
+ status = pj_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);
+
+ /*
+ * Initialize media endpoint.
+ * This will implicitly initialize PJMEDIA too.
+ */
+ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Create memory pool for our file player */
+ pool = pj_pool_create( &cp.factory, /* pool factory */
+ "app", /* pool name. */
+ 4000, /* init size */
+ 4000, /* increment size */
+ NULL /* callback on error */
+ );
+
+ status = pjmedia_tonegen_create(pool, 8000, 1, SAMPLES_PER_FRAME, 16, 0, &port);
+ if (status != PJ_SUCCESS)
+ return 1;
+
+ {
+ pjmedia_tone_desc tones[3];
+
+ tones[0].freq1 = 200;
+ tones[0].freq2 = 0;
+ tones[0].on_msec = ON_DURATION;
+ tones[0].off_msec = OFF_DURATION;
+
+ tones[1].freq1 = 400;
+ tones[1].freq2 = 0;
+ tones[1].on_msec = ON_DURATION;
+ tones[1].off_msec = OFF_DURATION;
+
+ tones[2].freq1 = 800;
+ tones[2].freq2 = 0;
+ tones[2].on_msec = ON_DURATION;
+ tones[2].off_msec = OFF_DURATION;
+
+ status = pjmedia_tonegen_play(port, 3, tones, 0);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
+ }
+
+ {
+ pjmedia_tone_digit digits[2];
+
+ digits[0].digit = '0';
+ digits[0].on_msec = ON_DURATION;
+ digits[0].off_msec = OFF_DURATION;
+
+ digits[1].digit = '0';
+ digits[1].on_msec = ON_DURATION;
+ digits[1].off_msec = OFF_DURATION;
+
+ status = pjmedia_tonegen_play_digits(port, 2, digits, 0);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
+ }
+
+ {
+ pjmedia_frame frm;
+ FILE *f;
+ void *buf;
+
+ buf = pj_pool_alloc(pool, 2*8000);
+ frm.buf = buf;
+
+ f = fopen("tonegen.pcm", "wb");
+
+ for (i=0; i<8000/SAMPLES_PER_FRAME; ++i) {
+ int count;
+ pjmedia_port_get_frame(port, &frm);
+ count = fwrite(buf, SAMPLES_PER_FRAME, 2, f);
+ if (count != 2)
+ break;
+ }
+
+ pj_assert(pjmedia_tonegen_is_busy(port) == 0);
+ fclose(f);
+ }
+
+ /* Delete port */
+ pjmedia_port_destroy(port);
+
+ /* Release application pool */
+ pj_pool_release( pool );
+
+ /* Destroy media endpoint. */
+ pjmedia_endpt_destroy( med_endpt );
+
+ /* Destroy pool factory */
+ pj_caching_pool_destroy( &cp );
+
+ /* Shutdown PJLIB */
+ pj_shutdown();
+
+
+ /* Done. */
+ return 0;
+}
diff --git a/pjsip-apps/src/samples/util.h b/pjsip-apps/src/samples/util.h
new file mode 100644
index 0000000..40a8f6e
--- /dev/null
+++ b/pjsip-apps/src/samples/util.h
@@ -0,0 +1,173 @@
+/* $Id: util.h 3550 2011-05-05 05:33:27Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+#include <stdlib.h> /* strtol() */
+
+/* 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)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ PJ_LOG(3,(sender, "%s: %s [code=%d]", title, errmsg, status));
+ return 1;
+}
+
+
+
+/* Constants */
+#define CLOCK_RATE 44100
+#define NSAMPLES (CLOCK_RATE * 20 / 1000)
+#define NCHANNELS 1
+#define NBITS 16
+
+/*
+ * Common sound options.
+ */
+#define SND_USAGE \
+" -d, --dev=NUM Sound device use device id NUM (default=-1) \n"\
+" -r, --rate=HZ Set clock rate in samples per sec (default=44100)\n"\
+" -c, --channel=NUM Set # of channels (default=1 for mono). \n"\
+" -f, --frame=NUM Set # of samples per frame (default equival 20ms)\n"\
+" -b, --bit=NUM Set # of bits per sample (default=16) \n"
+
+
+/*
+ * This utility function parses the command line and look for
+ * common sound options.
+ */
+pj_status_t get_snd_options(const char *app_name,
+ int argc,
+ char *argv[],
+ int *dev_id,
+ int *clock_rate,
+ int *channel_count,
+ int *samples_per_frame,
+ int *bits_per_sample)
+{
+ struct pj_getopt_option long_options[] = {
+ { "dev", 1, 0, 'd' },
+ { "rate", 1, 0, 'r' },
+ { "channel", 1, 0, 'c' },
+ { "frame", 1, 0, 'f' },
+ { "bit", 1, 0, 'b' },
+ { NULL, 0, 0, 0 },
+ };
+ int c;
+ int option_index;
+ long val;
+ char *err;
+
+ *samples_per_frame = 0;
+
+ pj_optind = 0;
+ while((c=pj_getopt_long(argc,argv, "d:r:c:f:b:",
+ long_options, &option_index))!=-1)
+ {
+
+ switch (c) {
+ case 'd':
+ /* device */
+ val = strtol(pj_optarg, &err, 10);
+ if (*err) {
+ PJ_LOG(3,(app_name, "Error: invalid value for device id"));
+ return PJ_EINVAL;
+ }
+ *dev_id = val;
+ break;
+
+ case 'r':
+ /* rate */
+ val = strtol(pj_optarg, &err, 10);
+ if (*err) {
+ PJ_LOG(3,(app_name, "Error: invalid value for clock rate"));
+ return PJ_EINVAL;
+ }
+ *clock_rate = val;
+ break;
+
+ case 'c':
+ /* channel count */
+ val = strtol(pj_optarg, &err, 10);
+ if (*err) {
+ PJ_LOG(3,(app_name, "Error: invalid channel count"));
+ return PJ_EINVAL;
+ }
+ *channel_count = val;
+ break;
+
+ case 'f':
+ /* frame count/samples per frame */
+ val = strtol(pj_optarg, &err, 10);
+ if (*err) {
+ PJ_LOG(3,(app_name, "Error: invalid samples per frame"));
+ return PJ_EINVAL;
+ }
+ *samples_per_frame = val;
+ break;
+
+ case 'b':
+ /* bit per sample */
+ val = strtol(pj_optarg, &err, 10);
+ if (*err) {
+ PJ_LOG(3,(app_name, "Error: invalid samples bits per sample"));
+ return PJ_EINVAL;
+ }
+ *bits_per_sample = val;
+ break;
+
+ default:
+ /* Unknown options */
+ PJ_LOG(3,(app_name, "Error: unknown options '%c'", pj_optopt));
+ return PJ_EINVAL;
+ }
+
+ }
+
+ if (*samples_per_frame == 0) {
+ *samples_per_frame = *clock_rate * *channel_count * 20 / 1000;
+ }
+
+ return 0;
+}
+
+
+/* Dump memory pool usage. */
+void dump_pool_usage( const char *app_name, pj_caching_pool *cp )
+{
+#if !defined(PJ_HAS_POOL_ALT_API) || PJ_HAS_POOL_ALT_API==0
+ pj_pool_t *p;
+ unsigned total_alloc = 0;
+ unsigned total_used = 0;
+
+ /* Accumulate memory usage in active list. */
+ p = cp->used_list.next;
+ while (p != (pj_pool_t*) &cp->used_list) {
+ total_alloc += pj_pool_get_capacity(p);
+ total_used += pj_pool_get_used_size(p);
+ p = p->next;
+ }
+
+ PJ_LOG(3, (app_name, "Total pool memory allocated=%d KB, used=%d KB",
+ total_alloc / 1000,
+ total_used / 1000));
+#endif
+}
diff --git a/pjsip-apps/src/samples/vid_streamutil.c b/pjsip-apps/src/samples/vid_streamutil.c
new file mode 100644
index 0000000..b56c398
--- /dev/null
+++ b/pjsip-apps/src/samples/vid_streamutil.c
@@ -0,0 +1,967 @@
+/* $Id: vid_streamutil.c 4084 2012-04-25 07:13:05Z ming $ */
+/*
+ * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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
+ */
+
+
+/**
+ * \page page_pjmedia_samples_vid_streamutil_c Samples: Video Streaming
+ *
+ * This example mainly demonstrates how to stream video to remote
+ * peer using RTP.
+ *
+ * This file is pjsip-apps/src/samples/vid_streamutil.c
+ *
+ * \includelineno vid_streamutil.c
+ */
+
+#include <pjlib.h>
+#include <pjlib-util.h>
+#include <pjmedia.h>
+#include <pjmedia-codec.h>
+#include <pjmedia/transport_srtp.h>
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+
+#include <stdlib.h> /* atoi() */
+#include <stdio.h>
+
+#include "util.h"
+
+
+static const char *desc =
+ " vid_streamutil \n"
+ "\n"
+ " PURPOSE: \n"
+ " Demonstrate how to use pjmedia video stream component to \n"
+ " transmit/receive RTP packets to/from video device/file. \n"
+ "\n"
+ "\n"
+ " USAGE: \n"
+ " vid_streamutil [options] \n"
+ "\n"
+ "\n"
+ " Options: \n"
+ " --codec=CODEC Set the codec name. \n"
+ " --local-port=PORT Set local RTP port (default=4000) \n"
+ " --remote=IP:PORT Set the remote peer. If this option is set, \n"
+ " the program will transmit RTP audio to the \n"
+ " specified address. (default: recv only) \n"
+ " --play-file=AVI Send video from the AVI file instead of from \n"
+ " the video device. \n"
+ " --send-recv Set stream direction to bidirectional. \n"
+ " --send-only Set stream direction to send only \n"
+ " --recv-only Set stream direction to recv only (default) \n"
+
+ " --send-width Video width to be sent \n"
+ " --send-height Video height to be sent \n"
+ " --send-width and --send-height not applicable \n"
+ " for file streaming (see --play-file) \n"
+
+ " --send-pt Payload type for sending \n"
+ " --recv-pt Payload type for receiving \n"
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ " --use-srtp[=NAME] Enable SRTP with crypto suite NAME \n"
+ " e.g: AES_CM_128_HMAC_SHA1_80 (default), \n"
+ " AES_CM_128_HMAC_SHA1_32 \n"
+ " Use this option along with the TX & RX keys, \n"
+ " formated of 60 hex digits (e.g: E148DA..) \n"
+ " --srtp-tx-key SRTP key for transmiting \n"
+ " --srtp-rx-key SRTP key for receiving \n"
+#endif
+
+ "\n"
+;
+
+#define THIS_FILE "vid_streamutil.c"
+
+
+/* If set, local renderer will be created to play original file */
+#define HAS_LOCAL_RENDERER_FOR_PLAY_FILE 1
+
+
+/* Default width and height for the renderer, better be set to maximum
+ * acceptable size.
+ */
+#define DEF_RENDERER_WIDTH 640
+#define DEF_RENDERER_HEIGHT 480
+
+
+/* Prototype */
+static void print_stream_stat(pjmedia_vid_stream *stream,
+ const pjmedia_vid_codec_param *codec_param);
+
+/* Prototype for LIBSRTP utility in file datatypes.c */
+int hex_string_to_octet_string(char *raw, char *hex, int len);
+
+/*
+ * Register all codecs.
+ */
+static pj_status_t init_codecs(pj_pool_factory *pf)
+{
+ pj_status_t status;
+
+ /* To suppress warning about unused var when all codecs are disabled */
+ PJ_UNUSED_ARG(status);
+
+#if defined(PJMEDIA_HAS_FFMPEG_VID_CODEC) && PJMEDIA_HAS_FFMPEG_VID_CODEC != 0
+ status = pjmedia_codec_ffmpeg_vid_init(NULL, pf);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+#endif
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Register all codecs.
+ */
+static void deinit_codecs()
+{
+#if defined(PJMEDIA_HAS_FFMPEG_VID_CODEC) && PJMEDIA_HAS_FFMPEG_VID_CODEC != 0
+ pjmedia_codec_ffmpeg_vid_deinit();
+#endif
+}
+
+static pj_status_t create_file_player( pj_pool_t *pool,
+ const char *file_name,
+ pjmedia_port **p_play_port)
+{
+ pjmedia_avi_streams *avi_streams;
+ pjmedia_avi_stream *vid_stream;
+ pjmedia_port *play_port;
+ pj_status_t status;
+
+ status = pjmedia_avi_player_create_streams(pool, file_name, 0, &avi_streams);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ vid_stream = pjmedia_avi_streams_get_stream_by_media(avi_streams,
+ 0,
+ PJMEDIA_TYPE_VIDEO);
+ if (!vid_stream)
+ return PJ_ENOTFOUND;
+
+ play_port = pjmedia_avi_stream_get_port(vid_stream);
+ pj_assert(play_port);
+
+ *p_play_port = play_port;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Create stream based on the codec, dir, remote address, etc.
+ */
+static pj_status_t create_stream( pj_pool_t *pool,
+ pjmedia_endpt *med_endpt,
+ const pjmedia_vid_codec_info *codec_info,
+ pjmedia_vid_codec_param *codec_param,
+ pjmedia_dir dir,
+ pj_int8_t rx_pt,
+ pj_int8_t tx_pt,
+ pj_uint16_t local_port,
+ const pj_sockaddr_in *rem_addr,
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ pj_bool_t use_srtp,
+ const pj_str_t *crypto_suite,
+ const pj_str_t *srtp_tx_key,
+ const pj_str_t *srtp_rx_key,
+#endif
+ pjmedia_vid_stream **p_stream )
+{
+ pjmedia_vid_stream_info info;
+ pjmedia_transport *transport = NULL;
+ pj_status_t status;
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ pjmedia_transport *srtp_tp = NULL;
+#endif
+
+ /* Reset stream info. */
+ pj_bzero(&info, sizeof(info));
+
+ /* Initialize stream info formats */
+ info.type = PJMEDIA_TYPE_VIDEO;
+ info.dir = dir;
+ info.codec_info = *codec_info;
+ info.tx_pt = (tx_pt == -1)? codec_info->pt : tx_pt;
+ info.rx_pt = (rx_pt == -1)? codec_info->pt : rx_pt;
+ info.ssrc = pj_rand();
+ if (codec_param)
+ info.codec_param = codec_param;
+
+ /* Copy remote address */
+ pj_memcpy(&info.rem_addr, rem_addr, sizeof(pj_sockaddr_in));
+
+ /* If remote address is not set, set to an arbitrary address
+ * (otherwise stream will assert).
+ */
+ if (info.rem_addr.addr.sa_family == 0) {
+ const pj_str_t addr = pj_str("127.0.0.1");
+ pj_sockaddr_in_init(&info.rem_addr.ipv4, &addr, 0);
+ }
+
+ /* Create media transport */
+ status = pjmedia_transport_udp_create(med_endpt, NULL, local_port,
+ 0, &transport);
+ if (status != PJ_SUCCESS)
+ return status;
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ /* Check if SRTP enabled */
+ if (use_srtp) {
+ pjmedia_srtp_crypto tx_plc, rx_plc;
+
+ status = pjmedia_transport_srtp_create(med_endpt, transport,
+ NULL, &srtp_tp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_bzero(&tx_plc, sizeof(pjmedia_srtp_crypto));
+ pj_bzero(&rx_plc, sizeof(pjmedia_srtp_crypto));
+
+ tx_plc.key = *srtp_tx_key;
+ tx_plc.name = *crypto_suite;
+ rx_plc.key = *srtp_rx_key;
+ rx_plc.name = *crypto_suite;
+
+ status = pjmedia_transport_srtp_start(srtp_tp, &tx_plc, &rx_plc);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ transport = srtp_tp;
+ }
+#endif
+
+ /* Now that the stream info is initialized, we can create the
+ * stream.
+ */
+
+ status = pjmedia_vid_stream_create( med_endpt, pool, &info,
+ transport,
+ NULL, p_stream);
+
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Error creating stream", status);
+ pjmedia_transport_close(transport);
+ return status;
+ }
+
+
+ return PJ_SUCCESS;
+}
+
+
+typedef struct play_file_data
+{
+ const char *file_name;
+ pjmedia_port *play_port;
+ pjmedia_port *stream_port;
+ pjmedia_vid_codec *decoder;
+ pjmedia_port *renderer;
+ void *read_buf;
+ pj_size_t read_buf_size;
+ void *dec_buf;
+ pj_size_t dec_buf_size;
+} play_file_data;
+
+
+static void clock_cb(const pj_timestamp *ts, void *user_data)
+{
+ play_file_data *play_file = (play_file_data*)user_data;
+ pjmedia_frame read_frame, write_frame;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(ts);
+
+ /* Read frame from file */
+ read_frame.buf = play_file->read_buf;
+ read_frame.size = play_file->read_buf_size;
+ pjmedia_port_get_frame(play_file->play_port, &read_frame);
+
+ /* Decode frame, if needed */
+ if (play_file->decoder) {
+ pjmedia_vid_codec *decoder = play_file->decoder;
+
+ write_frame.buf = play_file->dec_buf;
+ write_frame.size = play_file->dec_buf_size;
+ status = pjmedia_vid_codec_decode(decoder, 1, &read_frame,
+ write_frame.size, &write_frame);
+ if (status != PJ_SUCCESS)
+ return;
+ } else {
+ write_frame = read_frame;
+ }
+
+ /* Display frame locally */
+ if (play_file->renderer)
+ pjmedia_port_put_frame(play_file->renderer, &write_frame);
+
+ /* Send frame */
+ pjmedia_port_put_frame(play_file->stream_port, &write_frame);
+}
+
+
+/*
+ * usage()
+ */
+static void usage()
+{
+ puts(desc);
+}
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+ pj_caching_pool cp;
+ pjmedia_endpt *med_endpt;
+ pj_pool_t *pool;
+ pjmedia_vid_stream *stream = NULL;
+ pjmedia_port *enc_port, *dec_port;
+ pj_status_t status;
+
+ pjmedia_vid_port *capture=NULL, *renderer=NULL;
+ pjmedia_vid_port_param vpp;
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ /* SRTP variables */
+ pj_bool_t use_srtp = PJ_FALSE;
+ char tmp_tx_key[64];
+ char tmp_rx_key[64];
+ pj_str_t srtp_tx_key = {NULL, 0};
+ pj_str_t srtp_rx_key = {NULL, 0};
+ pj_str_t srtp_crypto_suite = {NULL, 0};
+ int tmp_key_len;
+#endif
+
+ /* Default values */
+ const pjmedia_vid_codec_info *codec_info;
+ pjmedia_vid_codec_param codec_param;
+ pjmedia_dir dir = PJMEDIA_DIR_DECODING;
+ pj_sockaddr_in remote_addr;
+ pj_uint16_t local_port = 4000;
+ char *codec_id = NULL;
+ pjmedia_rect_size tx_size = {0};
+ pj_int8_t rx_pt = -1, tx_pt = -1;
+
+ play_file_data play_file = { NULL };
+ pjmedia_port *play_port = NULL;
+ pjmedia_vid_codec *play_decoder = NULL;
+ pjmedia_clock *play_clock = NULL;
+
+ enum {
+ OPT_CODEC = 'c',
+ OPT_LOCAL_PORT = 'p',
+ OPT_REMOTE = 'r',
+ OPT_PLAY_FILE = 'f',
+ OPT_SEND_RECV = 'b',
+ OPT_SEND_ONLY = 's',
+ OPT_RECV_ONLY = 'i',
+ OPT_SEND_WIDTH = 'W',
+ OPT_SEND_HEIGHT = 'H',
+ OPT_RECV_PT = 't',
+ OPT_SEND_PT = 'T',
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ OPT_USE_SRTP = 'S',
+#endif
+ OPT_SRTP_TX_KEY = 'x',
+ OPT_SRTP_RX_KEY = 'y',
+ OPT_HELP = 'h',
+ };
+
+ struct pj_getopt_option long_options[] = {
+ { "codec", 1, 0, OPT_CODEC },
+ { "local-port", 1, 0, OPT_LOCAL_PORT },
+ { "remote", 1, 0, OPT_REMOTE },
+ { "play-file", 1, 0, OPT_PLAY_FILE },
+ { "send-recv", 0, 0, OPT_SEND_RECV },
+ { "send-only", 0, 0, OPT_SEND_ONLY },
+ { "recv-only", 0, 0, OPT_RECV_ONLY },
+ { "send-width", 1, 0, OPT_SEND_WIDTH },
+ { "send-height", 1, 0, OPT_SEND_HEIGHT },
+ { "recv-pt", 1, 0, OPT_RECV_PT },
+ { "send-pt", 1, 0, OPT_SEND_PT },
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ { "use-srtp", 2, 0, OPT_USE_SRTP },
+ { "srtp-tx-key", 1, 0, OPT_SRTP_TX_KEY },
+ { "srtp-rx-key", 1, 0, OPT_SRTP_RX_KEY },
+#endif
+ { "help", 0, 0, OPT_HELP },
+ { NULL, 0, 0, 0 },
+ };
+
+ int c;
+ int option_index;
+
+
+ pj_bzero(&remote_addr, sizeof(remote_addr));
+
+
+ /* init PJLIB : */
+ status = pj_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Parse arguments */
+ pj_optind = 0;
+ while((c=pj_getopt_long(argc,argv, "h", long_options, &option_index))!=-1)
+ {
+ switch (c) {
+ case OPT_CODEC:
+ codec_id = pj_optarg;
+ break;
+
+ case OPT_LOCAL_PORT:
+ local_port = (pj_uint16_t) atoi(pj_optarg);
+ if (local_port < 1) {
+ printf("Error: invalid local port %s\n", pj_optarg);
+ return 1;
+ }
+ break;
+
+ case OPT_REMOTE:
+ {
+ pj_str_t ip = pj_str(strtok(pj_optarg, ":"));
+ pj_uint16_t port = (pj_uint16_t) atoi(strtok(NULL, ":"));
+
+ status = pj_sockaddr_in_init(&remote_addr, &ip, port);
+ if (status != PJ_SUCCESS) {
+ app_perror(THIS_FILE, "Invalid remote address", status);
+ return 1;
+ }
+ }
+ break;
+
+ case OPT_PLAY_FILE:
+ play_file.file_name = pj_optarg;
+ break;
+
+ case OPT_SEND_RECV:
+ dir = PJMEDIA_DIR_ENCODING_DECODING;
+ break;
+
+ case OPT_SEND_ONLY:
+ dir = PJMEDIA_DIR_ENCODING;
+ break;
+
+ case OPT_RECV_ONLY:
+ dir = PJMEDIA_DIR_DECODING;
+ break;
+
+ case OPT_SEND_WIDTH:
+ tx_size.w = (unsigned)atoi(pj_optarg);
+ break;
+
+ case OPT_SEND_HEIGHT:
+ tx_size.h = (unsigned)atoi(pj_optarg);
+ break;
+
+ case OPT_RECV_PT:
+ rx_pt = (pj_int8_t)atoi(pj_optarg);
+ break;
+
+ case OPT_SEND_PT:
+ tx_pt = (pj_int8_t)atoi(pj_optarg);
+ break;
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ case OPT_USE_SRTP:
+ use_srtp = PJ_TRUE;
+ if (pj_optarg) {
+ pj_strset(&srtp_crypto_suite, pj_optarg, strlen(pj_optarg));
+ } else {
+ srtp_crypto_suite = pj_str("AES_CM_128_HMAC_SHA1_80");
+ }
+ break;
+
+ case OPT_SRTP_TX_KEY:
+ tmp_key_len = hex_string_to_octet_string(tmp_tx_key, pj_optarg,
+ strlen(pj_optarg));
+ pj_strset(&srtp_tx_key, tmp_tx_key, tmp_key_len/2);
+ break;
+
+ case OPT_SRTP_RX_KEY:
+ tmp_key_len = hex_string_to_octet_string(tmp_rx_key, pj_optarg,
+ strlen(pj_optarg));
+ pj_strset(&srtp_rx_key, tmp_rx_key, tmp_key_len/2);
+ break;
+#endif
+
+ case OPT_HELP:
+ usage();
+ return 1;
+
+ default:
+ printf("Invalid options %s\n", argv[pj_optind]);
+ return 1;
+ }
+
+ }
+
+
+ /* Verify arguments. */
+ if (dir & PJMEDIA_DIR_ENCODING) {
+ if (remote_addr.sin_addr.s_addr == 0) {
+ printf("Error: remote address must be set\n");
+ return 1;
+ }
+ }
+
+ if (play_file.file_name != NULL && dir != PJMEDIA_DIR_ENCODING) {
+ printf("Direction is set to --send-only because of --play-file\n");
+ dir = PJMEDIA_DIR_ENCODING;
+ }
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ /* SRTP validation */
+ if (use_srtp) {
+ if (!srtp_tx_key.slen || !srtp_rx_key.slen)
+ {
+ printf("Error: Key for each SRTP stream direction must be set\n");
+ return 1;
+ }
+ }
+#endif
+
+ /* Must create a pool factory before we can allocate any memory. */
+ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+
+ /*
+ * Initialize media endpoint.
+ * This will implicitly initialize PJMEDIA too.
+ */
+ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Create memory pool for application purpose */
+ pool = pj_pool_create( &cp.factory, /* pool factory */
+ "app", /* pool name. */
+ 4000, /* init size */
+ 4000, /* increment size */
+ NULL /* callback on error */
+ );
+
+ /* Init video format manager */
+ pjmedia_video_format_mgr_create(pool, 64, 0, NULL);
+
+ /* Init video converter manager */
+ pjmedia_converter_mgr_create(pool, NULL);
+
+ /* Init event manager */
+ pjmedia_event_mgr_create(pool, 0, NULL);
+
+ /* Init video codec manager */
+ pjmedia_vid_codec_mgr_create(pool, NULL);
+
+ /* Init video subsystem */
+ pjmedia_vid_dev_subsys_init(&cp.factory);
+
+ /* Register all supported codecs */
+ status = init_codecs(&cp.factory);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+
+ /* Find which codec to use. */
+ if (codec_id) {
+ unsigned count = 1;
+ pj_str_t str_codec_id = pj_str(codec_id);
+
+ status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL,
+ &str_codec_id, &count,
+ &codec_info, NULL);
+ if (status != PJ_SUCCESS) {
+ printf("Error: unable to find codec %s\n", codec_id);
+ return 1;
+ }
+ } else {
+ static pjmedia_vid_codec_info info[1];
+ unsigned count = PJ_ARRAY_SIZE(info);
+
+ /* Default to first codec */
+ pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, NULL);
+ codec_info = &info[0];
+ }
+
+ /* Get codec default param for info */
+ status = pjmedia_vid_codec_mgr_get_default_param(NULL, codec_info,
+ &codec_param);
+ pj_assert(status == PJ_SUCCESS);
+
+ /* Set outgoing video size */
+ if (tx_size.w && tx_size.h)
+ codec_param.enc_fmt.det.vid.size = tx_size;
+
+#if DEF_RENDERER_WIDTH && DEF_RENDERER_HEIGHT
+ /* Set incoming video size */
+ if (DEF_RENDERER_WIDTH > codec_param.dec_fmt.det.vid.size.w)
+ codec_param.dec_fmt.det.vid.size.w = DEF_RENDERER_WIDTH;
+ if (DEF_RENDERER_HEIGHT > codec_param.dec_fmt.det.vid.size.h)
+ codec_param.dec_fmt.det.vid.size.h = DEF_RENDERER_HEIGHT;
+#endif
+
+ if (play_file.file_name) {
+ pjmedia_video_format_detail *file_vfd;
+ pjmedia_clock_param clock_param;
+ char fmt_name[5];
+
+ /* Create file player */
+ status = create_file_player(pool, play_file.file_name, &play_port);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+
+ /* Collect format info */
+ file_vfd = pjmedia_format_get_video_format_detail(&play_port->info.fmt,
+ PJ_TRUE);
+ PJ_LOG(2, (THIS_FILE, "Reading video stream %dx%d %s @%.2ffps",
+ file_vfd->size.w, file_vfd->size.h,
+ pjmedia_fourcc_name(play_port->info.fmt.id, fmt_name),
+ (1.0*file_vfd->fps.num/file_vfd->fps.denum)));
+
+ /* Allocate file read buffer */
+ play_file.read_buf_size = PJMEDIA_MAX_VIDEO_ENC_FRAME_SIZE;
+ play_file.read_buf = pj_pool_zalloc(pool, play_file.read_buf_size);
+
+ /* Create decoder, if the file and the stream uses different codec */
+ if (codec_info->fmt_id != (pjmedia_format_id)play_port->info.fmt.id) {
+ const pjmedia_video_format_info *dec_vfi;
+ pjmedia_video_apply_fmt_param dec_vafp = {0};
+ const pjmedia_vid_codec_info *codec_info2;
+ pjmedia_vid_codec_param codec_param2;
+
+ /* Find decoder */
+ status = pjmedia_vid_codec_mgr_get_codec_info2(NULL,
+ play_port->info.fmt.id,
+ &codec_info2);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+
+ /* Init decoder */
+ status = pjmedia_vid_codec_mgr_alloc_codec(NULL, codec_info2,
+ &play_decoder);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+
+ status = play_decoder->op->init(play_decoder, pool);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+
+ /* Open decoder */
+ status = pjmedia_vid_codec_mgr_get_default_param(NULL, codec_info2,
+ &codec_param2);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+
+ codec_param2.dir = PJMEDIA_DIR_DECODING;
+ status = play_decoder->op->open(play_decoder, &codec_param2);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+
+ /* Get decoder format info and apply param */
+ dec_vfi = pjmedia_get_video_format_info(NULL,
+ codec_info2->dec_fmt_id[0]);
+ if (!dec_vfi || !dec_vfi->apply_fmt) {
+ status = PJ_ENOTSUP;
+ goto on_exit;
+ }
+ dec_vafp.size = file_vfd->size;
+ (*dec_vfi->apply_fmt)(dec_vfi, &dec_vafp);
+
+ /* Allocate buffer to receive decoder output */
+ play_file.dec_buf_size = dec_vafp.framebytes;
+ play_file.dec_buf = pj_pool_zalloc(pool, play_file.dec_buf_size);
+ }
+
+ /* Create player clock */
+ clock_param.usec_interval = PJMEDIA_PTIME(&file_vfd->fps);
+ clock_param.clock_rate = codec_info->clock_rate;
+ status = pjmedia_clock_create2(pool, &clock_param,
+ PJMEDIA_CLOCK_NO_HIGHEST_PRIO,
+ &clock_cb, &play_file, &play_clock);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+
+ /* Override stream codec param for encoding direction */
+ codec_param.enc_fmt.det.vid.size = file_vfd->size;
+ codec_param.enc_fmt.det.vid.fps = file_vfd->fps;
+
+ } else {
+ pjmedia_vid_port_param_default(&vpp);
+
+ /* Set as active for all video devices */
+ vpp.active = PJ_TRUE;
+
+ /* Create video device port. */
+ if (dir & PJMEDIA_DIR_ENCODING) {
+ /* Create capture */
+ status = pjmedia_vid_dev_default_param(
+ pool,
+ PJMEDIA_VID_DEFAULT_CAPTURE_DEV,
+ &vpp.vidparam);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+
+ pjmedia_format_copy(&vpp.vidparam.fmt, &codec_param.enc_fmt);
+ vpp.vidparam.fmt.id = codec_param.dec_fmt.id;
+ vpp.vidparam.dir = PJMEDIA_DIR_CAPTURE;
+
+ status = pjmedia_vid_port_create(pool, &vpp, &capture);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+ }
+
+ if (dir & PJMEDIA_DIR_DECODING) {
+ /* Create renderer */
+ status = pjmedia_vid_dev_default_param(
+ pool,
+ PJMEDIA_VID_DEFAULT_RENDER_DEV,
+ &vpp.vidparam);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+
+ pjmedia_format_copy(&vpp.vidparam.fmt, &codec_param.dec_fmt);
+ vpp.vidparam.dir = PJMEDIA_DIR_RENDER;
+ vpp.vidparam.disp_size = vpp.vidparam.fmt.det.vid.size;
+ vpp.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
+ vpp.vidparam.window_flags = PJMEDIA_VID_DEV_WND_BORDER |
+ PJMEDIA_VID_DEV_WND_RESIZABLE;
+
+ status = pjmedia_vid_port_create(pool, &vpp, &renderer);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+ }
+ }
+
+ /* Set to ignore fmtp */
+ codec_param.ignore_fmtp = PJ_TRUE;
+
+ /* Create stream based on program arguments */
+ status = create_stream(pool, med_endpt, codec_info, &codec_param,
+ dir, rx_pt, tx_pt, local_port, &remote_addr,
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ use_srtp, &srtp_crypto_suite,
+ &srtp_tx_key, &srtp_rx_key,
+#endif
+ &stream);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+
+ /* Get the port interface of the stream */
+ status = pjmedia_vid_stream_get_port(stream, PJMEDIA_DIR_ENCODING,
+ &enc_port);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ status = pjmedia_vid_stream_get_port(stream, PJMEDIA_DIR_DECODING,
+ &dec_port);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+ /* Start streaming */
+ status = pjmedia_vid_stream_start(stream);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+
+ /* Start renderer */
+ if (renderer) {
+ status = pjmedia_vid_port_connect(renderer, dec_port, PJ_FALSE);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+ status = pjmedia_vid_port_start(renderer);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+ }
+
+ /* Start capture */
+ if (capture) {
+ status = pjmedia_vid_port_connect(capture, enc_port, PJ_FALSE);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+ status = pjmedia_vid_port_start(capture);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+ }
+
+ /* Start playing file */
+ if (play_file.file_name) {
+
+#if HAS_LOCAL_RENDERER_FOR_PLAY_FILE
+ /* Create local renderer */
+ pjmedia_vid_port_param_default(&vpp);
+ vpp.active = PJ_FALSE;
+ status = pjmedia_vid_dev_default_param(
+ pool,
+ PJMEDIA_VID_DEFAULT_RENDER_DEV,
+ &vpp.vidparam);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+
+ vpp.vidparam.dir = PJMEDIA_DIR_RENDER;
+ pjmedia_format_copy(&vpp.vidparam.fmt, &codec_param.dec_fmt);
+ vpp.vidparam.fmt.det.vid.size = play_port->info.fmt.det.vid.size;
+ vpp.vidparam.fmt.det.vid.fps = play_port->info.fmt.det.vid.fps;
+ vpp.vidparam.disp_size = vpp.vidparam.fmt.det.vid.size;
+ vpp.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
+ vpp.vidparam.window_flags = PJMEDIA_VID_DEV_WND_BORDER |
+ PJMEDIA_VID_DEV_WND_RESIZABLE;
+
+ status = pjmedia_vid_port_create(pool, &vpp, &renderer);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+ status = pjmedia_vid_port_start(renderer);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+#endif
+
+ /* Init play file data */
+ play_file.play_port = play_port;
+ play_file.stream_port = enc_port;
+ play_file.decoder = play_decoder;
+ if (renderer) {
+ play_file.renderer = pjmedia_vid_port_get_passive_port(renderer);
+ }
+
+ status = pjmedia_clock_start(play_clock);
+ if (status != PJ_SUCCESS)
+ goto on_exit;
+ }
+
+ /* Done */
+
+ if (dir == PJMEDIA_DIR_DECODING)
+ printf("Stream is active, dir is recv-only, local port is %d\n",
+ local_port);
+ else if (dir == PJMEDIA_DIR_ENCODING)
+ printf("Stream is active, dir is send-only, sending to %s:%d\n",
+ pj_inet_ntoa(remote_addr.sin_addr),
+ pj_ntohs(remote_addr.sin_port));
+ else
+ printf("Stream is active, send/recv, local port is %d, "
+ "sending to %s:%d\n",
+ local_port,
+ pj_inet_ntoa(remote_addr.sin_addr),
+ pj_ntohs(remote_addr.sin_port));
+
+ if (dir & PJMEDIA_DIR_ENCODING)
+ PJ_LOG(2, (THIS_FILE, "Sending %dx%d %.*s @%.2ffps",
+ codec_param.enc_fmt.det.vid.size.w,
+ codec_param.enc_fmt.det.vid.size.h,
+ codec_info->encoding_name.slen,
+ codec_info->encoding_name.ptr,
+ (1.0*codec_param.enc_fmt.det.vid.fps.num/
+ codec_param.enc_fmt.det.vid.fps.denum)));
+
+ for (;;) {
+ char tmp[10];
+
+ puts("");
+ puts("Commands:");
+ puts(" q Quit");
+ puts("");
+
+ printf("Command: "); fflush(stdout);
+
+ if (fgets(tmp, sizeof(tmp), stdin) == NULL) {
+ puts("EOF while reading stdin, will quit now..");
+ break;
+ }
+
+ if (tmp[0] == 'q')
+ break;
+
+ }
+
+
+
+ /* Start deinitialization: */
+on_exit:
+
+ /* Stop video devices */
+ if (capture)
+ pjmedia_vid_port_stop(capture);
+ if (renderer)
+ pjmedia_vid_port_stop(renderer);
+
+ /* Stop and destroy file clock */
+ if (play_clock) {
+ pjmedia_clock_stop(play_clock);
+ pjmedia_clock_destroy(play_clock);
+ }
+
+ /* Destroy file reader/player */
+ if (play_port)
+ pjmedia_port_destroy(play_port);
+
+ /* Destroy file decoder */
+ if (play_decoder) {
+ play_decoder->op->close(play_decoder);
+ pjmedia_vid_codec_mgr_dealloc_codec(NULL, play_decoder);
+ }
+
+ /* Destroy video devices */
+ if (capture)
+ pjmedia_vid_port_destroy(capture);
+ if (renderer)
+ pjmedia_vid_port_destroy(renderer);
+
+ /* Destroy stream */
+ if (stream) {
+ pjmedia_transport *tp;
+
+ tp = pjmedia_vid_stream_get_transport(stream);
+ pjmedia_vid_stream_destroy(stream);
+
+ pjmedia_transport_close(tp);
+ }
+
+ /* Deinit codecs */
+ deinit_codecs();
+
+ /* Shutdown video subsystem */
+ pjmedia_vid_dev_subsys_shutdown();
+
+ /* Destroy event manager */
+ pjmedia_event_mgr_destroy(NULL);
+
+ /* Release application pool */
+ pj_pool_release( pool );
+
+ /* Destroy media endpoint. */
+ pjmedia_endpt_destroy( med_endpt );
+
+ /* Destroy pool factory */
+ pj_caching_pool_destroy( &cp );
+
+ /* Shutdown PJLIB */
+ pj_shutdown();
+
+ return (status == PJ_SUCCESS) ? 0 : 1;
+}
+
+
+#else
+
+int main(int argc, char *argv[])
+{
+ PJ_UNUSED_ARG(argc);
+ PJ_UNUSED_ARG(argv);
+ puts("Error: this sample requires video capability (PJMEDIA_HAS_VIDEO == 1)");
+ return -1;
+}
+
+#endif /* PJMEDIA_HAS_VIDEO */