From ef5ff2591f6c20a1cc1cef5f0cefd134ea6d8b4d Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Sun, 21 May 2006 19:00:28 +0000 Subject: Added sound test sample git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@464 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip-apps/build/Samples-vc.mak | 1 + pjsip-apps/build/Samples.mak | 1 + pjsip-apps/build/samples.dsp | 4 + pjsip-apps/src/samples/debug.c | 2 +- pjsip-apps/src/samples/sndtest.c | 540 ++++++++++++++++++++++++++++++++++++ pjsip-apps/src/samples/streamutil.c | 3 + 6 files changed, 550 insertions(+), 1 deletion(-) create mode 100644 pjsip-apps/src/samples/sndtest.c diff --git a/pjsip-apps/build/Samples-vc.mak b/pjsip-apps/build/Samples-vc.mak index 3293ad50..c8ae70ab 100644 --- a/pjsip-apps/build/Samples-vc.mak +++ b/pjsip-apps/build/Samples-vc.mak @@ -45,6 +45,7 @@ SAMPLES = $(BINDIR)\confsample.exe \ $(BINDIR)\siprtp.exe \ $(BINDIR)\sipstateless.exe \ $(BINDIR)\sndinfo.exe \ + $(BINDIR)\sndtest.exe \ $(BINDIR)\streamutil.exe diff --git a/pjsip-apps/build/Samples.mak b/pjsip-apps/build/Samples.mak index 0f44df19..dce5949e 100644 --- a/pjsip-apps/build/Samples.mak +++ b/pjsip-apps/build/Samples.mak @@ -48,6 +48,7 @@ SAMPLES := confsample \ siprtp \ sipstateless \ sndinfo \ + sndtest \ streamutil EXES := $(foreach file, $(SAMPLES), $(BINDIR)/$(file)-$(TARGET_NAME)$(HOST_EXE)) diff --git a/pjsip-apps/build/samples.dsp b/pjsip-apps/build/samples.dsp index e0c374dd..19641c46 100644 --- a/pjsip-apps/build/samples.dsp +++ b/pjsip-apps/build/samples.dsp @@ -130,6 +130,10 @@ SOURCE=..\src\samples\sndinfo.c # End Source File # Begin Source File +SOURCE=..\src\samples\sndtest.c +# End Source File +# Begin Source File + SOURCE=..\src\samples\streamutil.c # End Source File # End Group diff --git a/pjsip-apps/src/samples/debug.c b/pjsip-apps/src/samples/debug.c index 259a7def..2574c188 100644 --- a/pjsip-apps/src/samples/debug.c +++ b/pjsip-apps/src/samples/debug.c @@ -27,5 +27,5 @@ * E.g.: * #include "playfile.c" */ -#include "siprtp.c" +#include "sndtest.c" diff --git a/pjsip-apps/src/samples/sndtest.c b/pjsip-apps/src/samples/sndtest.c new file mode 100644 index 00000000..6b518e29 --- /dev/null +++ b/pjsip-apps/src/samples/sndtest.c @@ -0,0 +1,540 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include + +#include /* atoi() */ +#include + + + +#define THIS_FILE "sndtest.c" + +/* Warn (print log with yellow color) if frame jitter is larger than + * this value (in usec). + */ +#define WARN_JITTER_USEC 1000 + +/* Test duration in msec */ +#define DURATION 10000 + +/* Max frames per sec. */ +#define MAX_FRAMES_PER_SEC 100 + +/* Number of frame durations to keep */ +#define MAX_DELAY_COUNTER (((DURATION/1000)+1)*MAX_FRAMES_PER_SEC) + + +struct stream_data +{ + pj_uint32_t first_timestamp; + pj_uint32_t last_timestamp; + pj_timestamp last_called; + unsigned counter; + unsigned min_delay; + unsigned max_delay; + unsigned delay[MAX_DELAY_COUNTER]; +}; + +struct test_data { + pjmedia_dir dir; + unsigned clock_rate; + unsigned samples_per_frame; + unsigned channel_count; + pj_bool_t running; + pj_bool_t has_error; + + struct stream_data capture_data; + struct stream_data playback_data; +}; + + + +static const char *desc = + " sndtest.c \n" + " \n" + " PURPOSE: \n" + " Test the performance of sound device. \n" + " \n" + " USAGE: \n" + " sndtest --help \n" + " sndtest [options] \n" + " \n" + " where options: \n" + " --id=ID -i Use device ID (default is -1) \n" + " --rate=HZ -r Set test clock rate (default=8000)\n" + " --frame=SAMPLES -f Set number of samples per frame\n" + " --channel=CH -n Set number of channels (default=1)\n" + " --verbose -v Show verbose result \n" + " --help -h Show this screen \n" +; + + + +static void enum_devices(void) +{ + int i, count; + + count = pjmedia_snd_get_dev_count(); + if (count == 0) { + PJ_LOG(3,(THIS_FILE, "No devices found")); + return; + } + + PJ_LOG(3,(THIS_FILE, "Found %d devices:", count)); + for (i=0; iname, info->input_count, info->output_count)); + } +} + + +static const char *get_dev_name(int dev_id) +{ + const pjmedia_snd_dev_info *info; + + if (dev_id == -1) + dev_id = 0; + + info = pjmedia_snd_get_dev_info(dev_id); + if (info == NULL) + return "????"; + + return info->name; +} + + +static pj_status_t play_cb(void *user_data, pj_uint32_t timestamp, + void *output, unsigned size) +{ + struct test_data *test_data = user_data; + struct stream_data *strm_data = &test_data->playback_data; + + if (!test_data->running) { + pj_memset(output, 0, size); + return PJ_SUCCESS; + } + + strm_data->last_timestamp = timestamp; + + if (strm_data->last_called.u64 == 0) { + pj_get_timestamp(&strm_data->last_called); + strm_data->min_delay = test_data->samples_per_frame * 1000000 / + test_data->clock_rate; + strm_data->first_timestamp = timestamp; + + } else if (strm_data->counter <= MAX_DELAY_COUNTER) { + pj_timestamp now; + unsigned delay; + + pj_get_timestamp(&now); + + delay = pj_elapsed_usec(&strm_data->last_called, &now); + if (delay < strm_data->min_delay) + strm_data->min_delay = delay; + if (delay > strm_data->max_delay) + strm_data->max_delay = delay; + + strm_data->last_called = now; + + strm_data->delay[strm_data->counter] = delay; + ++strm_data->counter; + } + + pj_memset(output, 0, size); + return PJ_SUCCESS; +} + +static pj_status_t rec_cb(void *user_data, pj_uint32_t timestamp, + const void *input, unsigned size) +{ + + struct test_data *test_data = user_data; + struct stream_data *strm_data = &test_data->capture_data; + + PJ_UNUSED_ARG(input); + PJ_UNUSED_ARG(size); + + if (!test_data->running) { + return PJ_SUCCESS; + } + + strm_data->last_timestamp = timestamp; + + if (strm_data->last_called.u64 == 0) { + pj_get_timestamp(&strm_data->last_called); + strm_data->min_delay = test_data->samples_per_frame * 1000000 / + test_data->clock_rate; + strm_data->first_timestamp = timestamp; + + } else if (strm_data->counter <= MAX_DELAY_COUNTER) { + pj_timestamp now; + unsigned delay; + + pj_get_timestamp(&now); + + delay = pj_elapsed_usec(&strm_data->last_called, &now); + if (delay < strm_data->min_delay) + strm_data->min_delay = delay; + if (delay > strm_data->max_delay) + strm_data->max_delay = delay; + + strm_data->last_called = now; + + strm_data->delay[strm_data->counter] = delay; + ++strm_data->counter; + } + + return PJ_SUCCESS; +} + +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 print_stream_data(const char *title, + struct test_data *test_data, + struct stream_data *strm_data, + int verbose) +{ + unsigned i, dur; + unsigned min_jitter, max_jitter, avg_jitter=0; + + PJ_LOG(3,(THIS_FILE, " %s stream report:", title)); + + /* Check that frames are captured/played */ + if (strm_data->counter == 0) { + PJ_LOG(1,(THIS_FILE, " Error: no frames are captured/played!")); + test_data->has_error = 1; + return; + } + + /* Duration */ + dur = (strm_data->counter+1) * test_data->samples_per_frame * 1000 / + test_data->clock_rate; + PJ_LOG(3,(THIS_FILE, " Duration: %ds.%03d", + dur/1000, dur%1000)); + + /* Frame interval */ + if (strm_data->max_delay - strm_data->min_delay < WARN_JITTER_USEC) { + PJ_LOG(3,(THIS_FILE, + " Frame interval: min=%d.%03dms, max=%d.%03dms", + strm_data->min_delay/1000, strm_data->min_delay%1000, + strm_data->max_delay/1000, strm_data->max_delay%1000)); + } else { + test_data->has_error = 1; + PJ_LOG(2,(THIS_FILE, + " Frame interval: min=%d.%03dms, max=%d.%03dms", + strm_data->min_delay/1000, strm_data->min_delay%1000, + strm_data->max_delay/1000, strm_data->max_delay%1000)); + } + + if (verbose) { + unsigned i; + unsigned decor = pj_log_get_decor(); + + PJ_LOG(3,(THIS_FILE, " Dumping frame delays:")); + + pj_log_set_decor(0); + for (i=0; icounter; ++i) + PJ_LOG(3,(THIS_FILE, " %d.%03d", strm_data->delay[i]/1000, + strm_data->delay[i]%1000)); + PJ_LOG(3,(THIS_FILE, "\r\n")); + pj_log_set_decor(decor); + } + + /* Calculate jitter */ + min_jitter = 0xFFFFF; + max_jitter = 0; + + for (i=1; icounter; ++i) { + int jitter; + jitter = strm_data->delay[i] - strm_data->delay[i-1]; + if (jitter < 0) jitter = -jitter; + + if (jitter < (int)min_jitter) min_jitter = jitter; + if (jitter > (int)max_jitter) max_jitter = jitter; + + avg_jitter = ((i-1) * avg_jitter + jitter) / i; + } + if (max_jitter < WARN_JITTER_USEC) { + PJ_LOG(3,(THIS_FILE, + " Jitter: min=%d.%03dms, avg=%d.%03dms, max=%d.%03dms", + min_jitter/1000, min_jitter%1000, + avg_jitter/1000, avg_jitter%1000, + max_jitter/1000, max_jitter%1000)); + } else { + test_data->has_error = 1; + PJ_LOG(2,(THIS_FILE, + " Jitter: min=%d.%03dms, avg=%d.%03dms, max=%d.%03dms", + min_jitter/1000, min_jitter%1000, + avg_jitter/1000, avg_jitter%1000, + max_jitter/1000, max_jitter%1000)); + } +} + + +static int perform_test(const char *title, int dev_id, pjmedia_dir dir, + unsigned clock_rate, unsigned samples_per_frame, + unsigned nchannel, int verbose) +{ + pj_status_t status = PJ_SUCCESS; + pjmedia_snd_stream *strm; + struct test_data test_data; + + + /* + * Init test parameters + */ + pj_memset(&test_data, 0, sizeof(test_data)); + test_data.dir = dir; + test_data.clock_rate = clock_rate; + test_data.samples_per_frame = samples_per_frame; + test_data.channel_count = nchannel; + + /* + * Open device. + */ + PJ_LOG(3,(THIS_FILE, "Testing %s", title)); + + if (dir == PJMEDIA_DIR_CAPTURE) { + status = pjmedia_snd_open_rec( dev_id, clock_rate, nchannel, + samples_per_frame, 16, &rec_cb, + &test_data, &strm); + } else if (dir == PJMEDIA_DIR_PLAYBACK) { + status = pjmedia_snd_open_player( dev_id, clock_rate, nchannel, + samples_per_frame, 16, &play_cb, + &test_data, &strm); + } else { + status = pjmedia_snd_open( dev_id, dev_id, clock_rate, nchannel, + samples_per_frame, 16, &rec_cb, &play_cb, + &test_data, &strm); + } + + if (status != PJ_SUCCESS) { + app_perror("Unable to open device for capture", status); + return status; + } + + /* Sleep for a while to let sound device "settles" */ + pj_thread_sleep(100); + + + /* + * Start the stream. + */ + status = pjmedia_snd_stream_start(strm); + if (status != PJ_SUCCESS) { + app_perror("Unable to start capture stream", status); + return status; + } + + /* Let the stream runs for 1 second to get stable result. + * (capture normally begins with frames available simultaneously). + */ + pj_thread_sleep(1000); + + + /* Begin gather data */ + test_data.running = 1; + + /* + * Let the test runs for a while. + */ + PJ_LOG(3,(THIS_FILE, + " Please wait while test is in progress (~%d secs)..", + (DURATION/1000))); + pj_thread_sleep(DURATION); + + + /* + * Close stream. + */ + test_data.running = 0; + pjmedia_snd_stream_close(strm); + + + /* + * Print results. + */ + PJ_LOG(3,(THIS_FILE, " Dumping results:")); + + PJ_LOG(3,(THIS_FILE, " Parameters: clock rate=%dHz, %d samples/frame", + clock_rate, samples_per_frame)); + + if (dir & PJMEDIA_DIR_PLAYBACK) + print_stream_data("Playback", &test_data, &test_data.playback_data, + verbose); + if (dir & PJMEDIA_DIR_CAPTURE) + print_stream_data("Capture", &test_data, &test_data.capture_data, + verbose); + + /* Check drifting */ + if (dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) { + int end_diff, start_diff, drift; + + end_diff = test_data.capture_data.last_timestamp - + test_data.playback_data.last_timestamp; + start_diff = test_data.capture_data.first_timestamp- + test_data.playback_data.first_timestamp; + drift = end_diff - start_diff; + + PJ_LOG(3,(THIS_FILE, " Checking for clock drifts:")); + + /* Allow one frame tolerance for clock drift detection */ + if (drift < (int)samples_per_frame) { + PJ_LOG(3,(THIS_FILE, " No clock drifts is detected")); + } else { + const char *which = (drift<0 ? "slower" : "faster"); + unsigned msec_dur; + + if (drift < 0) drift = -drift; + + + msec_dur = (test_data.capture_data.last_timestamp - + test_data.capture_data.first_timestamp) * 1000 / + test_data.clock_rate; + + PJ_LOG(2,(THIS_FILE, + " Sound capture is %d samples %s than playback " + "at the end of the test (at the rate about %d samples" + " per second)", + drift, which, + drift * 1000 / msec_dur)); + + } + } + + if (test_data.has_error == 0) { + PJ_LOG(3,(THIS_FILE, " Test completed, sound device looks okay.")); + return 0; + } else { + PJ_LOG(2,(THIS_FILE, " Test completed with some warnings")); + return 1; + } +} + + +int main(int argc, char *argv[]) +{ + pj_caching_pool cp; + pjmedia_endpt *med_endpt; + int id = -1, verbose = 0; + int clock_rate = 8000; + int frame = -1; + int channel = 1; + struct pj_getopt_option long_options[] = { + { "id", 1, 0, 'i' }, + { "rate", 1, 0, 'r' }, + { "frame", 1, 0, 'f' }, + { "channel", 1, 0, 'n' }, + { "verbose", 0, 0, 'v' }, + { "help", 0, 0, 'h' }, + { NULL, 0, 0, 0 } + }; + int c, option_index; + + + pj_status_t status; + + /* Init pjlib */ + 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); + + /* Print devices */ + enum_devices(); + + /* Parse options */ + pj_optind = 0; + while((c=pj_getopt_long(argc,argv, "i:r:f:n:vh", + long_options, &option_index))!=-1) + { + switch (c) { + case 'i': + id = atoi(pj_optarg); + break; + case 'r': + clock_rate = atoi(pj_optarg); + break; + case 'f': + frame = atoi(pj_optarg); + break; + case 'n': + channel = atoi(pj_optarg); + break; + case 'v': + verbose = 1; + break; + case 'h': + puts(desc); + return 0; + break; + default: + printf("Error: invalid options %s\n", argv[pj_optind-1]); + puts(desc); + return 1; + } + } + + if (pj_optind != argc) { + printf("Error: invalid options\n"); + puts(desc); + return 1; + } + + if (!verbose) + pj_log_set_level(3); + + if (frame == -1) + frame = 10 * clock_rate / 1000; + + + status = perform_test(get_dev_name(id), id, PJMEDIA_DIR_CAPTURE_PLAYBACK, + clock_rate, frame, channel, verbose); + if (status != 0) + return 1; + + + return 0; +} + + diff --git a/pjsip-apps/src/samples/streamutil.c b/pjsip-apps/src/samples/streamutil.c index aea56178..bfe8b4ea 100644 --- a/pjsip-apps/src/samples/streamutil.c +++ b/pjsip-apps/src/samples/streamutil.c @@ -419,6 +419,9 @@ int main(int argc, char *argv[]) } + /* Start streaming */ + pjmedia_stream_start(stream); + /* Done */ -- cgit v1.2.3