summaryrefslogtreecommitdiff
path: root/pjmedia/src/pjmedia-audiodev/pa_dev.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjmedia/src/pjmedia-audiodev/pa_dev.c')
-rw-r--r--pjmedia/src/pjmedia-audiodev/pa_dev.c1263
1 files changed, 1263 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia-audiodev/pa_dev.c b/pjmedia/src/pjmedia-audiodev/pa_dev.c
new file mode 100644
index 00000000..5644e1f1
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/pa_dev.c
@@ -0,0 +1,1263 @@
+/* $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_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/string.h>
+#include <portaudio.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+
+
+#define THIS_FILE "pa_dev.c"
+#define DRIVER_NAME "PA"
+
+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 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
+};
+
+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())
+ {
+ 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())
+ {
+ 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;
+}
+
+/* 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);
+
+
+/*
+ * 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);
+
+ PaUtil_SetDebugPrintFunction(&pa_log_cb);
+
+ 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: 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 */
+