Added audiotest and initial work on audio demo sample

git-svn-id: https://svn.pjsip.org/repos/pjproject/branches/projects/aps-direct@2463 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjmedia/src/pjmedia-audiodev/audiotest.c b/pjmedia/src/pjmedia-audiodev/audiotest.c
new file mode 100644
index 0000000..563e1e8
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/audiotest.c
@@ -0,0 +1,298 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 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/audiotest.h>
+#include <pjmedia-audiodev/audiodev.h>
+#include <pjlib.h>
+#include <pjlib-util.h>
+ 
+#define THIS_FILE	    "audiotest.c"
+
+/* Test duration in msec */
+#define DURATION	    10000
+
+/* Skip the first msec from the calculation */
+#define SKIP_DURATION	    1000
+
+/* Max frames per sec (to calculate number of delays to keep). */
+#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 
+{
+    pj_pool_t			   *pool;
+    const pjmedia_aud_dev_param    *param;
+    pjmedia_aud_test_results	   *result;
+    pj_bool_t			    running;
+    pj_bool_t			    has_error;
+    pj_mutex_t			   *mutex;
+
+    struct stream_data		    capture_data;
+    struct stream_data		    playback_data;
+};
+
+static pj_status_t play_cb(void *user_data, pjmedia_frame *frame)
+{
+    struct test_data *test_data = (struct test_data *)user_data;
+    struct stream_data *strm_data = &test_data->playback_data;
+
+    pj_mutex_lock(test_data->mutex);
+
+    /* Skip frames when test is not started or test has finished */
+    if (!test_data->running) {
+	pj_bzero(frame->buf, frame->size);
+	pj_mutex_unlock(test_data->mutex);
+	return PJ_SUCCESS;
+    }
+
+    /* Save last timestamp seen (to calculate drift) */
+    strm_data->last_timestamp = frame->timestamp.u32.lo;
+
+    if (strm_data->last_called.u64 == 0) {
+	pj_get_timestamp(&strm_data->last_called);
+	/* Init min_delay to one frame */
+	strm_data->min_delay = test_data->param->samples_per_frame * 1000000 /
+			       test_data->param->clock_rate;
+	strm_data->first_timestamp = frame->timestamp.u32.lo;
+
+    } else if (strm_data->counter <= MAX_DELAY_COUNTER) {
+	pj_timestamp now;
+	unsigned delay;
+
+	pj_get_timestamp(&now);
+	
+	/* Calculate frame interval */
+	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;
+
+	/* Save the frame interval for later calculation */
+	strm_data->delay[strm_data->counter] = delay;
+	++strm_data->counter;
+
+    } else {
+
+	/* No space, can't take anymore frames */
+	test_data->running = 0;
+
+    }
+
+    pj_bzero(frame->buf, frame->size);
+
+    pj_mutex_unlock(test_data->mutex);
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame)
+{
+    struct test_data *test_data = (struct test_data*)user_data;
+    struct stream_data *strm_data = &test_data->capture_data;
+
+    pj_mutex_lock(test_data->mutex);
+
+    /* Skip frames when test is not started or test has finished */
+    if (!test_data->running) {
+	pj_mutex_unlock(test_data->mutex);
+	return PJ_SUCCESS;
+    }
+
+    /* Save last timestamp seen (to calculate drift) */
+    strm_data->last_timestamp = frame->timestamp.u32.lo;
+
+    if (strm_data->last_called.u64 == 0) {
+	pj_get_timestamp(&strm_data->last_called);
+	/* Init min_delay to one frame */
+	strm_data->min_delay = test_data->param->samples_per_frame * 1000000 /
+			       test_data->param->clock_rate;
+	strm_data->first_timestamp = frame->timestamp.u32.lo;
+
+    } else if (strm_data->counter <= MAX_DELAY_COUNTER) {
+	pj_timestamp now;
+	unsigned delay;
+
+	pj_get_timestamp(&now);
+
+	/* Calculate frame interval */
+	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;
+
+	/* Save the frame interval for later calculation */
+	strm_data->delay[strm_data->counter] = delay;
+	++strm_data->counter;
+
+    } else {
+
+	/* No space, can't take anymore frames */
+	test_data->running = 0;
+
+    }
+
+    pj_mutex_unlock(test_data->mutex);
+    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);
+}
+
+
+PJ_DEF(pj_status_t) pjmedia_aud_test( const pjmedia_aud_dev_param *param,
+				      pjmedia_aud_test_results *result)
+{
+    pj_status_t status = PJ_SUCCESS;
+    pjmedia_aud_stream *strm;
+    struct test_data test_data;
+    
+    /*
+     * Init test parameters
+     */
+    pj_bzero(&test_data, sizeof(test_data));
+    test_data.param = param;
+    test_data.result = result;
+
+    test_data.pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(),
+				    "audtest", 1000, 1000, NULL);
+    pj_mutex_create_simple(test_data.pool, "sndtest", &test_data.mutex); 
+
+    /*
+     * Open device.
+     */
+    status = pjmedia_aud_stream_create(test_data.param, &rec_cb, &play_cb, 
+				       &test_data, &strm);
+    if (status != PJ_SUCCESS) {
+        app_perror("Unable to open device", status);
+	pj_pool_release(test_data.pool);
+        return status;
+    }
+
+
+    /* Sleep for a while to let sound device "settles" */
+    pj_thread_sleep(200);
+
+    /*
+     * Start the stream.
+     */
+    status = pjmedia_aud_stream_start(strm);
+    if (status != PJ_SUCCESS) {
+        app_perror("Unable to start capture stream", status);
+	pjmedia_aud_stream_destroy(strm);
+	pj_pool_release(test_data.pool);
+        return status;
+    }
+
+    PJ_LOG(3,(THIS_FILE,
+	      " Please wait while test is in progress (~%d secs)..",
+	      (DURATION+SKIP_DURATION)/1000));
+
+    /* Let the stream runs for few msec/sec to get stable result.
+     * (capture normally begins with frames available simultaneously).
+     */
+    pj_thread_sleep(SKIP_DURATION);
+
+
+    /* Begin gather data */
+    test_data.running = 1;
+
+    /* 
+     * Let the test runs for a while.
+     */
+    pj_thread_sleep(DURATION);
+
+
+    /*
+     * Close stream.
+     */
+    test_data.running = 0;
+    pjmedia_aud_stream_destroy(strm);
+    pj_pool_release(test_data.pool);
+
+
+    /* 
+     * Gather results
+     */
+    result->rec.frame_cnt = test_data.capture_data.counter;
+    result->rec.min_interval = test_data.capture_data.min_delay / 1000;
+    result->rec.max_interval = test_data.capture_data.max_delay / 1000;
+    result->rec.max_burst = test_data.capture_data.max_delay / 1000 /
+			    (param->samples_per_frame * 1000 / param->clock_rate);
+
+    result->play.frame_cnt = test_data.playback_data.counter;
+    result->play.min_interval = test_data.playback_data.min_delay / 1000;
+    result->play.max_interval = test_data.playback_data.max_delay / 1000;
+    result->play.max_burst = test_data.playback_data.max_delay / 1000 /
+			     (param->samples_per_frame * 1000 / param->clock_rate);
+
+    /* Check drifting */
+    if (param->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? end_diff - start_diff :
+		start_diff - end_diff;
+
+	/* Allow one frame tolerance for clock drift detection */
+	if (drift < (int)param->samples_per_frame) {
+	    result->rec_drift_per_sec = 0;
+	} else {
+	    unsigned msec_dur;
+
+	    msec_dur = (test_data.capture_data.last_timestamp - 
+		       test_data.capture_data.first_timestamp) * 1000 /
+		       test_data.param->clock_rate;
+
+	    result->rec_drift_per_sec = drift * 1000 / msec_dur;
+
+	}
+    }
+
+    return test_data.has_error? PJ_EUNKNOWN : PJ_SUCCESS;
+}
+