* #27232: jni: added pjproject checkout as regular git content

We will remove it once the next release of pjsip (with Android support)
comes out and is merged into SFLphone.
diff --git a/jni/pjproject-android/.svn/pristine/ff/ff069ce589bbf36226cf910275fc57b96f4f5857.svn-base b/jni/pjproject-android/.svn/pristine/ff/ff069ce589bbf36226cf910275fc57b96f4f5857.svn-base
new file mode 100644
index 0000000..bfaa5b5
--- /dev/null
+++ b/jni/pjproject-android/.svn/pristine/ff/ff069ce589bbf36226cf910275fc57b96f4f5857.svn-base
@@ -0,0 +1,1284 @@
+/* $Id$ */
+/* 
+ * 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_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/string.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+
+#include <portaudio.h>
+
+#define THIS_FILE	"pa_dev.c"
+#define DRIVER_NAME	"PA"
+
+/* Enable call to PaUtil_SetDebugPrintFunction, but this is not always
+ * available across all PortAudio versions (?)
+ */
+/*#define USE_PA_DEBUG_PRINT */
+
+struct pa_aud_factory
+{
+    pjmedia_aud_dev_factory	 base;
+    pj_pool_factory		*pf;
+    pj_pool_t			*pool;
+};
+
+
+/* 
+ * Sound stream descriptor.
+ * This struct may be used for both unidirectional or bidirectional sound
+ * streams.
+ */
+struct pa_aud_stream
+{
+    pjmedia_aud_stream	 base;
+
+    pj_pool_t		*pool;
+    pj_str_t		 name;
+    pjmedia_dir		 dir;
+    int			 play_id;
+    int			 rec_id;
+    int			 bytes_per_sample;
+    pj_uint32_t		 samples_per_sec;
+    unsigned		 samples_per_frame;
+    int			 channel_count;
+
+    PaStream		*rec_strm;
+    PaStream		*play_strm;
+
+    void		*user_data;
+    pjmedia_aud_rec_cb   rec_cb;
+    pjmedia_aud_play_cb  play_cb;
+
+    pj_timestamp	 play_timestamp;
+    pj_timestamp	 rec_timestamp;
+    pj_uint32_t		 underflow;
+    pj_uint32_t		 overflow;
+
+    pj_bool_t		 quit_flag;
+
+    pj_bool_t		 rec_thread_exited;
+    pj_bool_t		 rec_thread_initialized;
+    pj_thread_desc	 rec_thread_desc;
+    pj_thread_t		*rec_thread;
+
+    pj_bool_t		 play_thread_exited;
+    pj_bool_t		 play_thread_initialized;
+    pj_thread_desc	 play_thread_desc;
+    pj_thread_t		*play_thread;
+
+    /* Sometime the record callback does not return framesize as configured
+     * (e.g: in OSS), while this module must guarantee returning framesize
+     * as configured in the creation settings. In this case, we need a buffer 
+     * for the recorded samples.
+     */
+    pj_int16_t		*rec_buf;
+    unsigned		 rec_buf_count;
+
+    /* Sometime the player callback does not request framesize as configured
+     * (e.g: in Linux OSS) while sound device will always get samples from 
+     * the other component as many as configured samples_per_frame. 
+     */
+    pj_int16_t		*play_buf;
+    unsigned		 play_buf_count;
+};
+
+
+/* Factory prototypes */
+static pj_status_t  pa_init(pjmedia_aud_dev_factory *f);
+static pj_status_t  pa_destroy(pjmedia_aud_dev_factory *f);
+static pj_status_t  pa_refresh(pjmedia_aud_dev_factory *f);
+static unsigned	    pa_get_dev_count(pjmedia_aud_dev_factory *f);
+static pj_status_t  pa_get_dev_info(pjmedia_aud_dev_factory *f, 
+				    unsigned index,
+				    pjmedia_aud_dev_info *info);
+static pj_status_t  pa_default_param(pjmedia_aud_dev_factory *f,
+				     unsigned index,
+				     pjmedia_aud_param *param);
+static pj_status_t  pa_create_stream(pjmedia_aud_dev_factory *f,
+				     const pjmedia_aud_param *param,
+				     pjmedia_aud_rec_cb rec_cb,
+				     pjmedia_aud_play_cb play_cb,
+				     void *user_data,
+				     pjmedia_aud_stream **p_aud_strm);
+
+/* Stream prototypes */
+static pj_status_t strm_get_param(pjmedia_aud_stream *strm,
+				  pjmedia_aud_param *param);
+static pj_status_t strm_get_cap(pjmedia_aud_stream *strm,
+	 		        pjmedia_aud_dev_cap cap,
+			        void *value);
+static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
+			        pjmedia_aud_dev_cap cap,
+			        const void *value);
+static pj_status_t strm_start(pjmedia_aud_stream *strm);
+static pj_status_t strm_stop(pjmedia_aud_stream *strm);
+static pj_status_t strm_destroy(pjmedia_aud_stream *strm);
+
+
+static pjmedia_aud_dev_factory_op pa_op = 
+{
+    &pa_init,
+    &pa_destroy,
+    &pa_get_dev_count,
+    &pa_get_dev_info,
+    &pa_default_param,
+    &pa_create_stream,
+    &pa_refresh    
+};
+
+static pjmedia_aud_stream_op pa_strm_op = 
+{
+    &strm_get_param,
+    &strm_get_cap,
+    &strm_set_cap,
+    &strm_start,
+    &strm_stop,
+    &strm_destroy
+};
+
+
+
+static int PaRecorderCallback(const void *input, 
+			      void *output,
+			      unsigned long frameCount,
+			      const PaStreamCallbackTimeInfo* timeInfo,
+			      PaStreamCallbackFlags statusFlags,
+			      void *userData )
+{
+    struct pa_aud_stream *stream = (struct pa_aud_stream*) userData;
+    pj_status_t status = 0;
+    unsigned nsamples;
+
+    PJ_UNUSED_ARG(output);
+    PJ_UNUSED_ARG(timeInfo);
+
+    if (stream->quit_flag)
+	goto on_break;
+
+    if (input == NULL)
+	return paContinue;
+
+    /* Known cases of callback's thread:
+     * - The thread may be changed in the middle of a session, e.g: in MacOS 
+     *   it happens when plugging/unplugging headphone.
+     * - The same thread may be reused in consecutive sessions. The first
+     *   session will leave TLS set, but release the TLS data address,
+     *   so the second session must re-register the callback's thread.
+     */
+    if (stream->rec_thread_initialized == 0 || !pj_thread_is_registered()) 
+    {
+	pj_bzero(stream->rec_thread_desc, sizeof(pj_thread_desc));
+	status = pj_thread_register("pa_rec", stream->rec_thread_desc, 
+				    &stream->rec_thread);
+	stream->rec_thread_initialized = 1;
+	PJ_LOG(5,(THIS_FILE, "Recorder thread started"));
+    }
+
+    if (statusFlags & paInputUnderflow)
+	++stream->underflow;
+    if (statusFlags & paInputOverflow)
+	++stream->overflow;
+
+    /* Calculate number of samples we've got */
+    nsamples = frameCount * stream->channel_count + stream->rec_buf_count;
+
+    if (nsamples >= stream->samples_per_frame) 
+    {
+	/* If buffer is not empty, combine the buffer with the just incoming
+	 * samples, then call put_frame.
+	 */
+	if (stream->rec_buf_count) {
+	    unsigned chunk_count = 0;
+	    pjmedia_frame frame;
+	
+	    chunk_count = stream->samples_per_frame - stream->rec_buf_count;
+	    pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count,
+				 (pj_int16_t*)input, chunk_count);
+
+	    frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+	    frame.buf = (void*) stream->rec_buf;
+	    frame.size = stream->samples_per_frame * stream->bytes_per_sample;
+	    frame.timestamp.u64 = stream->rec_timestamp.u64;
+	    frame.bit_info = 0;
+
+	    status = (*stream->rec_cb)(stream->user_data, &frame);
+
+	    input = (pj_int16_t*) input + chunk_count;
+	    nsamples -= stream->samples_per_frame;
+	    stream->rec_buf_count = 0;
+	    stream->rec_timestamp.u64 += stream->samples_per_frame /
+					 stream->channel_count;
+	}
+
+	/* Give all frames we have */
+	while (nsamples >= stream->samples_per_frame && status == 0) {
+	    pjmedia_frame frame;
+
+	    frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+	    frame.buf = (void*) input;
+	    frame.size = stream->samples_per_frame * stream->bytes_per_sample;
+	    frame.timestamp.u64 = stream->rec_timestamp.u64;
+	    frame.bit_info = 0;
+
+	    status = (*stream->rec_cb)(stream->user_data, &frame);
+
+	    input = (pj_int16_t*) input + stream->samples_per_frame;
+	    nsamples -= stream->samples_per_frame;
+	    stream->rec_timestamp.u64 += stream->samples_per_frame /
+					 stream->channel_count;
+	}
+
+	/* Store the remaining samples into the buffer */
+	if (nsamples && status == 0) {
+	    stream->rec_buf_count = nsamples;
+	    pjmedia_copy_samples(stream->rec_buf, (pj_int16_t*)input, 
+			         nsamples);
+	}
+
+    } else {
+	/* Not enough samples, let's just store them in the buffer */
+	pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count,
+			     (pj_int16_t*)input, 
+			     frameCount * stream->channel_count);
+	stream->rec_buf_count += frameCount * stream->channel_count;
+    }
+
+    if (status==0) 
+	return paContinue;
+
+on_break:
+    stream->rec_thread_exited = 1;
+    return paAbort;
+}
+
+static int PaPlayerCallback( const void *input, 
+			     void *output,
+			     unsigned long frameCount,
+			     const PaStreamCallbackTimeInfo* timeInfo,
+			     PaStreamCallbackFlags statusFlags,
+			     void *userData )
+{
+    struct pa_aud_stream *stream = (struct pa_aud_stream*) userData;
+    pj_status_t status = 0;
+    unsigned nsamples_req = frameCount * stream->channel_count;
+
+    PJ_UNUSED_ARG(input);
+    PJ_UNUSED_ARG(timeInfo);
+
+    if (stream->quit_flag)
+	goto on_break;
+
+    if (output == NULL)
+	return paContinue;
+
+    /* Known cases of callback's thread:
+     * - The thread may be changed in the middle of a session, e.g: in MacOS 
+     *   it happens when plugging/unplugging headphone.
+     * - The same thread may be reused in consecutive sessions. The first
+     *   session will leave TLS set, but release the TLS data address,
+     *   so the second session must re-register the callback's thread.
+     */
+    if (stream->play_thread_initialized == 0 || !pj_thread_is_registered()) 
+    {
+	pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc));
+	status = pj_thread_register("portaudio", stream->play_thread_desc,
+				    &stream->play_thread);
+	stream->play_thread_initialized = 1;
+	PJ_LOG(5,(THIS_FILE, "Player thread started"));
+    }
+
+    if (statusFlags & paOutputUnderflow)
+	++stream->underflow;
+    if (statusFlags & paOutputOverflow)
+	++stream->overflow;
+
+
+    /* Check if any buffered samples */
+    if (stream->play_buf_count) {
+	/* samples buffered >= requested by sound device */
+	if (stream->play_buf_count >= nsamples_req) {
+	    pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, 
+				 nsamples_req);
+	    stream->play_buf_count -= nsamples_req;
+	    pjmedia_move_samples(stream->play_buf, 
+				 stream->play_buf + nsamples_req,
+				 stream->play_buf_count);
+	    nsamples_req = 0;
+	    
+	    return paContinue;
+	}
+
+	/* samples buffered < requested by sound device */
+	pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, 
+			     stream->play_buf_count);
+	nsamples_req -= stream->play_buf_count;
+	output = (pj_int16_t*)output + stream->play_buf_count;
+	stream->play_buf_count = 0;
+    }
+
+    /* Fill output buffer as requested */
+    while (nsamples_req && status == 0) {
+	if (nsamples_req >= stream->samples_per_frame) {
+	    pjmedia_frame frame;
+
+	    frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+	    frame.buf = output;
+	    frame.size = stream->samples_per_frame *  stream->bytes_per_sample;
+	    frame.timestamp.u64 = stream->play_timestamp.u64;
+	    frame.bit_info = 0;
+
+	    status = (*stream->play_cb)(stream->user_data, &frame);
+	    if (status != PJ_SUCCESS)
+		goto on_break;
+
+	    if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+		pj_bzero(frame.buf, frame.size);
+
+	    nsamples_req -= stream->samples_per_frame;
+	    output = (pj_int16_t*)output + stream->samples_per_frame;
+	} else {
+	    pjmedia_frame frame;
+
+	    frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
+	    frame.buf = stream->play_buf;
+	    frame.size = stream->samples_per_frame *  stream->bytes_per_sample;
+	    frame.timestamp.u64 = stream->play_timestamp.u64;
+	    frame.bit_info = 0;
+
+	    status = (*stream->play_cb)(stream->user_data, &frame);
+	    if (status != PJ_SUCCESS)
+		goto on_break;
+
+	    if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
+		pj_bzero(frame.buf, frame.size);
+
+	    pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, 
+				 nsamples_req);
+	    stream->play_buf_count = stream->samples_per_frame - nsamples_req;
+	    pjmedia_move_samples(stream->play_buf, 
+				 stream->play_buf+nsamples_req,
+				 stream->play_buf_count);
+	    nsamples_req = 0;
+	}
+
+	stream->play_timestamp.u64 += stream->samples_per_frame /
+				      stream->channel_count;
+    }
+    
+    if (status==0) 
+	return paContinue;
+
+on_break:
+    stream->play_thread_exited = 1;
+    return paAbort;
+}
+
+
+static int PaRecorderPlayerCallback( const void *input, 
+				     void *output,
+				     unsigned long frameCount,
+				     const PaStreamCallbackTimeInfo* timeInfo,
+				     PaStreamCallbackFlags statusFlags,
+				     void *userData )
+{
+    int rc;
+
+    rc = PaRecorderCallback(input, output, frameCount, timeInfo,
+			    statusFlags, userData);
+    if (rc != paContinue)
+	return rc;
+
+    rc = PaPlayerCallback(input, output, frameCount, timeInfo,
+			  statusFlags, userData);
+    return rc;
+}
+
+#ifdef USE_PA_DEBUG_PRINT
+/* Logging callback from PA */
+static void pa_log_cb(const char *log)
+{
+    PJ_LOG(5,(THIS_FILE, "PA message: %s", log));
+}
+
+/* We should include pa_debugprint.h for this, but the header
+ * is not available publicly. :(
+ */
+typedef void (*PaUtilLogCallback ) (const char *log);
+void PaUtil_SetDebugPrintFunction(PaUtilLogCallback  cb);
+#endif
+
+
+/*
+ * Init PortAudio audio driver.
+ */
+pjmedia_aud_dev_factory* pjmedia_pa_factory(pj_pool_factory *pf)
+{
+    struct pa_aud_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "portaudio", 64, 64, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, struct pa_aud_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &pa_op;
+
+    return &f->base;
+}
+
+
+/* API: Init factory */
+static pj_status_t pa_init(pjmedia_aud_dev_factory *f)
+{
+    int err;
+
+    PJ_UNUSED_ARG(f);
+
+#ifdef USE_PA_DEBUG_PRINT
+    PaUtil_SetDebugPrintFunction(&pa_log_cb);
+#endif
+
+    err = Pa_Initialize();
+
+    PJ_LOG(4,(THIS_FILE, 
+	      "PortAudio sound library initialized, status=%d", err));
+    PJ_LOG(4,(THIS_FILE, "PortAudio host api count=%d",
+			 Pa_GetHostApiCount()));
+    PJ_LOG(4,(THIS_FILE, "Sound device count=%d",
+			 pa_get_dev_count(f)));
+
+    return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
+
+/* API: Destroy factory */
+static pj_status_t pa_destroy(pjmedia_aud_dev_factory *f)
+{
+    struct pa_aud_factory *pa = (struct pa_aud_factory*)f;
+    pj_pool_t *pool;
+    int err;
+
+    PJ_LOG(4,(THIS_FILE, "PortAudio sound library shutting down.."));
+
+    err = Pa_Terminate();
+
+    pool = pa->pool;
+    pa->pool = NULL;
+    pj_pool_release(pool);
+    
+    return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
+
+/* API: Refresh the device list. */
+static pj_status_t pa_refresh(pjmedia_aud_dev_factory *f)
+{
+    PJ_UNUSED_ARG(f);
+    return PJ_ENOTSUP;
+}
+
+
+/* API: Get device count. */
+static unsigned	pa_get_dev_count(pjmedia_aud_dev_factory *f)
+{
+    int count = Pa_GetDeviceCount();
+    PJ_UNUSED_ARG(f);
+    return count < 0 ? 0 : count;
+}
+
+
+/* API: Get device info. */
+static pj_status_t  pa_get_dev_info(pjmedia_aud_dev_factory *f, 
+				    unsigned index,
+				    pjmedia_aud_dev_info *info)
+{
+    const PaDeviceInfo *pa_info;
+
+    PJ_UNUSED_ARG(f);
+
+    pa_info = Pa_GetDeviceInfo(index);
+    if (!pa_info)
+	return PJMEDIA_EAUD_INVDEV;
+
+    pj_bzero(info, sizeof(*info));
+    strncpy(info->name, pa_info->name, sizeof(info->name));
+    info->name[sizeof(info->name)-1] = '\0';
+    info->input_count = pa_info->maxInputChannels;
+    info->output_count = pa_info->maxOutputChannels;
+    info->default_samples_per_sec = (unsigned)pa_info->defaultSampleRate;
+    strncpy(info->driver, DRIVER_NAME, sizeof(info->driver));
+    info->driver[sizeof(info->driver)-1] = '\0';
+    info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+		 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: fill in with default parameter. */
+static pj_status_t  pa_default_param(pjmedia_aud_dev_factory *f,
+				     unsigned index,
+				     pjmedia_aud_param *param)
+{
+    pjmedia_aud_dev_info adi;
+    pj_status_t status;
+
+    PJ_UNUSED_ARG(f);
+
+    status = pa_get_dev_info(f, index, &adi);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    pj_bzero(param, sizeof(*param));
+    if (adi.input_count && adi.output_count) {
+	param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+	param->rec_id = index;
+	param->play_id = index;
+    } else if (adi.input_count) {
+	param->dir = PJMEDIA_DIR_CAPTURE;
+	param->rec_id = index;
+	param->play_id = PJMEDIA_AUD_INVALID_DEV;
+    } else if (adi.output_count) {
+	param->dir = PJMEDIA_DIR_PLAYBACK;
+	param->play_id = index;
+	param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+    } else {
+	return PJMEDIA_EAUD_INVDEV;
+    }
+
+    param->clock_rate = adi.default_samples_per_sec;
+    param->channel_count = 1;
+    param->samples_per_frame = adi.default_samples_per_sec * 20 / 1000;
+    param->bits_per_sample = 16;
+    param->flags = adi.caps;
+    param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+    param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+
+    return PJ_SUCCESS;
+}
+
+
+/* Internal: Get PortAudio default input device ID */
+static int pa_get_default_input_dev(int channel_count)
+{
+    int i, count;
+
+    /* Special for Windows - try to use the DirectSound implementation
+     * first since it provides better latency.
+     */
+#if PJMEDIA_PREFER_DIRECT_SOUND
+    if (Pa_HostApiTypeIdToHostApiIndex(paDirectSound) >= 0) {
+	const PaHostApiInfo *pHI;
+	int index = Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
+	pHI = Pa_GetHostApiInfo(index);
+	if (pHI) {
+	    const PaDeviceInfo *paDevInfo = NULL;
+	    paDevInfo = Pa_GetDeviceInfo(pHI->defaultInputDevice);
+	    if (paDevInfo && paDevInfo->maxInputChannels >= channel_count)
+		return pHI->defaultInputDevice;
+	}
+    }
+#endif
+
+    /* Enumerate the host api's for the default devices, and return
+     * the device with suitable channels.
+     */
+    count = Pa_GetHostApiCount();
+    for (i=0; i < count; ++i) {
+	const PaHostApiInfo *pHAInfo;
+
+	pHAInfo = Pa_GetHostApiInfo(i);
+	if (!pHAInfo)
+	    continue;
+
+	if (pHAInfo->defaultInputDevice >= 0) {
+	    const PaDeviceInfo *paDevInfo;
+
+	    paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultInputDevice);
+
+	    if (paDevInfo->maxInputChannels >= channel_count)
+		return pHAInfo->defaultInputDevice;
+	}
+    }
+
+    /* If still no device is found, enumerate all devices */
+    count = Pa_GetDeviceCount();
+    for (i=0; i<count; ++i) {
+	const PaDeviceInfo *paDevInfo;
+
+	paDevInfo = Pa_GetDeviceInfo(i);
+	if (paDevInfo->maxInputChannels >= channel_count)
+	    return i;
+    }
+    
+    return -1;
+}
+
+/* Internal: Get PortAudio default output device ID */
+static int pa_get_default_output_dev(int channel_count)
+{
+    int i, count;
+
+    /* Special for Windows - try to use the DirectSound implementation
+     * first since it provides better latency.
+     */
+#if PJMEDIA_PREFER_DIRECT_SOUND
+    if (Pa_HostApiTypeIdToHostApiIndex(paDirectSound) >= 0) {
+	const PaHostApiInfo *pHI;
+	int index = Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
+	pHI = Pa_GetHostApiInfo(index);
+	if (pHI) {
+	    const PaDeviceInfo *paDevInfo = NULL;
+	    paDevInfo = Pa_GetDeviceInfo(pHI->defaultOutputDevice);
+	    if (paDevInfo && paDevInfo->maxOutputChannels >= channel_count)
+		return pHI->defaultOutputDevice;
+	}
+    }
+#endif
+
+    /* Enumerate the host api's for the default devices, and return
+     * the device with suitable channels.
+     */
+    count = Pa_GetHostApiCount();
+    for (i=0; i < count; ++i) {
+	const PaHostApiInfo *pHAInfo;
+
+	pHAInfo = Pa_GetHostApiInfo(i);
+	if (!pHAInfo)
+	    continue;
+
+	if (pHAInfo->defaultOutputDevice >= 0) {
+	    const PaDeviceInfo *paDevInfo;
+
+	    paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultOutputDevice);
+
+	    if (paDevInfo->maxOutputChannels >= channel_count)
+		return pHAInfo->defaultOutputDevice;
+	}
+    }
+
+    /* If still no device is found, enumerate all devices */
+    count = Pa_GetDeviceCount();
+    for (i=0; i<count; ++i) {
+	const PaDeviceInfo *paDevInfo;
+
+	paDevInfo = Pa_GetDeviceInfo(i);
+	if (paDevInfo->maxOutputChannels >= channel_count)
+	    return i;
+    }
+
+    return -1;
+}
+
+
+/* Internal: create capture/recorder stream */
+static pj_status_t create_rec_stream( struct pa_aud_factory *pa,
+				      const pjmedia_aud_param *param,
+				      pjmedia_aud_rec_cb rec_cb,
+				      void *user_data,
+				      pjmedia_aud_stream **p_snd_strm)
+{
+    pj_pool_t *pool;
+    pjmedia_aud_dev_index rec_id;
+    struct pa_aud_stream *stream;
+    PaStreamParameters inputParam;
+    int sampleFormat;
+    const PaDeviceInfo *paDevInfo = NULL;
+    const PaHostApiInfo *paHostApiInfo = NULL;
+    unsigned paFrames, paRate, paLatency;
+    const PaStreamInfo *paSI;
+    PaError err;
+
+    PJ_ASSERT_RETURN(rec_cb && p_snd_strm, PJ_EINVAL);
+
+    rec_id = param->rec_id;
+    if (rec_id < 0) {
+	rec_id = pa_get_default_input_dev(param->channel_count);
+	if (rec_id < 0) {
+	    /* No such device. */
+	    return PJMEDIA_EAUD_NODEFDEV;
+	}
+    }
+
+    paDevInfo = Pa_GetDeviceInfo(rec_id);
+    if (!paDevInfo) {
+	/* Assumed it is "No such device" error. */
+	return PJMEDIA_EAUD_INVDEV;
+    }
+
+    if (param->bits_per_sample == 8)
+	sampleFormat = paUInt8;
+    else if (param->bits_per_sample == 16)
+	sampleFormat = paInt16;
+    else if (param->bits_per_sample == 32)
+	sampleFormat = paInt32;
+    else
+	return PJMEDIA_EAUD_SAMPFORMAT;
+    
+    pool = pj_pool_create(pa->pf, "recstrm", 1024, 1024, NULL);
+    if (!pool)
+	return PJ_ENOMEM;
+
+    stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
+    stream->pool = pool;
+    pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
+    stream->dir = PJMEDIA_DIR_CAPTURE;
+    stream->rec_id = rec_id;
+    stream->play_id = -1;
+    stream->user_data = user_data;
+    stream->samples_per_sec = param->clock_rate;
+    stream->samples_per_frame = param->samples_per_frame;
+    stream->bytes_per_sample = param->bits_per_sample / 8;
+    stream->channel_count = param->channel_count;
+    stream->rec_cb = rec_cb;
+
+    stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool, 
+		      stream->samples_per_frame * stream->bytes_per_sample);
+    stream->rec_buf_count = 0;
+
+    pj_bzero(&inputParam, sizeof(inputParam));
+    inputParam.device = rec_id;
+    inputParam.channelCount = param->channel_count;
+    inputParam.hostApiSpecificStreamInfo = NULL;
+    inputParam.sampleFormat = sampleFormat;
+    if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
+	inputParam.suggestedLatency = param->input_latency_ms / 1000.0;
+    else
+	inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0;
+
+    paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
+
+    /* Frames in PortAudio is number of samples in a single channel */
+    paFrames = param->samples_per_frame / param->channel_count;
+
+    err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
+			 param->clock_rate, paFrames, 
+			 paClipOff, &PaRecorderCallback, stream );
+    if (err != paNoError) {
+	pj_pool_release(pool);
+	return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
+    }
+
+    paSI = Pa_GetStreamInfo(stream->rec_strm);
+    paRate = (unsigned)paSI->sampleRate;
+    paLatency = (unsigned)(paSI->inputLatency * 1000);
+
+    PJ_LOG(5,(THIS_FILE, "Opened device %s (%s) for recording, sample "
+			 "rate=%d, ch=%d, "
+			 "bits=%d, %d samples per frame, latency=%d ms",
+			 paDevInfo->name, paHostApiInfo->name,
+			 paRate, param->channel_count,
+			 param->bits_per_sample, param->samples_per_frame,
+			 paLatency));
+
+    *p_snd_strm = &stream->base;
+    return PJ_SUCCESS;
+}
+
+
+/* Internal: create playback stream */
+static pj_status_t create_play_stream(struct pa_aud_factory *pa,
+				      const pjmedia_aud_param *param,
+				      pjmedia_aud_play_cb play_cb,
+				      void *user_data,
+				      pjmedia_aud_stream **p_snd_strm)
+{
+    pj_pool_t *pool;
+    pjmedia_aud_dev_index play_id;
+    struct pa_aud_stream *stream;
+    PaStreamParameters outputParam;
+    int sampleFormat;
+    const PaDeviceInfo *paDevInfo = NULL;
+    const PaHostApiInfo *paHostApiInfo = NULL;
+    const PaStreamInfo *paSI;
+    unsigned paFrames, paRate, paLatency;
+    PaError err;
+
+    PJ_ASSERT_RETURN(play_cb && p_snd_strm, PJ_EINVAL);
+
+    play_id = param->play_id;
+    if (play_id < 0) {
+	play_id = pa_get_default_output_dev(param->channel_count);
+	if (play_id < 0) {
+	    /* No such device. */
+	    return PJMEDIA_EAUD_NODEFDEV;
+	}
+    } 
+
+    paDevInfo = Pa_GetDeviceInfo(play_id);
+    if (!paDevInfo) {
+	/* Assumed it is "No such device" error. */
+	return PJMEDIA_EAUD_INVDEV;
+    }
+
+    if (param->bits_per_sample == 8)
+	sampleFormat = paUInt8;
+    else if (param->bits_per_sample == 16)
+	sampleFormat = paInt16;
+    else if (param->bits_per_sample == 32)
+	sampleFormat = paInt32;
+    else
+	return PJMEDIA_EAUD_SAMPFORMAT;
+    
+    pool = pj_pool_create(pa->pf, "playstrm", 1024, 1024, NULL);
+    if (!pool)
+	return PJ_ENOMEM;
+
+    stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
+    stream->pool = pool;
+    pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
+    stream->dir = PJMEDIA_DIR_PLAYBACK;
+    stream->play_id = play_id;
+    stream->rec_id = -1;
+    stream->user_data = user_data;
+    stream->samples_per_sec = param->clock_rate;
+    stream->samples_per_frame = param->samples_per_frame;
+    stream->bytes_per_sample = param->bits_per_sample / 8;
+    stream->channel_count = param->channel_count;
+    stream->play_cb = play_cb;
+
+    stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool, 
+					    stream->samples_per_frame * 
+					    stream->bytes_per_sample);
+    stream->play_buf_count = 0;
+
+    pj_bzero(&outputParam, sizeof(outputParam));
+    outputParam.device = play_id;
+    outputParam.channelCount = param->channel_count;
+    outputParam.hostApiSpecificStreamInfo = NULL;
+    outputParam.sampleFormat = sampleFormat;
+    if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
+	outputParam.suggestedLatency=param->output_latency_ms / 1000.0;
+    else
+	outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0;
+
+    paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
+
+    /* Frames in PortAudio is number of samples in a single channel */
+    paFrames = param->samples_per_frame / param->channel_count;
+
+    err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
+			 param->clock_rate,  paFrames, 
+			 paClipOff, &PaPlayerCallback, stream );
+    if (err != paNoError) {
+	pj_pool_release(pool);
+	return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
+    }
+
+    paSI = Pa_GetStreamInfo(stream->play_strm);
+    paRate = (unsigned)(paSI->sampleRate);
+    paLatency = (unsigned)(paSI->outputLatency * 1000);
+
+    PJ_LOG(5,(THIS_FILE, "Opened device %d: %s(%s) for playing, sample rate=%d"
+			 ", ch=%d, "
+			 "bits=%d, %d samples per frame, latency=%d ms",
+			 play_id, paDevInfo->name, paHostApiInfo->name, 
+			 paRate, param->channel_count,
+		 	 param->bits_per_sample, param->samples_per_frame, 
+			 paLatency));
+
+    *p_snd_strm = &stream->base;
+
+    return PJ_SUCCESS;
+}
+
+
+/* Internal: Create both player and recorder stream */
+static pj_status_t create_bidir_stream(struct pa_aud_factory *pa,
+				       const pjmedia_aud_param *param,
+				       pjmedia_aud_rec_cb rec_cb,
+				       pjmedia_aud_play_cb play_cb,
+				       void *user_data,
+				       pjmedia_aud_stream **p_snd_strm)
+{
+    pj_pool_t *pool;
+    pjmedia_aud_dev_index rec_id, play_id;
+    struct pa_aud_stream *stream;
+    PaStream *paStream = NULL;
+    PaStreamParameters inputParam;
+    PaStreamParameters outputParam;
+    int sampleFormat;
+    const PaDeviceInfo *paRecDevInfo = NULL;
+    const PaDeviceInfo *paPlayDevInfo = NULL;
+    const PaHostApiInfo *paRecHostApiInfo = NULL;
+    const PaHostApiInfo *paPlayHostApiInfo = NULL;
+    const PaStreamInfo *paSI;
+    unsigned paFrames, paRate, paInputLatency, paOutputLatency;
+    PaError err;
+
+    PJ_ASSERT_RETURN(play_cb && rec_cb && p_snd_strm, PJ_EINVAL);
+
+    rec_id = param->rec_id;
+    if (rec_id < 0) {
+	rec_id = pa_get_default_input_dev(param->channel_count);
+	if (rec_id < 0) {
+	    /* No such device. */
+	    return PJMEDIA_EAUD_NODEFDEV;
+	}
+    }
+
+    paRecDevInfo = Pa_GetDeviceInfo(rec_id);
+    if (!paRecDevInfo) {
+	/* Assumed it is "No such device" error. */
+	return PJMEDIA_EAUD_INVDEV;
+    }
+
+    play_id = param->play_id;
+    if (play_id < 0) {
+	play_id = pa_get_default_output_dev(param->channel_count);
+	if (play_id < 0) {
+	    /* No such device. */
+	    return PJMEDIA_EAUD_NODEFDEV;
+	}
+    } 
+
+    paPlayDevInfo = Pa_GetDeviceInfo(play_id);
+    if (!paPlayDevInfo) {
+	/* Assumed it is "No such device" error. */
+	return PJMEDIA_EAUD_INVDEV;
+    }
+
+
+    if (param->bits_per_sample == 8)
+	sampleFormat = paUInt8;
+    else if (param->bits_per_sample == 16)
+	sampleFormat = paInt16;
+    else if (param->bits_per_sample == 32)
+	sampleFormat = paInt32;
+    else
+	return PJMEDIA_EAUD_SAMPFORMAT;
+    
+    pool = pj_pool_create(pa->pf, "sndstream", 1024, 1024, NULL);
+    if (!pool)
+	return PJ_ENOMEM;
+
+    stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
+    stream->pool = pool;
+    pj_strdup2_with_null(pool, &stream->name, paRecDevInfo->name);
+    stream->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+    stream->play_id = play_id;
+    stream->rec_id = rec_id;
+    stream->user_data = user_data;
+    stream->samples_per_sec = param->clock_rate;
+    stream->samples_per_frame = param->samples_per_frame;
+    stream->bytes_per_sample = param->bits_per_sample / 8;
+    stream->channel_count = param->channel_count;
+    stream->rec_cb = rec_cb;
+    stream->play_cb = play_cb;
+
+    stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool, 
+		      stream->samples_per_frame * stream->bytes_per_sample);
+    stream->rec_buf_count = 0;
+
+    stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool, 
+		       stream->samples_per_frame * stream->bytes_per_sample);
+    stream->play_buf_count = 0;
+
+    pj_bzero(&inputParam, sizeof(inputParam));
+    inputParam.device = rec_id;
+    inputParam.channelCount = param->channel_count;
+    inputParam.hostApiSpecificStreamInfo = NULL;
+    inputParam.sampleFormat = sampleFormat;
+    if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
+	inputParam.suggestedLatency = param->input_latency_ms / 1000.0;
+    else
+	inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0;
+
+    paRecHostApiInfo = Pa_GetHostApiInfo(paRecDevInfo->hostApi);
+
+    pj_bzero(&outputParam, sizeof(outputParam));
+    outputParam.device = play_id;
+    outputParam.channelCount = param->channel_count;
+    outputParam.hostApiSpecificStreamInfo = NULL;
+    outputParam.sampleFormat = sampleFormat;
+    if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
+	outputParam.suggestedLatency=param->output_latency_ms / 1000.0;
+    else
+	outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0;
+
+    paPlayHostApiInfo = Pa_GetHostApiInfo(paPlayDevInfo->hostApi);
+
+    /* Frames in PortAudio is number of samples in a single channel */
+    paFrames = param->samples_per_frame / param->channel_count;
+
+    /* If both input and output are on the same device, open a single stream
+     * for both input and output.
+     */
+    if (rec_id == play_id) {
+	err = Pa_OpenStream( &paStream, &inputParam, &outputParam,
+			     param->clock_rate, paFrames, 
+			     paClipOff, &PaRecorderPlayerCallback, stream );
+	if (err == paNoError) {
+	    /* Set play stream and record stream to the same stream */
+	    stream->play_strm = stream->rec_strm = paStream;
+	}
+    } else {
+	err = -1;
+    }
+
+    /* .. otherwise if input and output are on the same device, OR if we're
+     * unable to open a bidirectional stream, then open two separate
+     * input and output stream.
+     */
+    if (paStream == NULL) {
+	/* Open input stream */
+	err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
+			     param->clock_rate, paFrames, 
+			     paClipOff, &PaRecorderCallback, stream );
+	if (err == paNoError) {
+	    /* Open output stream */
+	    err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
+				 param->clock_rate, paFrames, 
+				 paClipOff, &PaPlayerCallback, stream );
+	    if (err != paNoError)
+		Pa_CloseStream(stream->rec_strm);
+	}
+    }
+
+    if (err != paNoError) {
+	pj_pool_release(pool);
+	return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
+    }
+
+    paSI = Pa_GetStreamInfo(stream->rec_strm);
+    paRate = (unsigned)(paSI->sampleRate);
+    paInputLatency = (unsigned)(paSI->inputLatency * 1000);
+    paSI = Pa_GetStreamInfo(stream->play_strm);
+    paOutputLatency = (unsigned)(paSI->outputLatency * 1000);
+
+    PJ_LOG(5,(THIS_FILE, "Opened device %s(%s)/%s(%s) for recording and "
+			 "playback, sample rate=%d, ch=%d, "
+			 "bits=%d, %d samples per frame, input latency=%d ms, "
+			 "output latency=%d ms",
+			 paRecDevInfo->name, paRecHostApiInfo->name,
+			 paPlayDevInfo->name, paPlayHostApiInfo->name,
+			 paRate, param->channel_count,
+			 param->bits_per_sample, param->samples_per_frame,
+			 paInputLatency, paOutputLatency));
+
+    *p_snd_strm = &stream->base;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: create stream */
+static pj_status_t  pa_create_stream(pjmedia_aud_dev_factory *f,
+				     const pjmedia_aud_param *param,
+				     pjmedia_aud_rec_cb rec_cb,
+				     pjmedia_aud_play_cb play_cb,
+				     void *user_data,
+				     pjmedia_aud_stream **p_aud_strm)
+{
+    struct pa_aud_factory *pa = (struct pa_aud_factory*)f;
+    pj_status_t status;
+
+    if (param->dir == PJMEDIA_DIR_CAPTURE) {
+	status = create_rec_stream(pa, param, rec_cb, user_data, p_aud_strm);
+    } else if (param->dir == PJMEDIA_DIR_PLAYBACK) {
+	status = create_play_stream(pa, param, play_cb, user_data, p_aud_strm);
+    } else if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
+	status = create_bidir_stream(pa, param, rec_cb, play_cb, user_data, 
+				     p_aud_strm);
+    } else {
+	return PJ_EINVAL;
+    }
+
+    if (status != PJ_SUCCESS)
+	return status;
+
+    (*p_aud_strm)->op = &pa_strm_op;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Get stream parameters */
+static pj_status_t strm_get_param(pjmedia_aud_stream *s,
+				  pjmedia_aud_param *pi)
+{
+    struct pa_aud_stream *strm = (struct pa_aud_stream*)s;
+    const PaStreamInfo *paPlaySI = NULL, *paRecSI = NULL;
+
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+    PJ_ASSERT_RETURN(strm->play_strm || strm->rec_strm, PJ_EINVALIDOP);
+
+    if (strm->play_strm) {
+	paPlaySI = Pa_GetStreamInfo(strm->play_strm);
+    }
+    if (strm->rec_strm) {
+	paRecSI = Pa_GetStreamInfo(strm->rec_strm);
+    }
+
+    pj_bzero(pi, sizeof(*pi));
+    pi->dir = strm->dir;
+    pi->play_id = strm->play_id;
+    pi->rec_id = strm->rec_id;
+    pi->clock_rate = (unsigned)(paPlaySI ? paPlaySI->sampleRate : 
+				paRecSI->sampleRate);
+    pi->channel_count = strm->channel_count;
+    pi->samples_per_frame = strm->samples_per_frame;
+    pi->bits_per_sample = strm->bytes_per_sample * 8;
+    if (paRecSI) {
+	pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
+	pi->input_latency_ms = (unsigned)(paRecSI ? paRecSI->inputLatency * 
+						    1000 : 0);
+    }
+    if (paPlaySI) {
+	pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+	pi->output_latency_ms = (unsigned)(paPlaySI? paPlaySI->outputLatency * 
+						     1000 : 0);
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: get capability */
+static pj_status_t strm_get_cap(pjmedia_aud_stream *s,
+	 		        pjmedia_aud_dev_cap cap,
+			        void *pval)
+{
+    struct pa_aud_stream *strm = (struct pa_aud_stream*)s;
+
+    PJ_ASSERT_RETURN(strm && pval, PJ_EINVAL);
+
+    if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && strm->rec_strm) {
+	const PaStreamInfo *si = Pa_GetStreamInfo(strm->rec_strm);
+	if (!si)
+	    return PJMEDIA_EAUD_SYSERR;
+
+	*(unsigned*)pval = (unsigned)(si->inputLatency * 1000);
+	return PJ_SUCCESS;
+    } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && strm->play_strm) {
+	const PaStreamInfo *si = Pa_GetStreamInfo(strm->play_strm);
+	if (!si)
+	    return PJMEDIA_EAUD_SYSERR;
+
+	*(unsigned*)pval = (unsigned)(si->outputLatency * 1000);
+	return PJ_SUCCESS;
+    } else {
+	return PJMEDIA_EAUD_INVCAP;
+    }
+}
+
+
+/* API: set capability */
+static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
+			        pjmedia_aud_dev_cap cap,
+			        const void *value)
+{
+    PJ_UNUSED_ARG(strm);
+    PJ_UNUSED_ARG(cap);
+    PJ_UNUSED_ARG(value);
+
+    /* Nothing is supported */
+    return PJMEDIA_EAUD_INVCAP;
+}
+
+
+/* API: start stream. */
+static pj_status_t strm_start(pjmedia_aud_stream *s)
+{
+    struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
+    int err = 0;
+
+    PJ_LOG(5,(THIS_FILE, "Starting %s stream..", stream->name.ptr));
+
+    if (stream->play_strm)
+	err = Pa_StartStream(stream->play_strm);
+
+    if (err==0 && stream->rec_strm && stream->rec_strm != stream->play_strm) {
+	err = Pa_StartStream(stream->rec_strm);
+	if (err != 0)
+	    Pa_StopStream(stream->play_strm);
+    }
+
+    PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
+
+    return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
+
+/* API: stop stream. */
+static pj_status_t strm_stop(pjmedia_aud_stream *s)
+{
+    struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
+    int i, err = 0;
+
+    stream->quit_flag = 1;
+    for (i=0; !stream->rec_thread_exited && i<100; ++i)
+	pj_thread_sleep(10);
+    for (i=0; !stream->play_thread_exited && i<100; ++i)
+	pj_thread_sleep(10);
+
+    pj_thread_sleep(1);
+
+    PJ_LOG(5,(THIS_FILE, "Stopping stream.."));
+
+    if (stream->play_strm)
+	err = Pa_StopStream(stream->play_strm);
+
+    if (stream->rec_strm && stream->rec_strm != stream->play_strm)
+	err = Pa_StopStream(stream->rec_strm);
+
+    stream->play_thread_initialized = 0;
+    stream->rec_thread_initialized = 0;
+
+    PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
+
+    return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
+
+/* API: destroy stream. */
+static pj_status_t strm_destroy(pjmedia_aud_stream *s)
+{
+    struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
+    int i, err = 0;
+
+    stream->quit_flag = 1;
+    for (i=0; !stream->rec_thread_exited && i<100; ++i) {
+	pj_thread_sleep(1);
+    }
+    for (i=0; !stream->play_thread_exited && i<100; ++i) {
+	pj_thread_sleep(1);
+    }
+
+    PJ_LOG(5,(THIS_FILE, "Closing %.*s: %lu underflow, %lu overflow",
+			 (int)stream->name.slen,
+			 stream->name.ptr,
+			 stream->underflow, stream->overflow));
+
+    if (stream->play_strm)
+	err = Pa_CloseStream(stream->play_strm);
+
+    if (stream->rec_strm && stream->rec_strm != stream->play_strm)
+	err = Pa_CloseStream(stream->rec_strm);
+
+    pj_pool_release(stream->pool);
+
+    return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
+}
+
+#endif	/* PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO */
+