Index: pjproject/branches/projects/aps-direct/pjmedia/build/pjmedia_audiodev.dsp
===================================================================
--- pjproject/branches/projects/aps-direct/pjmedia/build/pjmedia_audiodev.dsp	(revision 2462)
+++ pjproject/branches/projects/aps-direct/pjmedia/build/pjmedia_audiodev.dsp	(revision 2463)
@@ -94,4 +94,8 @@
 # Begin Source File
 
+SOURCE="..\src\pjmedia-audiodev\audiotest.c"
+# End Source File
+# Begin Source File
+
 SOURCE="..\src\pjmedia-audiodev\pa_dev.c"
 # End Source File
@@ -112,4 +116,8 @@
 SOURCE="..\include\pjmedia-audiodev\audiodev_imp.h"
 # End Source File
+# Begin Source File
+
+SOURCE="..\include\pjmedia-audiodev\audiotest.h"
+# End Source File
 # End Group
 # End Target
Index: pjproject/branches/projects/aps-direct/pjmedia/include/pjmedia-audiodev/audiodev.h
===================================================================
--- pjproject/branches/projects/aps-direct/pjmedia/include/pjmedia-audiodev/audiodev.h	(revision 2462)
+++ pjproject/branches/projects/aps-direct/pjmedia/include/pjmedia-audiodev/audiodev.h	(revision 2463)
@@ -226,5 +226,5 @@
      * The underlying driver name 
      */
-    char driver[64];
+    char driver[128];
 
     /** 
@@ -409,4 +409,12 @@
  */
 PJ_DECL(pj_status_t) pjmedia_aud_subsys_init(pj_pool_factory *pf);
+
+
+/**
+ * Get the pool factory registered to the audio subsystem.
+ *
+ * @return		The pool factory.
+ */
+PJ_DECL(pj_pool_factory*) pjmedia_aud_subsys_get_pool_factory(void);
 
 
Index: pjproject/branches/projects/aps-direct/pjmedia/include/pjmedia-audiodev/audiotest.h
===================================================================
--- pjproject/branches/projects/aps-direct/pjmedia/include/pjmedia-audiodev/audiotest.h	(revision 2463)
+++ pjproject/branches/projects/aps-direct/pjmedia/include/pjmedia-audiodev/audiotest.h	(revision 2463)
@@ -0,0 +1,106 @@
+/* $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 
+ */
+#ifndef __AUDIOTEST_H__
+#define __AUDIOTEST_H__
+
+/**
+ * @file audiotest.h
+ * @brief Audio test utility.
+ */
+#include <pjmedia-audiodev/audiodev.h>
+
+
+PJ_BEGIN_DECL
+
+/**
+ * @defgroup PJMED_AUD_TEST Audio tests utility.
+ * @ingroup PJMED_AUD_PORT
+ * @brief Audio test utility.
+ * @{
+ */
+
+/**
+ * Statistic for each direction.
+ */
+typedef struct pjmedia_aud_test_stat
+{
+    /**
+     * Number of frames processed during the test.
+     */
+    unsigned frame_cnt;
+
+    /** 
+     * Minimum inter-frame arrival time, in milliseconds 
+     */
+    unsigned min_interval;
+
+    /** 
+     * Maximum inter-frame arrival time, in milliseconds 
+     */
+    unsigned max_interval;
+
+    /** 
+     * Maximum number of frame burst 
+     */
+    unsigned max_burst;
+
+} pjmedia_aud_test_stat;
+
+
+/**
+ * Test results.
+ */
+typedef struct pjmedia_aud_test_results
+{
+    /**
+     * Recording statistic.
+     */
+    pjmedia_aud_test_stat rec;
+
+    /**
+     * Playback statistic.
+     */
+    pjmedia_aud_test_stat play;
+
+    /** 
+     * Clock drifts per second, in samples. Positive number indicates rec
+     * device is running faster than playback device.
+     */
+    pj_int32_t rec_drift_per_sec;
+
+} pjmedia_aud_test_results;
+
+
+/**
+ * Perform audio device testing.
+ */
+PJ_DECL(pj_status_t) pjmedia_aud_test(const pjmedia_aud_dev_param *param,
+				      pjmedia_aud_test_results *result);
+
+/**
+ * @)
+ */
+
+PJ_END_DECL
+
+
+#endif	/* __AUDIOTEST_H__ */
+
+
Index: pjproject/branches/projects/aps-direct/pjmedia/src/pjmedia-audiodev/audiodev.c
===================================================================
--- pjproject/branches/projects/aps-direct/pjmedia/src/pjmedia-audiodev/audiodev.c	(revision 2462)
+++ pjproject/branches/projects/aps-direct/pjmedia/src/pjmedia-audiodev/audiodev.c	(revision 2463)
@@ -30,5 +30,5 @@
  * At driver level, device ID is a 16bit unsigned integer index.
  */
-#define MAKE_DEV_ID(f_id, index)   (((f_id & 0xFFFF) << 16) & (index & 0xFFFF))
+#define MAKE_DEV_ID(f_id, index)   (((f_id & 0xFFFF) << 16) | (index & 0xFFFF))
 #define GET_INDEX(dev_id)	   ((dev_id) & 0xFFFF)
 #define GET_FID(dev_id)		   ((dev_id) >> 16)
@@ -40,23 +40,21 @@
 pjmedia_aud_dev_factory* pjmedia_wmme_factory(pj_pool_factory *pf);
 
-
-/* Array of factories */
-static struct factory
-{
-    pjmedia_aud_dev_factory*   (*create)(pj_pool_factory*);
-    pjmedia_aud_dev_factory	*f;
-
-} factories[] = 
-{
-    /* WMME */
+#define MAX_DRIVERS	16
+
+/* The audio subsystem */
+static struct aud_subsys
+{
+    pj_pool_factory *pf;
+    unsigned	     factory_cnt;
+
+    struct factory
     {
-	&pjmedia_wmme_factory
-    },
-    /* PortAudio: */
-    {
-	&pjmedia_pa_factory
-    },
-};
-static unsigned factory_cnt;
+	pjmedia_aud_dev_factory*   (*create)(pj_pool_factory*);
+	pjmedia_aud_dev_factory	    *f;
+
+    } factories[MAX_DRIVERS];
+
+} aud_subsys;
+
 
 
@@ -67,22 +65,34 @@
     pj_status_t status = PJ_ENOMEM;
 
-    factory_cnt = 0;
-
-    for (i=0; i<PJ_ARRAY_SIZE(factories); ++i) {
-	factories[i].f = (*factories[i].create)(pf);
-	if (!factories[i].f)
-	    continue;
-
-	status = factories[i].f->op->init(factories[i].f);
+    aud_subsys.pf = pf;
+    aud_subsys.factory_cnt = 0;
+
+    aud_subsys.factories[aud_subsys.factory_cnt++].create = &pjmedia_pa_factory;
+    aud_subsys.factories[aud_subsys.factory_cnt++].create = &pjmedia_wmme_factory;
+
+    for (i=0; i<aud_subsys.factory_cnt; ++i) {
+	pjmedia_aud_dev_factory *f;
+
+	f = (*aud_subsys.factories[i].create)(pf);
+	if (!f)
+	    continue;
+
+	status = f->op->init(f);
 	if (status != PJ_SUCCESS) {
-	    factories[i].f->op->destroy(factories[i].f);
-	    factories[i].f = NULL;
+	    f->op->destroy(f);
+	    continue;
 	}
 
-	factories[i].f->internal.id = i;
-	++factory_cnt;
+	aud_subsys.factories[i].f = f;
+	aud_subsys.factories[i].f->internal.id = i;
     }
 
-    return factory_cnt ? PJ_SUCCESS : status;
+    return aud_subsys.factory_cnt ? PJ_SUCCESS : status;
+}
+
+/* API: get the pool factory registered to the audio subsystem. */
+PJ_DEF(pj_pool_factory*) pjmedia_aud_subsys_get_pool_factory(void)
+{
+    return aud_subsys.pf;
 }
 
@@ -92,10 +102,12 @@
     unsigned i;
 
-    for (i=0; i<PJ_ARRAY_SIZE(factories); ++i) {
-	if (!factories[i].f)
-	    continue;
-
-	factories[i].f->op->destroy(factories[i].f);
-	factories[i].f = NULL;
+    for (i=0; i<aud_subsys.factory_cnt; ++i) {
+	pjmedia_aud_dev_factory *f = aud_subsys.factories[i].f;
+
+	if (!f)
+	    continue;
+
+	f->op->destroy(f);
+	aud_subsys.factories[i].f = NULL;
     }
 
@@ -108,9 +120,11 @@
     unsigned i, count = 0;
 
-    for (i=0; i<PJ_ARRAY_SIZE(factories); ++i) {
-	if (!factories[i].f)
-	    continue;
-
-	count += factories[i].f->op->get_dev_count(factories[i].f);
+    for (i=0; i<aud_subsys.factory_cnt; ++i) {
+	pjmedia_aud_dev_factory *f = aud_subsys.factories[i].f;
+
+	if (!f)
+	    continue;
+
+	count += f->op->get_dev_count(f);
     }
 
@@ -124,11 +138,12 @@
     unsigned i, count = 0;
 
-    for (i=0; i<PJ_ARRAY_SIZE(factories) && count < max_count; ++i) {
+    for (i=0; i<aud_subsys.factory_cnt && count < max_count; ++i) {
+	pjmedia_aud_dev_factory *f = aud_subsys.factories[i].f;
 	unsigned j, fcount;
 
-	if (!factories[i].f)
-	    continue;
-
-	fcount = factories[i].f->op->get_dev_count(factories[i].f);
+	if (!f)
+	    continue;
+
+	fcount = f->op->get_dev_count(f);
 	for (j=0; j<fcount && count<max_count; ++j) {
 	    ids[count++] = MAKE_DEV_ID(i, j);
@@ -144,4 +159,5 @@
 					     pjmedia_aud_dev_info *info)
 {
+    pjmedia_aud_dev_factory *f;
     int f_id, index;
 
@@ -152,12 +168,12 @@
     index = GET_INDEX(id);
 
-    if (f_id < 0 || f_id >= PJ_ARRAY_SIZE(factories))
-	return PJMEDIA_EAUD_INVDEV;
-
-    if (factories[f_id].f == NULL)
-	return PJMEDIA_EAUD_INVDEV;
-
-    return factories[f_id].f->op->get_dev_info(factories[f_id].f,
-					       index, info);
+    if (f_id < 0 || f_id >= (int)aud_subsys.factory_cnt)
+	return PJMEDIA_EAUD_INVDEV;
+
+    f = aud_subsys.factories[f_id].f;
+    if (f == NULL)
+	return PJMEDIA_EAUD_INVDEV;
+
+    return f->op->get_dev_info(f, index, info);
 }
 
@@ -168,4 +184,5 @@
 						  pjmedia_aud_dev_param *param)
 {
+    pjmedia_aud_dev_factory *f;
     int f_id, index;
     pj_status_t status;
@@ -177,12 +194,12 @@
     index = GET_INDEX(id);
 
-    if (f_id < 0 || f_id >= PJ_ARRAY_SIZE(factories))
-	return PJMEDIA_EAUD_INVDEV;
-
-    if (factories[f_id].f == NULL)
-	return PJMEDIA_EAUD_INVDEV;
-
-    status = factories[f_id].f->op->default_param(factories[f_id].f,
-					          index, param);
+    if (f_id < 0 || f_id >= (int)aud_subsys.factory_cnt)
+	return PJMEDIA_EAUD_INVDEV;
+
+    f = aud_subsys.factories[f_id].f;
+    if (f == NULL)
+	return PJMEDIA_EAUD_INVDEV;
+
+    status = f->op->default_param(f, index, param);
     if (status != PJ_SUCCESS)
 	return status;
@@ -204,4 +221,5 @@
 					      pjmedia_aud_stream **p_aud_strm)
 {
+    pjmedia_aud_dev_factory *f;
     pjmedia_aud_dev_param param;
     int f_id;
@@ -222,5 +240,5 @@
 	f_id = GET_FID(param.play_id);
 
-    if (f_id < 0 || f_id >= PJ_ARRAY_SIZE(factories))
+    if (f_id < 0 || f_id >= (int)aud_subsys.factory_cnt)
 	return PJMEDIA_EAUD_INVDEV;
     
@@ -229,14 +247,14 @@
     param.play_id = GET_INDEX(param.play_id);
 
-    if (factories[f_id].f == NULL)
-	return PJMEDIA_EAUD_INVDEV;
-
-    status = factories[f_id].f->op->create_stream(factories[f_id].f,
-					          &param, rec_cb, play_cb,
-						  user_data, p_aud_strm);
+    f = aud_subsys.factories[f_id].f;
+    if (f == NULL)
+	return PJMEDIA_EAUD_INVDEV;
+
+    status = f->op->create_stream(f, &param, rec_cb, play_cb,
+				  user_data, p_aud_strm);
     if (status != PJ_SUCCESS)
 	return status;
 
-    (*p_aud_strm)->factory = factories[f_id].f;
+    (*p_aud_strm)->factory = f;
     return PJ_SUCCESS;
 }
Index: pjproject/branches/projects/aps-direct/pjmedia/src/pjmedia-audiodev/audiotest.c
===================================================================
--- pjproject/branches/projects/aps-direct/pjmedia/src/pjmedia-audiodev/audiotest.c	(revision 2463)
+++ pjproject/branches/projects/aps-direct/pjmedia/src/pjmedia-audiodev/audiotest.c	(revision 2463)
@@ -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;
+}
+
Index: pjproject/branches/projects/aps-direct/pjproject.dsw
===================================================================
--- pjproject/branches/projects/aps-direct/pjproject.dsw	(revision 2462)
+++ pjproject/branches/projects/aps-direct/pjproject.dsw	(revision 2463)
@@ -474,4 +474,7 @@
     Project_Dep_Name libsrtp
     End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name pjmedia_audiodev
+    End Project Dependency
 }}}
 
Index: pjproject/branches/projects/aps-direct/pjsip-apps/build/sample_debug.dsp
===================================================================
--- pjproject/branches/projects/aps-direct/pjsip-apps/build/sample_debug.dsp	(revision 2462)
+++ pjproject/branches/projects/aps-direct/pjsip-apps/build/sample_debug.dsp	(revision 2463)
@@ -91,11 +91,4 @@
 
 SOURCE=..\src\samples\debug.c
-
-!IF  "$(CFG)" == "sample_debug - Win32 Release"
-
-!ELSEIF  "$(CFG)" == "sample_debug - Win32 Debug"
-
-!ENDIF 
-
 # End Source File
 # End Group
Index: pjproject/branches/projects/aps-direct/pjsip-apps/build/samples.dsp
===================================================================
--- pjproject/branches/projects/aps-direct/pjsip-apps/build/samples.dsp	(revision 2462)
+++ pjproject/branches/projects/aps-direct/pjsip-apps/build/samples.dsp	(revision 2463)
@@ -91,4 +91,8 @@
 # Begin Source File
 
+SOURCE=..\src\samples\auddemo.c
+# End Source File
+# Begin Source File
+
 SOURCE=..\src\samples\confbench.c
 # End Source File
Index: pjproject/branches/projects/aps-direct/pjsip-apps/src/samples/auddemo.c
===================================================================
--- pjproject/branches/projects/aps-direct/pjsip-apps/src/samples/auddemo.c	(revision 2463)
+++ pjproject/branches/projects/aps-direct/pjsip-apps/src/samples/auddemo.c	(revision 2463)
@@ -0,0 +1,415 @@
+/* $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/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
+
+
+static unsigned dev_count;
+static pjmedia_aud_dev_id dev_id[MAX_DEVICES];
+
+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));
+
+    dev_count = pjmedia_aud_dev_enum(PJ_ARRAY_SIZE(dev_id), dev_id);
+
+    for (i=0; i<dev_count; ++i) {
+	pjmedia_aud_dev_info info;
+
+	status = pjmedia_aud_dev_get_info(dev_id[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];
+
+    text[0] = '\0';
+
+    if (caps & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) {
+	strcat(text, "extfmt ");
+    }
+
+    if (caps & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) {
+	strcat(text, "inlatency ");
+    }
+
+    if (caps & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) {
+	strcat(text, "outlatency ");
+    }
+
+    if (caps & PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING) {
+	strcat(text, "invol ");
+    }
+
+    if (caps & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
+	strcat(text, "outvol ");
+    }
+
+    if (caps & PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_VOLUME) {
+	strcat(text, "insignal ");
+    }
+
+    if (caps & PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_VOLUME) {
+	strcat(text, "outsignal ");
+    }
+
+    if (caps & PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE) {
+	strcat(text, "inroute ");
+    }
+
+    if (caps & PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE) {
+	strcat(text, "outroute ");
+    }
+
+    if (caps & PJMEDIA_AUD_DEV_CAP_EC) {
+	strcat(text, "ec ");
+    }
+
+    if (caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL) {
+	strcat(text, "ectail ");
+    }
+
+    if (caps & PJMEDIA_AUD_DEV_CAP_VAD) {
+	strcat(text, "vad ");
+    }
+
+    if (caps & PJMEDIA_AUD_DEV_CAP_CNG) {
+	strcat(text, "cng ");
+    }
+
+    if (caps & PJMEDIA_AUD_DEV_CAP_PLC) {
+	strcat(text, "plc ");
+    }
+
+    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(dev_id[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", dev_id[index], dev_id[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].fmt_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].bitrate);
+	    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_dev_param param;
+    pjmedia_aud_test_results result;
+    pj_status_t status;
+
+    if (dir & PJMEDIA_DIR_CAPTURE) {
+	status = pjmedia_aud_dev_default_param(dev_id[rec_id], &param);
+    } else {
+	status = pjmedia_aud_dev_default_param(dev_id[play_id], &param);
+    }
+
+    if (status != PJ_SUCCESS) {
+	app_perror("pjmedia_aud_dev_default_param()", status);
+	return;
+    }
+
+    param.dir = dir;
+    param.rec_id = dev_id[rec_id];
+    param.play_id = dev_id[play_id];
+    param.clock_rate = clock_rate;
+    param.channel_count = chnum;
+    param.samples_per_frame = clock_rate * chnum * ptime / 1000;
+
+    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: max interval=%u, burst=%u",
+		      "Recording result",
+		      result.rec.max_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: max interval=%u, burst=%u",
+		      "Playback result",
+		      result.play.max_interval,
+		      result.play.max_burst));
+	}
+    }
+
+    if (dir==PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+	if (result.rec_drift_per_sec) {
+	    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 void print_menu(void)
+{
+    puts("");
+    puts("Audio demo menu:");
+    puts("-------------------------------");
+    puts("  l                        List 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("  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 | PJ_LOG_HAS_COLOR);
+
+    /* 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 '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 '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;
+}
+
+
Index: pjproject/branches/projects/aps-direct/pjsip-apps/src/samples/debug.c
===================================================================
--- pjproject/branches/projects/aps-direct/pjsip-apps/src/samples/debug.c	(revision 2462)
+++ pjproject/branches/projects/aps-direct/pjsip-apps/src/samples/debug.c	(revision 2463)
@@ -29,4 +29,4 @@
  *  #include "playfile.c"
  */
-#include "aectest.c"
+#include "auddemo.c"
 
