/* $Id$ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO #include #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; imaxInputChannels >= 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; imaxOutputChannels >= 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 */