blob: 095f772526f352f1a7565a4a41c0854cf3c05b2d [file] [log] [blame]
Benny Prijono2cd64f82009-02-17 19:57:48 +00001/* $Id$ */
2/*
3 * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
4 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20#include <pjmedia-audiodev/audiodev_imp.h>
Benny Prijono2cd64f82009-02-17 19:57:48 +000021#include <pj/assert.h>
22#include <pj/log.h>
23#include <pj/os.h>
24#include <pj/string.h>
Benny Prijono2cd64f82009-02-17 19:57:48 +000025
Benny Prijono8eeab0b2009-03-04 19:00:28 +000026#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
27
Benny Prijonodebe8e12009-06-03 12:29:35 +000028#include <portaudio.h>
Benny Prijono8eeab0b2009-03-04 19:00:28 +000029
Benny Prijono2cd64f82009-02-17 19:57:48 +000030#define THIS_FILE "pa_dev.c"
Benny Prijono598b01d2009-02-18 13:55:03 +000031#define DRIVER_NAME "PA"
Benny Prijono2cd64f82009-02-17 19:57:48 +000032
Benny Prijonofe0c1272010-01-13 16:28:15 +000033/* Enable call to PaUtil_SetDebugPrintFunction, but this is not always
34 * available across all PortAudio versions (?)
35 */
36/*#define USE_PA_DEBUG_PRINT */
37
Benny Prijono2cd64f82009-02-17 19:57:48 +000038struct pa_aud_factory
39{
40 pjmedia_aud_dev_factory base;
41 pj_pool_factory *pf;
42 pj_pool_t *pool;
43};
44
45
46/*
47 * Sound stream descriptor.
48 * This struct may be used for both unidirectional or bidirectional sound
49 * streams.
50 */
51struct pa_aud_stream
52{
53 pjmedia_aud_stream base;
54
55 pj_pool_t *pool;
56 pj_str_t name;
57 pjmedia_dir dir;
58 int play_id;
59 int rec_id;
60 int bytes_per_sample;
61 pj_uint32_t samples_per_sec;
62 unsigned samples_per_frame;
63 int channel_count;
64
65 PaStream *rec_strm;
66 PaStream *play_strm;
67
68 void *user_data;
69 pjmedia_aud_rec_cb rec_cb;
70 pjmedia_aud_play_cb play_cb;
71
72 pj_timestamp play_timestamp;
73 pj_timestamp rec_timestamp;
74 pj_uint32_t underflow;
75 pj_uint32_t overflow;
76
77 pj_bool_t quit_flag;
78
79 pj_bool_t rec_thread_exited;
80 pj_bool_t rec_thread_initialized;
81 pj_thread_desc rec_thread_desc;
82 pj_thread_t *rec_thread;
83
84 pj_bool_t play_thread_exited;
85 pj_bool_t play_thread_initialized;
86 pj_thread_desc play_thread_desc;
87 pj_thread_t *play_thread;
88
89 /* Sometime the record callback does not return framesize as configured
90 * (e.g: in OSS), while this module must guarantee returning framesize
91 * as configured in the creation settings. In this case, we need a buffer
92 * for the recorded samples.
93 */
94 pj_int16_t *rec_buf;
95 unsigned rec_buf_count;
96
97 /* Sometime the player callback does not request framesize as configured
98 * (e.g: in Linux OSS) while sound device will always get samples from
99 * the other component as many as configured samples_per_frame.
100 */
101 pj_int16_t *play_buf;
102 unsigned play_buf_count;
103};
104
105
106/* Factory prototypes */
107static pj_status_t pa_init(pjmedia_aud_dev_factory *f);
108static pj_status_t pa_destroy(pjmedia_aud_dev_factory *f);
109static unsigned pa_get_dev_count(pjmedia_aud_dev_factory *f);
110static pj_status_t pa_get_dev_info(pjmedia_aud_dev_factory *f,
Benny Prijono598b01d2009-02-18 13:55:03 +0000111 unsigned index,
Benny Prijono2cd64f82009-02-17 19:57:48 +0000112 pjmedia_aud_dev_info *info);
113static pj_status_t pa_default_param(pjmedia_aud_dev_factory *f,
Benny Prijono598b01d2009-02-18 13:55:03 +0000114 unsigned index,
Benny Prijono10454dc2009-02-21 14:21:59 +0000115 pjmedia_aud_param *param);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000116static pj_status_t pa_create_stream(pjmedia_aud_dev_factory *f,
Benny Prijono10454dc2009-02-21 14:21:59 +0000117 const pjmedia_aud_param *param,
Benny Prijono2cd64f82009-02-17 19:57:48 +0000118 pjmedia_aud_rec_cb rec_cb,
119 pjmedia_aud_play_cb play_cb,
120 void *user_data,
121 pjmedia_aud_stream **p_aud_strm);
122
123/* Stream prototypes */
124static pj_status_t strm_get_param(pjmedia_aud_stream *strm,
Benny Prijono10454dc2009-02-21 14:21:59 +0000125 pjmedia_aud_param *param);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000126static pj_status_t strm_get_cap(pjmedia_aud_stream *strm,
127 pjmedia_aud_dev_cap cap,
128 void *value);
129static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
130 pjmedia_aud_dev_cap cap,
131 const void *value);
132static pj_status_t strm_start(pjmedia_aud_stream *strm);
133static pj_status_t strm_stop(pjmedia_aud_stream *strm);
134static pj_status_t strm_destroy(pjmedia_aud_stream *strm);
135
136
137static pjmedia_aud_dev_factory_op pa_op =
138{
139 &pa_init,
140 &pa_destroy,
141 &pa_get_dev_count,
142 &pa_get_dev_info,
143 &pa_default_param,
144 &pa_create_stream
145};
146
147static pjmedia_aud_stream_op pa_strm_op =
148{
149 &strm_get_param,
150 &strm_get_cap,
151 &strm_set_cap,
152 &strm_start,
153 &strm_stop,
154 &strm_destroy
155};
156
157
158
159static int PaRecorderCallback(const void *input,
160 void *output,
161 unsigned long frameCount,
162 const PaStreamCallbackTimeInfo* timeInfo,
163 PaStreamCallbackFlags statusFlags,
164 void *userData )
165{
166 struct pa_aud_stream *stream = (struct pa_aud_stream*) userData;
167 pj_status_t status = 0;
168 unsigned nsamples;
169
170 PJ_UNUSED_ARG(output);
171 PJ_UNUSED_ARG(timeInfo);
172
173 if (stream->quit_flag)
174 goto on_break;
175
176 if (input == NULL)
177 return paContinue;
178
179 /* Known cases of callback's thread:
180 * - The thread may be changed in the middle of a session, e.g: in MacOS
181 * it happens when plugging/unplugging headphone.
182 * - The same thread may be reused in consecutive sessions. The first
183 * session will leave TLS set, but release the TLS data address,
184 * so the second session must re-register the callback's thread.
185 */
186 if (stream->rec_thread_initialized == 0 || !pj_thread_is_registered())
187 {
188 status = pj_thread_register("pa_rec", stream->rec_thread_desc,
189 &stream->rec_thread);
190 stream->rec_thread_initialized = 1;
191 PJ_LOG(5,(THIS_FILE, "Recorder thread started"));
192 }
193
194 if (statusFlags & paInputUnderflow)
195 ++stream->underflow;
196 if (statusFlags & paInputOverflow)
197 ++stream->overflow;
198
Benny Prijono2cd64f82009-02-17 19:57:48 +0000199 /* Calculate number of samples we've got */
200 nsamples = frameCount * stream->channel_count + stream->rec_buf_count;
201
202 if (nsamples >= stream->samples_per_frame)
203 {
204 /* If buffer is not empty, combine the buffer with the just incoming
205 * samples, then call put_frame.
206 */
207 if (stream->rec_buf_count) {
208 unsigned chunk_count = 0;
Benny Prijono598b01d2009-02-18 13:55:03 +0000209 pjmedia_frame frame;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000210
211 chunk_count = stream->samples_per_frame - stream->rec_buf_count;
212 pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count,
213 (pj_int16_t*)input, chunk_count);
Benny Prijono598b01d2009-02-18 13:55:03 +0000214
215 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
216 frame.buf = (void*) stream->rec_buf;
217 frame.size = stream->samples_per_frame * stream->bytes_per_sample;
218 frame.timestamp.u64 = stream->rec_timestamp.u64;
219 frame.bit_info = 0;
220
221 status = (*stream->rec_cb)(stream->user_data, &frame);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000222
223 input = (pj_int16_t*) input + chunk_count;
224 nsamples -= stream->samples_per_frame;
225 stream->rec_buf_count = 0;
Benny Prijono598b01d2009-02-18 13:55:03 +0000226 stream->rec_timestamp.u64 += stream->samples_per_frame /
227 stream->channel_count;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000228 }
229
230 /* Give all frames we have */
231 while (nsamples >= stream->samples_per_frame && status == 0) {
Benny Prijono598b01d2009-02-18 13:55:03 +0000232 pjmedia_frame frame;
233
234 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
235 frame.buf = (void*) input;
236 frame.size = stream->samples_per_frame * stream->bytes_per_sample;
237 frame.timestamp.u64 = stream->rec_timestamp.u64;
238 frame.bit_info = 0;
239
240 status = (*stream->rec_cb)(stream->user_data, &frame);
241
Benny Prijono2cd64f82009-02-17 19:57:48 +0000242 input = (pj_int16_t*) input + stream->samples_per_frame;
243 nsamples -= stream->samples_per_frame;
Benny Prijono598b01d2009-02-18 13:55:03 +0000244 stream->rec_timestamp.u64 += stream->samples_per_frame /
245 stream->channel_count;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000246 }
247
248 /* Store the remaining samples into the buffer */
249 if (nsamples && status == 0) {
250 stream->rec_buf_count = nsamples;
251 pjmedia_copy_samples(stream->rec_buf, (pj_int16_t*)input,
252 nsamples);
253 }
254
255 } else {
256 /* Not enough samples, let's just store them in the buffer */
257 pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count,
258 (pj_int16_t*)input,
259 frameCount * stream->channel_count);
260 stream->rec_buf_count += frameCount * stream->channel_count;
261 }
262
263 if (status==0)
264 return paContinue;
265
266on_break:
267 stream->rec_thread_exited = 1;
268 return paAbort;
269}
270
271static int PaPlayerCallback( const void *input,
272 void *output,
273 unsigned long frameCount,
274 const PaStreamCallbackTimeInfo* timeInfo,
275 PaStreamCallbackFlags statusFlags,
276 void *userData )
277{
278 struct pa_aud_stream *stream = (struct pa_aud_stream*) userData;
279 pj_status_t status = 0;
280 unsigned nsamples_req = frameCount * stream->channel_count;
281
282 PJ_UNUSED_ARG(input);
283 PJ_UNUSED_ARG(timeInfo);
284
285 if (stream->quit_flag)
286 goto on_break;
287
288 if (output == NULL)
289 return paContinue;
290
291 /* Known cases of callback's thread:
292 * - The thread may be changed in the middle of a session, e.g: in MacOS
293 * it happens when plugging/unplugging headphone.
294 * - The same thread may be reused in consecutive sessions. The first
295 * session will leave TLS set, but release the TLS data address,
296 * so the second session must re-register the callback's thread.
297 */
298 if (stream->play_thread_initialized == 0 || !pj_thread_is_registered())
299 {
300 status = pj_thread_register("portaudio", stream->play_thread_desc,
301 &stream->play_thread);
302 stream->play_thread_initialized = 1;
303 PJ_LOG(5,(THIS_FILE, "Player thread started"));
304 }
305
306 if (statusFlags & paOutputUnderflow)
307 ++stream->underflow;
308 if (statusFlags & paOutputOverflow)
309 ++stream->overflow;
310
Benny Prijono2cd64f82009-02-17 19:57:48 +0000311
312 /* Check if any buffered samples */
313 if (stream->play_buf_count) {
314 /* samples buffered >= requested by sound device */
315 if (stream->play_buf_count >= nsamples_req) {
316 pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
317 nsamples_req);
318 stream->play_buf_count -= nsamples_req;
319 pjmedia_move_samples(stream->play_buf,
320 stream->play_buf + nsamples_req,
321 stream->play_buf_count);
322 nsamples_req = 0;
323
324 return paContinue;
325 }
326
327 /* samples buffered < requested by sound device */
328 pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
329 stream->play_buf_count);
330 nsamples_req -= stream->play_buf_count;
331 output = (pj_int16_t*)output + stream->play_buf_count;
332 stream->play_buf_count = 0;
333 }
334
335 /* Fill output buffer as requested */
336 while (nsamples_req && status == 0) {
337 if (nsamples_req >= stream->samples_per_frame) {
Benny Prijono598b01d2009-02-18 13:55:03 +0000338 pjmedia_frame frame;
339
340 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
341 frame.buf = output;
342 frame.size = stream->samples_per_frame * stream->bytes_per_sample;
343 frame.timestamp.u64 = stream->play_timestamp.u64;
344 frame.bit_info = 0;
345
346 status = (*stream->play_cb)(stream->user_data, &frame);
347 if (status != PJ_SUCCESS)
348 goto on_break;
349
350 if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
351 pj_bzero(frame.buf, frame.size);
352
Benny Prijono2cd64f82009-02-17 19:57:48 +0000353 nsamples_req -= stream->samples_per_frame;
354 output = (pj_int16_t*)output + stream->samples_per_frame;
355 } else {
Benny Prijono598b01d2009-02-18 13:55:03 +0000356 pjmedia_frame frame;
357
358 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
359 frame.buf = stream->play_buf;
360 frame.size = stream->samples_per_frame * stream->bytes_per_sample;
361 frame.timestamp.u64 = stream->play_timestamp.u64;
362 frame.bit_info = 0;
363
364 status = (*stream->play_cb)(stream->user_data, &frame);
365 if (status != PJ_SUCCESS)
366 goto on_break;
367
368 if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
369 pj_bzero(frame.buf, frame.size);
370
Benny Prijono2cd64f82009-02-17 19:57:48 +0000371 pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
372 nsamples_req);
373 stream->play_buf_count = stream->samples_per_frame - nsamples_req;
374 pjmedia_move_samples(stream->play_buf,
375 stream->play_buf+nsamples_req,
376 stream->play_buf_count);
377 nsamples_req = 0;
378 }
Benny Prijono598b01d2009-02-18 13:55:03 +0000379
380 stream->play_timestamp.u64 += stream->samples_per_frame /
381 stream->channel_count;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000382 }
383
384 if (status==0)
385 return paContinue;
386
387on_break:
388 stream->play_thread_exited = 1;
389 return paAbort;
390}
391
392
393static int PaRecorderPlayerCallback( const void *input,
394 void *output,
395 unsigned long frameCount,
396 const PaStreamCallbackTimeInfo* timeInfo,
397 PaStreamCallbackFlags statusFlags,
398 void *userData )
399{
400 int rc;
401
402 rc = PaRecorderCallback(input, output, frameCount, timeInfo,
403 statusFlags, userData);
404 if (rc != paContinue)
405 return rc;
406
407 rc = PaPlayerCallback(input, output, frameCount, timeInfo,
408 statusFlags, userData);
409 return rc;
410}
411
Benny Prijonofe0c1272010-01-13 16:28:15 +0000412#ifdef USE_PA_DEBUG_PRINT
Benny Prijono2cd64f82009-02-17 19:57:48 +0000413/* Logging callback from PA */
414static void pa_log_cb(const char *log)
415{
416 PJ_LOG(5,(THIS_FILE, "PA message: %s", log));
417}
418
419/* We should include pa_debugprint.h for this, but the header
420 * is not available publicly. :(
421 */
422typedef void (*PaUtilLogCallback ) (const char *log);
423void PaUtil_SetDebugPrintFunction(PaUtilLogCallback cb);
Benny Prijonofe0c1272010-01-13 16:28:15 +0000424#endif
Benny Prijono2cd64f82009-02-17 19:57:48 +0000425
426
427/*
428 * Init PortAudio audio driver.
429 */
430pjmedia_aud_dev_factory* pjmedia_pa_factory(pj_pool_factory *pf)
431{
432 struct pa_aud_factory *f;
433 pj_pool_t *pool;
434
435 pool = pj_pool_create(pf, "portaudio", 64, 64, NULL);
436 f = PJ_POOL_ZALLOC_T(pool, struct pa_aud_factory);
437 f->pf = pf;
438 f->pool = pool;
439 f->base.op = &pa_op;
440
441 return &f->base;
442}
443
444
445/* API: Init factory */
446static pj_status_t pa_init(pjmedia_aud_dev_factory *f)
447{
448 int err;
449
450 PJ_UNUSED_ARG(f);
451
Benny Prijonofe0c1272010-01-13 16:28:15 +0000452#ifdef USE_PA_DEBUG_PRINT
Benny Prijono2cd64f82009-02-17 19:57:48 +0000453 PaUtil_SetDebugPrintFunction(&pa_log_cb);
Benny Prijonofe0c1272010-01-13 16:28:15 +0000454#endif
Benny Prijono2cd64f82009-02-17 19:57:48 +0000455
456 err = Pa_Initialize();
457
458 PJ_LOG(4,(THIS_FILE,
459 "PortAudio sound library initialized, status=%d", err));
460 PJ_LOG(4,(THIS_FILE, "PortAudio host api count=%d",
461 Pa_GetHostApiCount()));
462 PJ_LOG(4,(THIS_FILE, "Sound device count=%d",
463 pa_get_dev_count(f)));
464
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000465 return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000466}
467
468
469/* API: Destroy factory */
470static pj_status_t pa_destroy(pjmedia_aud_dev_factory *f)
471{
472 struct pa_aud_factory *pa = (struct pa_aud_factory*)f;
473 pj_pool_t *pool;
474 int err;
475
476 PJ_LOG(4,(THIS_FILE, "PortAudio sound library shutting down.."));
477
478 err = Pa_Terminate();
479
480 pool = pa->pool;
481 pa->pool = NULL;
482 pj_pool_release(pool);
483
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000484 return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000485}
486
487
488/* API: Get device count. */
489static unsigned pa_get_dev_count(pjmedia_aud_dev_factory *f)
490{
491 int count = Pa_GetDeviceCount();
492 PJ_UNUSED_ARG(f);
493 return count < 0 ? 0 : count;
494}
495
496
497/* API: Get device info. */
498static pj_status_t pa_get_dev_info(pjmedia_aud_dev_factory *f,
Benny Prijono598b01d2009-02-18 13:55:03 +0000499 unsigned index,
Benny Prijono2cd64f82009-02-17 19:57:48 +0000500 pjmedia_aud_dev_info *info)
501{
502 const PaDeviceInfo *pa_info;
503
504 PJ_UNUSED_ARG(f);
505
Benny Prijono598b01d2009-02-18 13:55:03 +0000506 pa_info = Pa_GetDeviceInfo(index);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000507 if (!pa_info)
508 return PJMEDIA_EAUD_INVDEV;
509
510 pj_bzero(info, sizeof(*info));
511 strncpy(info->name, pa_info->name, sizeof(info->name));
512 info->name[sizeof(info->name)-1] = '\0';
513 info->input_count = pa_info->maxInputChannels;
514 info->output_count = pa_info->maxOutputChannels;
515 info->default_samples_per_sec = (unsigned)pa_info->defaultSampleRate;
516 strncpy(info->driver, DRIVER_NAME, sizeof(info->driver));
517 info->driver[sizeof(info->driver)-1] = '\0';
518 info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
519 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
520
521 return PJ_SUCCESS;
522}
523
524
525/* API: fill in with default parameter. */
526static pj_status_t pa_default_param(pjmedia_aud_dev_factory *f,
Benny Prijono598b01d2009-02-18 13:55:03 +0000527 unsigned index,
Benny Prijono10454dc2009-02-21 14:21:59 +0000528 pjmedia_aud_param *param)
Benny Prijono2cd64f82009-02-17 19:57:48 +0000529{
530 pjmedia_aud_dev_info adi;
531 pj_status_t status;
532
533 PJ_UNUSED_ARG(f);
534
Benny Prijono598b01d2009-02-18 13:55:03 +0000535 status = pa_get_dev_info(f, index, &adi);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000536 if (status != PJ_SUCCESS)
537 return status;
538
539 pj_bzero(param, sizeof(*param));
540 if (adi.input_count && adi.output_count) {
541 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
Benny Prijono598b01d2009-02-18 13:55:03 +0000542 param->rec_id = index;
543 param->play_id = index;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000544 } else if (adi.input_count) {
545 param->dir = PJMEDIA_DIR_CAPTURE;
Benny Prijono598b01d2009-02-18 13:55:03 +0000546 param->rec_id = index;
Benny Prijono96e74f32009-02-22 12:00:12 +0000547 param->play_id = PJMEDIA_AUD_INVALID_DEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000548 } else if (adi.output_count) {
549 param->dir = PJMEDIA_DIR_PLAYBACK;
Benny Prijono598b01d2009-02-18 13:55:03 +0000550 param->play_id = index;
Benny Prijono96e74f32009-02-22 12:00:12 +0000551 param->rec_id = PJMEDIA_AUD_INVALID_DEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000552 } else {
553 return PJMEDIA_EAUD_INVDEV;
554 }
555
556 param->clock_rate = adi.default_samples_per_sec;
557 param->channel_count = 1;
558 param->samples_per_frame = adi.default_samples_per_sec * 20 / 1000;
559 param->bits_per_sample = 16;
560 param->flags = adi.caps;
561 param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
562 param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
563
564 return PJ_SUCCESS;
565}
566
567
568/* Internal: Get PortAudio default input device ID */
569static int pa_get_default_input_dev(int channel_count)
570{
571 int i, count;
572
573 /* Special for Windows - try to use the DirectSound implementation
574 * first since it provides better latency.
575 */
576#if PJMEDIA_PREFER_DIRECT_SOUND
577 if (Pa_HostApiTypeIdToHostApiIndex(paDirectSound) >= 0) {
578 const PaHostApiInfo *pHI;
579 int index = Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
580 pHI = Pa_GetHostApiInfo(index);
581 if (pHI) {
582 const PaDeviceInfo *paDevInfo = NULL;
583 paDevInfo = Pa_GetDeviceInfo(pHI->defaultInputDevice);
584 if (paDevInfo && paDevInfo->maxInputChannels >= channel_count)
585 return pHI->defaultInputDevice;
586 }
587 }
588#endif
589
590 /* Enumerate the host api's for the default devices, and return
591 * the device with suitable channels.
592 */
593 count = Pa_GetHostApiCount();
594 for (i=0; i < count; ++i) {
595 const PaHostApiInfo *pHAInfo;
596
597 pHAInfo = Pa_GetHostApiInfo(i);
598 if (!pHAInfo)
599 continue;
600
601 if (pHAInfo->defaultInputDevice >= 0) {
602 const PaDeviceInfo *paDevInfo;
603
604 paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultInputDevice);
605
606 if (paDevInfo->maxInputChannels >= channel_count)
607 return pHAInfo->defaultInputDevice;
608 }
609 }
610
611 /* If still no device is found, enumerate all devices */
612 count = Pa_GetDeviceCount();
613 for (i=0; i<count; ++i) {
614 const PaDeviceInfo *paDevInfo;
615
616 paDevInfo = Pa_GetDeviceInfo(i);
617 if (paDevInfo->maxInputChannels >= channel_count)
618 return i;
619 }
620
621 return -1;
622}
623
624/* Internal: Get PortAudio default output device ID */
625static int pa_get_default_output_dev(int channel_count)
626{
627 int i, count;
628
629 /* Special for Windows - try to use the DirectSound implementation
630 * first since it provides better latency.
631 */
632#if PJMEDIA_PREFER_DIRECT_SOUND
633 if (Pa_HostApiTypeIdToHostApiIndex(paDirectSound) >= 0) {
634 const PaHostApiInfo *pHI;
635 int index = Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
636 pHI = Pa_GetHostApiInfo(index);
637 if (pHI) {
638 const PaDeviceInfo *paDevInfo = NULL;
639 paDevInfo = Pa_GetDeviceInfo(pHI->defaultOutputDevice);
640 if (paDevInfo && paDevInfo->maxOutputChannels >= channel_count)
641 return pHI->defaultOutputDevice;
642 }
643 }
644#endif
645
646 /* Enumerate the host api's for the default devices, and return
647 * the device with suitable channels.
648 */
649 count = Pa_GetHostApiCount();
650 for (i=0; i < count; ++i) {
651 const PaHostApiInfo *pHAInfo;
652
653 pHAInfo = Pa_GetHostApiInfo(i);
654 if (!pHAInfo)
655 continue;
656
657 if (pHAInfo->defaultOutputDevice >= 0) {
658 const PaDeviceInfo *paDevInfo;
659
660 paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultOutputDevice);
661
662 if (paDevInfo->maxOutputChannels >= channel_count)
663 return pHAInfo->defaultOutputDevice;
664 }
665 }
666
667 /* If still no device is found, enumerate all devices */
668 count = Pa_GetDeviceCount();
669 for (i=0; i<count; ++i) {
670 const PaDeviceInfo *paDevInfo;
671
672 paDevInfo = Pa_GetDeviceInfo(i);
673 if (paDevInfo->maxOutputChannels >= channel_count)
674 return i;
675 }
676
677 return -1;
678}
679
680
681/* Internal: create capture/recorder stream */
682static pj_status_t create_rec_stream( struct pa_aud_factory *pa,
Benny Prijono10454dc2009-02-21 14:21:59 +0000683 const pjmedia_aud_param *param,
Benny Prijono2cd64f82009-02-17 19:57:48 +0000684 pjmedia_aud_rec_cb rec_cb,
685 void *user_data,
686 pjmedia_aud_stream **p_snd_strm)
687{
688 pj_pool_t *pool;
Benny Prijono10454dc2009-02-21 14:21:59 +0000689 pjmedia_aud_dev_index rec_id;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000690 struct pa_aud_stream *stream;
691 PaStreamParameters inputParam;
692 int sampleFormat;
693 const PaDeviceInfo *paDevInfo = NULL;
694 const PaHostApiInfo *paHostApiInfo = NULL;
695 unsigned paFrames, paRate, paLatency;
696 const PaStreamInfo *paSI;
697 PaError err;
698
Benny Prijono555139d2009-02-19 12:08:19 +0000699 PJ_ASSERT_RETURN(rec_cb && p_snd_strm, PJ_EINVAL);
700
Benny Prijono2cd64f82009-02-17 19:57:48 +0000701 rec_id = param->rec_id;
702 if (rec_id < 0) {
703 rec_id = pa_get_default_input_dev(param->channel_count);
704 if (rec_id < 0) {
705 /* No such device. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000706 return PJMEDIA_EAUD_NODEFDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000707 }
708 }
709
710 paDevInfo = Pa_GetDeviceInfo(rec_id);
711 if (!paDevInfo) {
712 /* Assumed it is "No such device" error. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000713 return PJMEDIA_EAUD_INVDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000714 }
715
716 if (param->bits_per_sample == 8)
717 sampleFormat = paUInt8;
718 else if (param->bits_per_sample == 16)
719 sampleFormat = paInt16;
720 else if (param->bits_per_sample == 32)
721 sampleFormat = paInt32;
722 else
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000723 return PJMEDIA_EAUD_SAMPFORMAT;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000724
725 pool = pj_pool_create(pa->pf, "recstrm", 1024, 1024, NULL);
726 if (!pool)
727 return PJ_ENOMEM;
728
729 stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
730 stream->pool = pool;
731 pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
732 stream->dir = PJMEDIA_DIR_CAPTURE;
733 stream->rec_id = rec_id;
734 stream->play_id = -1;
735 stream->user_data = user_data;
736 stream->samples_per_sec = param->clock_rate;
737 stream->samples_per_frame = param->samples_per_frame;
738 stream->bytes_per_sample = param->bits_per_sample / 8;
739 stream->channel_count = param->channel_count;
740 stream->rec_cb = rec_cb;
741
742 stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool,
743 stream->samples_per_frame * stream->bytes_per_sample);
744 stream->rec_buf_count = 0;
745
746 pj_bzero(&inputParam, sizeof(inputParam));
747 inputParam.device = rec_id;
748 inputParam.channelCount = param->channel_count;
749 inputParam.hostApiSpecificStreamInfo = NULL;
750 inputParam.sampleFormat = sampleFormat;
751 if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
752 inputParam.suggestedLatency = param->input_latency_ms / 1000.0;
753 else
754 inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0;
755
756 paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
757
758 /* Frames in PortAudio is number of samples in a single channel */
759 paFrames = param->samples_per_frame / param->channel_count;
760
761 err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
762 param->clock_rate, paFrames,
763 paClipOff, &PaRecorderCallback, stream );
764 if (err != paNoError) {
765 pj_pool_release(pool);
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000766 return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000767 }
768
769 paSI = Pa_GetStreamInfo(stream->rec_strm);
770 paRate = (unsigned)paSI->sampleRate;
771 paLatency = (unsigned)(paSI->inputLatency * 1000);
772
773 PJ_LOG(5,(THIS_FILE, "Opened device %s (%s) for recording, sample "
774 "rate=%d, ch=%d, "
775 "bits=%d, %d samples per frame, latency=%d ms",
776 paDevInfo->name, paHostApiInfo->name,
777 paRate, param->channel_count,
778 param->bits_per_sample, param->samples_per_frame,
779 paLatency));
780
781 *p_snd_strm = &stream->base;
782 return PJ_SUCCESS;
783}
784
785
786/* Internal: create playback stream */
787static pj_status_t create_play_stream(struct pa_aud_factory *pa,
Benny Prijono10454dc2009-02-21 14:21:59 +0000788 const pjmedia_aud_param *param,
Benny Prijono2cd64f82009-02-17 19:57:48 +0000789 pjmedia_aud_play_cb play_cb,
790 void *user_data,
791 pjmedia_aud_stream **p_snd_strm)
792{
793 pj_pool_t *pool;
Benny Prijono10454dc2009-02-21 14:21:59 +0000794 pjmedia_aud_dev_index play_id;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000795 struct pa_aud_stream *stream;
796 PaStreamParameters outputParam;
797 int sampleFormat;
798 const PaDeviceInfo *paDevInfo = NULL;
799 const PaHostApiInfo *paHostApiInfo = NULL;
800 const PaStreamInfo *paSI;
801 unsigned paFrames, paRate, paLatency;
802 PaError err;
803
Benny Prijono555139d2009-02-19 12:08:19 +0000804 PJ_ASSERT_RETURN(play_cb && p_snd_strm, PJ_EINVAL);
805
Benny Prijono2cd64f82009-02-17 19:57:48 +0000806 play_id = param->play_id;
807 if (play_id < 0) {
808 play_id = pa_get_default_output_dev(param->channel_count);
809 if (play_id < 0) {
810 /* No such device. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000811 return PJMEDIA_EAUD_NODEFDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000812 }
813 }
814
815 paDevInfo = Pa_GetDeviceInfo(play_id);
816 if (!paDevInfo) {
817 /* Assumed it is "No such device" error. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000818 return PJMEDIA_EAUD_INVDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000819 }
820
821 if (param->bits_per_sample == 8)
822 sampleFormat = paUInt8;
823 else if (param->bits_per_sample == 16)
824 sampleFormat = paInt16;
825 else if (param->bits_per_sample == 32)
826 sampleFormat = paInt32;
827 else
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000828 return PJMEDIA_EAUD_SAMPFORMAT;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000829
830 pool = pj_pool_create(pa->pf, "playstrm", 1024, 1024, NULL);
831 if (!pool)
832 return PJ_ENOMEM;
833
834 stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
835 stream->pool = pool;
836 pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
837 stream->dir = PJMEDIA_DIR_PLAYBACK;
838 stream->play_id = play_id;
839 stream->rec_id = -1;
840 stream->user_data = user_data;
841 stream->samples_per_sec = param->clock_rate;
842 stream->samples_per_frame = param->samples_per_frame;
843 stream->bytes_per_sample = param->bits_per_sample / 8;
844 stream->channel_count = param->channel_count;
845 stream->play_cb = play_cb;
846
847 stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool,
848 stream->samples_per_frame *
849 stream->bytes_per_sample);
850 stream->play_buf_count = 0;
851
852 pj_bzero(&outputParam, sizeof(outputParam));
853 outputParam.device = play_id;
854 outputParam.channelCount = param->channel_count;
855 outputParam.hostApiSpecificStreamInfo = NULL;
856 outputParam.sampleFormat = sampleFormat;
857 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
858 outputParam.suggestedLatency=param->output_latency_ms / 1000.0;
859 else
860 outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0;
861
862 paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
863
864 /* Frames in PortAudio is number of samples in a single channel */
865 paFrames = param->samples_per_frame / param->channel_count;
866
867 err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
868 param->clock_rate, paFrames,
869 paClipOff, &PaPlayerCallback, stream );
870 if (err != paNoError) {
871 pj_pool_release(pool);
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000872 return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000873 }
874
875 paSI = Pa_GetStreamInfo(stream->play_strm);
876 paRate = (unsigned)(paSI->sampleRate);
877 paLatency = (unsigned)(paSI->outputLatency * 1000);
878
879 PJ_LOG(5,(THIS_FILE, "Opened device %d: %s(%s) for playing, sample rate=%d"
880 ", ch=%d, "
881 "bits=%d, %d samples per frame, latency=%d ms",
882 play_id, paDevInfo->name, paHostApiInfo->name,
883 paRate, param->channel_count,
884 param->bits_per_sample, param->samples_per_frame,
885 paLatency));
886
887 *p_snd_strm = &stream->base;
888
889 return PJ_SUCCESS;
890}
891
892
893/* Internal: Create both player and recorder stream */
894static pj_status_t create_bidir_stream(struct pa_aud_factory *pa,
Benny Prijono10454dc2009-02-21 14:21:59 +0000895 const pjmedia_aud_param *param,
Benny Prijono2cd64f82009-02-17 19:57:48 +0000896 pjmedia_aud_rec_cb rec_cb,
897 pjmedia_aud_play_cb play_cb,
898 void *user_data,
899 pjmedia_aud_stream **p_snd_strm)
900{
901 pj_pool_t *pool;
Benny Prijono10454dc2009-02-21 14:21:59 +0000902 pjmedia_aud_dev_index rec_id, play_id;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000903 struct pa_aud_stream *stream;
904 PaStream *paStream = NULL;
905 PaStreamParameters inputParam;
906 PaStreamParameters outputParam;
907 int sampleFormat;
908 const PaDeviceInfo *paRecDevInfo = NULL;
909 const PaDeviceInfo *paPlayDevInfo = NULL;
910 const PaHostApiInfo *paRecHostApiInfo = NULL;
911 const PaHostApiInfo *paPlayHostApiInfo = NULL;
912 const PaStreamInfo *paSI;
913 unsigned paFrames, paRate, paInputLatency, paOutputLatency;
914 PaError err;
915
Benny Prijono555139d2009-02-19 12:08:19 +0000916 PJ_ASSERT_RETURN(play_cb && rec_cb && p_snd_strm, PJ_EINVAL);
917
Benny Prijono2cd64f82009-02-17 19:57:48 +0000918 rec_id = param->rec_id;
919 if (rec_id < 0) {
920 rec_id = pa_get_default_input_dev(param->channel_count);
921 if (rec_id < 0) {
922 /* No such device. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000923 return PJMEDIA_EAUD_NODEFDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000924 }
925 }
926
927 paRecDevInfo = Pa_GetDeviceInfo(rec_id);
928 if (!paRecDevInfo) {
929 /* Assumed it is "No such device" error. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000930 return PJMEDIA_EAUD_INVDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000931 }
932
933 play_id = param->play_id;
934 if (play_id < 0) {
935 play_id = pa_get_default_output_dev(param->channel_count);
936 if (play_id < 0) {
937 /* No such device. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000938 return PJMEDIA_EAUD_NODEFDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000939 }
940 }
941
942 paPlayDevInfo = Pa_GetDeviceInfo(play_id);
943 if (!paPlayDevInfo) {
944 /* Assumed it is "No such device" error. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000945 return PJMEDIA_EAUD_INVDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000946 }
947
948
949 if (param->bits_per_sample == 8)
950 sampleFormat = paUInt8;
951 else if (param->bits_per_sample == 16)
952 sampleFormat = paInt16;
953 else if (param->bits_per_sample == 32)
954 sampleFormat = paInt32;
955 else
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000956 return PJMEDIA_EAUD_SAMPFORMAT;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000957
958 pool = pj_pool_create(pa->pf, "sndstream", 1024, 1024, NULL);
959 if (!pool)
960 return PJ_ENOMEM;
961
962 stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
963 stream->pool = pool;
964 pj_strdup2_with_null(pool, &stream->name, paRecDevInfo->name);
965 stream->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
966 stream->play_id = play_id;
967 stream->rec_id = rec_id;
968 stream->user_data = user_data;
969 stream->samples_per_sec = param->clock_rate;
970 stream->samples_per_frame = param->samples_per_frame;
971 stream->bytes_per_sample = param->bits_per_sample / 8;
972 stream->channel_count = param->channel_count;
973 stream->rec_cb = rec_cb;
974 stream->play_cb = play_cb;
975
976 stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool,
977 stream->samples_per_frame * stream->bytes_per_sample);
978 stream->rec_buf_count = 0;
979
980 stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool,
981 stream->samples_per_frame * stream->bytes_per_sample);
982 stream->play_buf_count = 0;
983
984 pj_bzero(&inputParam, sizeof(inputParam));
985 inputParam.device = rec_id;
986 inputParam.channelCount = param->channel_count;
987 inputParam.hostApiSpecificStreamInfo = NULL;
988 inputParam.sampleFormat = sampleFormat;
989 if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
990 inputParam.suggestedLatency = param->input_latency_ms / 1000.0;
991 else
992 inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0;
993
994 paRecHostApiInfo = Pa_GetHostApiInfo(paRecDevInfo->hostApi);
995
996 pj_bzero(&outputParam, sizeof(outputParam));
997 outputParam.device = play_id;
998 outputParam.channelCount = param->channel_count;
999 outputParam.hostApiSpecificStreamInfo = NULL;
1000 outputParam.sampleFormat = sampleFormat;
1001 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
1002 outputParam.suggestedLatency=param->output_latency_ms / 1000.0;
1003 else
1004 outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0;
1005
1006 paPlayHostApiInfo = Pa_GetHostApiInfo(paPlayDevInfo->hostApi);
1007
1008 /* Frames in PortAudio is number of samples in a single channel */
1009 paFrames = param->samples_per_frame / param->channel_count;
1010
1011 /* If both input and output are on the same device, open a single stream
1012 * for both input and output.
1013 */
1014 if (rec_id == play_id) {
1015 err = Pa_OpenStream( &paStream, &inputParam, &outputParam,
1016 param->clock_rate, paFrames,
1017 paClipOff, &PaRecorderPlayerCallback, stream );
1018 if (err == paNoError) {
1019 /* Set play stream and record stream to the same stream */
1020 stream->play_strm = stream->rec_strm = paStream;
1021 }
1022 } else {
1023 err = -1;
1024 }
1025
1026 /* .. otherwise if input and output are on the same device, OR if we're
1027 * unable to open a bidirectional stream, then open two separate
1028 * input and output stream.
1029 */
1030 if (paStream == NULL) {
1031 /* Open input stream */
1032 err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
1033 param->clock_rate, paFrames,
1034 paClipOff, &PaRecorderCallback, stream );
1035 if (err == paNoError) {
1036 /* Open output stream */
1037 err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
1038 param->clock_rate, paFrames,
1039 paClipOff, &PaPlayerCallback, stream );
1040 if (err != paNoError)
1041 Pa_CloseStream(stream->rec_strm);
1042 }
1043 }
1044
1045 if (err != paNoError) {
1046 pj_pool_release(pool);
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001047 return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001048 }
1049
1050 paSI = Pa_GetStreamInfo(stream->rec_strm);
1051 paRate = (unsigned)(paSI->sampleRate);
1052 paInputLatency = (unsigned)(paSI->inputLatency * 1000);
1053 paSI = Pa_GetStreamInfo(stream->play_strm);
1054 paOutputLatency = (unsigned)(paSI->outputLatency * 1000);
1055
1056 PJ_LOG(5,(THIS_FILE, "Opened device %s(%s)/%s(%s) for recording and "
1057 "playback, sample rate=%d, ch=%d, "
1058 "bits=%d, %d samples per frame, input latency=%d ms, "
1059 "output latency=%d ms",
1060 paRecDevInfo->name, paRecHostApiInfo->name,
1061 paPlayDevInfo->name, paPlayHostApiInfo->name,
1062 paRate, param->channel_count,
1063 param->bits_per_sample, param->samples_per_frame,
1064 paInputLatency, paOutputLatency));
1065
1066 *p_snd_strm = &stream->base;
1067
1068 return PJ_SUCCESS;
1069}
1070
1071
1072/* API: create stream */
1073static pj_status_t pa_create_stream(pjmedia_aud_dev_factory *f,
Benny Prijono10454dc2009-02-21 14:21:59 +00001074 const pjmedia_aud_param *param,
Benny Prijono2cd64f82009-02-17 19:57:48 +00001075 pjmedia_aud_rec_cb rec_cb,
1076 pjmedia_aud_play_cb play_cb,
1077 void *user_data,
1078 pjmedia_aud_stream **p_aud_strm)
1079{
1080 struct pa_aud_factory *pa = (struct pa_aud_factory*)f;
1081 pj_status_t status;
1082
1083 if (param->dir == PJMEDIA_DIR_CAPTURE) {
1084 status = create_rec_stream(pa, param, rec_cb, user_data, p_aud_strm);
1085 } else if (param->dir == PJMEDIA_DIR_PLAYBACK) {
1086 status = create_play_stream(pa, param, play_cb, user_data, p_aud_strm);
1087 } else if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
1088 status = create_bidir_stream(pa, param, rec_cb, play_cb, user_data,
1089 p_aud_strm);
1090 } else {
1091 return PJ_EINVAL;
1092 }
1093
1094 if (status != PJ_SUCCESS)
1095 return status;
1096
1097 (*p_aud_strm)->op = &pa_strm_op;
1098
1099 return PJ_SUCCESS;
1100}
1101
1102
1103/* API: Get stream parameters */
1104static pj_status_t strm_get_param(pjmedia_aud_stream *s,
Benny Prijono10454dc2009-02-21 14:21:59 +00001105 pjmedia_aud_param *pi)
Benny Prijono2cd64f82009-02-17 19:57:48 +00001106{
1107 struct pa_aud_stream *strm = (struct pa_aud_stream*)s;
1108 const PaStreamInfo *paPlaySI = NULL, *paRecSI = NULL;
1109
1110 PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
1111 PJ_ASSERT_RETURN(strm->play_strm || strm->rec_strm, PJ_EINVALIDOP);
1112
1113 if (strm->play_strm) {
1114 paPlaySI = Pa_GetStreamInfo(strm->play_strm);
1115 }
1116 if (strm->rec_strm) {
1117 paRecSI = Pa_GetStreamInfo(strm->rec_strm);
1118 }
1119
1120 pj_bzero(pi, sizeof(*pi));
1121 pi->dir = strm->dir;
1122 pi->play_id = strm->play_id;
1123 pi->rec_id = strm->rec_id;
1124 pi->clock_rate = (unsigned)(paPlaySI ? paPlaySI->sampleRate :
1125 paRecSI->sampleRate);
1126 pi->channel_count = strm->channel_count;
1127 pi->samples_per_frame = strm->samples_per_frame;
1128 pi->bits_per_sample = strm->bytes_per_sample * 8;
1129 if (paRecSI) {
1130 pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
1131 pi->input_latency_ms = (unsigned)(paRecSI ? paRecSI->inputLatency *
Benny Prijono598b01d2009-02-18 13:55:03 +00001132 1000 : 0);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001133 }
1134 if (paPlaySI) {
1135 pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
1136 pi->output_latency_ms = (unsigned)(paPlaySI? paPlaySI->outputLatency *
Benny Prijono598b01d2009-02-18 13:55:03 +00001137 1000 : 0);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001138 }
1139
1140 return PJ_SUCCESS;
1141}
1142
1143
1144/* API: get capability */
1145static pj_status_t strm_get_cap(pjmedia_aud_stream *s,
1146 pjmedia_aud_dev_cap cap,
1147 void *pval)
1148{
1149 struct pa_aud_stream *strm = (struct pa_aud_stream*)s;
1150
1151 PJ_ASSERT_RETURN(strm && pval, PJ_EINVAL);
1152
1153 if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && strm->rec_strm) {
1154 const PaStreamInfo *si = Pa_GetStreamInfo(strm->rec_strm);
1155 if (!si)
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001156 return PJMEDIA_EAUD_SYSERR;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001157
Benny Prijono598b01d2009-02-18 13:55:03 +00001158 *(unsigned*)pval = (unsigned)(si->inputLatency * 1000);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001159 return PJ_SUCCESS;
1160 } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && strm->play_strm) {
1161 const PaStreamInfo *si = Pa_GetStreamInfo(strm->play_strm);
1162 if (!si)
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001163 return PJMEDIA_EAUD_SYSERR;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001164
Benny Prijono598b01d2009-02-18 13:55:03 +00001165 *(unsigned*)pval = (unsigned)(si->outputLatency * 1000);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001166 return PJ_SUCCESS;
1167 } else {
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001168 return PJMEDIA_EAUD_INVCAP;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001169 }
1170}
1171
1172
1173/* API: set capability */
1174static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
1175 pjmedia_aud_dev_cap cap,
1176 const void *value)
1177{
1178 PJ_UNUSED_ARG(strm);
1179 PJ_UNUSED_ARG(cap);
1180 PJ_UNUSED_ARG(value);
1181
1182 /* Nothing is supported */
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001183 return PJMEDIA_EAUD_INVCAP;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001184}
1185
1186
1187/* API: start stream. */
1188static pj_status_t strm_start(pjmedia_aud_stream *s)
1189{
1190 struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
1191 int err = 0;
1192
1193 PJ_LOG(5,(THIS_FILE, "Starting %s stream..", stream->name.ptr));
1194
1195 if (stream->play_strm)
1196 err = Pa_StartStream(stream->play_strm);
1197
1198 if (err==0 && stream->rec_strm && stream->rec_strm != stream->play_strm) {
1199 err = Pa_StartStream(stream->rec_strm);
1200 if (err != 0)
1201 Pa_StopStream(stream->play_strm);
1202 }
1203
1204 PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
1205
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001206 return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001207}
1208
1209
1210/* API: stop stream. */
1211static pj_status_t strm_stop(pjmedia_aud_stream *s)
1212{
1213 struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
1214 int i, err = 0;
1215
1216 stream->quit_flag = 1;
1217 for (i=0; !stream->rec_thread_exited && i<100; ++i)
1218 pj_thread_sleep(10);
1219 for (i=0; !stream->play_thread_exited && i<100; ++i)
1220 pj_thread_sleep(10);
1221
1222 pj_thread_sleep(1);
1223
1224 PJ_LOG(5,(THIS_FILE, "Stopping stream.."));
1225
1226 if (stream->play_strm)
1227 err = Pa_StopStream(stream->play_strm);
1228
1229 if (stream->rec_strm && stream->rec_strm != stream->play_strm)
1230 err = Pa_StopStream(stream->rec_strm);
1231
1232 stream->play_thread_initialized = 0;
1233 stream->rec_thread_initialized = 0;
1234
1235 PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
1236
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001237 return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001238}
1239
1240
1241/* API: destroy stream. */
1242static pj_status_t strm_destroy(pjmedia_aud_stream *s)
1243{
1244 struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
1245 int i, err = 0;
1246
1247 stream->quit_flag = 1;
1248 for (i=0; !stream->rec_thread_exited && i<100; ++i) {
1249 pj_thread_sleep(1);
1250 }
1251 for (i=0; !stream->play_thread_exited && i<100; ++i) {
1252 pj_thread_sleep(1);
1253 }
1254
1255 PJ_LOG(5,(THIS_FILE, "Closing %.*s: %lu underflow, %lu overflow",
1256 (int)stream->name.slen,
1257 stream->name.ptr,
1258 stream->underflow, stream->overflow));
1259
1260 if (stream->play_strm)
1261 err = Pa_CloseStream(stream->play_strm);
1262
1263 if (stream->rec_strm && stream->rec_strm != stream->play_strm)
1264 err = Pa_CloseStream(stream->rec_strm);
1265
1266 pj_pool_release(stream->pool);
1267
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001268 return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001269}
1270
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001271#endif /* PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO */
1272