blob: 1188b8abe616bf90a529a9c589e53011326ef2c6 [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 {
Sauw Ming4fc590f2011-01-13 16:42:21 +0000188 pj_bzero(stream->rec_thread_desc, sizeof(pj_thread_desc));
Benny Prijono2cd64f82009-02-17 19:57:48 +0000189 status = pj_thread_register("pa_rec", stream->rec_thread_desc,
190 &stream->rec_thread);
191 stream->rec_thread_initialized = 1;
192 PJ_LOG(5,(THIS_FILE, "Recorder thread started"));
193 }
194
195 if (statusFlags & paInputUnderflow)
196 ++stream->underflow;
197 if (statusFlags & paInputOverflow)
198 ++stream->overflow;
199
Benny Prijono2cd64f82009-02-17 19:57:48 +0000200 /* Calculate number of samples we've got */
201 nsamples = frameCount * stream->channel_count + stream->rec_buf_count;
202
203 if (nsamples >= stream->samples_per_frame)
204 {
205 /* If buffer is not empty, combine the buffer with the just incoming
206 * samples, then call put_frame.
207 */
208 if (stream->rec_buf_count) {
209 unsigned chunk_count = 0;
Benny Prijono598b01d2009-02-18 13:55:03 +0000210 pjmedia_frame frame;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000211
212 chunk_count = stream->samples_per_frame - stream->rec_buf_count;
213 pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count,
214 (pj_int16_t*)input, chunk_count);
Benny Prijono598b01d2009-02-18 13:55:03 +0000215
216 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
217 frame.buf = (void*) stream->rec_buf;
218 frame.size = stream->samples_per_frame * stream->bytes_per_sample;
219 frame.timestamp.u64 = stream->rec_timestamp.u64;
220 frame.bit_info = 0;
221
222 status = (*stream->rec_cb)(stream->user_data, &frame);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000223
224 input = (pj_int16_t*) input + chunk_count;
225 nsamples -= stream->samples_per_frame;
226 stream->rec_buf_count = 0;
Benny Prijono598b01d2009-02-18 13:55:03 +0000227 stream->rec_timestamp.u64 += stream->samples_per_frame /
228 stream->channel_count;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000229 }
230
231 /* Give all frames we have */
232 while (nsamples >= stream->samples_per_frame && status == 0) {
Benny Prijono598b01d2009-02-18 13:55:03 +0000233 pjmedia_frame frame;
234
235 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
236 frame.buf = (void*) input;
237 frame.size = stream->samples_per_frame * stream->bytes_per_sample;
238 frame.timestamp.u64 = stream->rec_timestamp.u64;
239 frame.bit_info = 0;
240
241 status = (*stream->rec_cb)(stream->user_data, &frame);
242
Benny Prijono2cd64f82009-02-17 19:57:48 +0000243 input = (pj_int16_t*) input + stream->samples_per_frame;
244 nsamples -= stream->samples_per_frame;
Benny Prijono598b01d2009-02-18 13:55:03 +0000245 stream->rec_timestamp.u64 += stream->samples_per_frame /
246 stream->channel_count;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000247 }
248
249 /* Store the remaining samples into the buffer */
250 if (nsamples && status == 0) {
251 stream->rec_buf_count = nsamples;
252 pjmedia_copy_samples(stream->rec_buf, (pj_int16_t*)input,
253 nsamples);
254 }
255
256 } else {
257 /* Not enough samples, let's just store them in the buffer */
258 pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count,
259 (pj_int16_t*)input,
260 frameCount * stream->channel_count);
261 stream->rec_buf_count += frameCount * stream->channel_count;
262 }
263
264 if (status==0)
265 return paContinue;
266
267on_break:
268 stream->rec_thread_exited = 1;
269 return paAbort;
270}
271
272static int PaPlayerCallback( const void *input,
273 void *output,
274 unsigned long frameCount,
275 const PaStreamCallbackTimeInfo* timeInfo,
276 PaStreamCallbackFlags statusFlags,
277 void *userData )
278{
279 struct pa_aud_stream *stream = (struct pa_aud_stream*) userData;
280 pj_status_t status = 0;
281 unsigned nsamples_req = frameCount * stream->channel_count;
282
283 PJ_UNUSED_ARG(input);
284 PJ_UNUSED_ARG(timeInfo);
285
286 if (stream->quit_flag)
287 goto on_break;
288
289 if (output == NULL)
290 return paContinue;
291
292 /* Known cases of callback's thread:
293 * - The thread may be changed in the middle of a session, e.g: in MacOS
294 * it happens when plugging/unplugging headphone.
295 * - The same thread may be reused in consecutive sessions. The first
296 * session will leave TLS set, but release the TLS data address,
297 * so the second session must re-register the callback's thread.
298 */
299 if (stream->play_thread_initialized == 0 || !pj_thread_is_registered())
300 {
Sauw Ming4fc590f2011-01-13 16:42:21 +0000301 pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc));
Benny Prijono2cd64f82009-02-17 19:57:48 +0000302 status = pj_thread_register("portaudio", stream->play_thread_desc,
303 &stream->play_thread);
304 stream->play_thread_initialized = 1;
305 PJ_LOG(5,(THIS_FILE, "Player thread started"));
306 }
307
308 if (statusFlags & paOutputUnderflow)
309 ++stream->underflow;
310 if (statusFlags & paOutputOverflow)
311 ++stream->overflow;
312
Benny Prijono2cd64f82009-02-17 19:57:48 +0000313
314 /* Check if any buffered samples */
315 if (stream->play_buf_count) {
316 /* samples buffered >= requested by sound device */
317 if (stream->play_buf_count >= nsamples_req) {
318 pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
319 nsamples_req);
320 stream->play_buf_count -= nsamples_req;
321 pjmedia_move_samples(stream->play_buf,
322 stream->play_buf + nsamples_req,
323 stream->play_buf_count);
324 nsamples_req = 0;
325
326 return paContinue;
327 }
328
329 /* samples buffered < requested by sound device */
330 pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
331 stream->play_buf_count);
332 nsamples_req -= stream->play_buf_count;
333 output = (pj_int16_t*)output + stream->play_buf_count;
334 stream->play_buf_count = 0;
335 }
336
337 /* Fill output buffer as requested */
338 while (nsamples_req && status == 0) {
339 if (nsamples_req >= stream->samples_per_frame) {
Benny Prijono598b01d2009-02-18 13:55:03 +0000340 pjmedia_frame frame;
341
342 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
343 frame.buf = output;
344 frame.size = stream->samples_per_frame * stream->bytes_per_sample;
345 frame.timestamp.u64 = stream->play_timestamp.u64;
346 frame.bit_info = 0;
347
348 status = (*stream->play_cb)(stream->user_data, &frame);
349 if (status != PJ_SUCCESS)
350 goto on_break;
351
352 if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
353 pj_bzero(frame.buf, frame.size);
354
Benny Prijono2cd64f82009-02-17 19:57:48 +0000355 nsamples_req -= stream->samples_per_frame;
356 output = (pj_int16_t*)output + stream->samples_per_frame;
357 } else {
Benny Prijono598b01d2009-02-18 13:55:03 +0000358 pjmedia_frame frame;
359
360 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
361 frame.buf = stream->play_buf;
362 frame.size = stream->samples_per_frame * stream->bytes_per_sample;
363 frame.timestamp.u64 = stream->play_timestamp.u64;
364 frame.bit_info = 0;
365
366 status = (*stream->play_cb)(stream->user_data, &frame);
367 if (status != PJ_SUCCESS)
368 goto on_break;
369
370 if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
371 pj_bzero(frame.buf, frame.size);
372
Benny Prijono2cd64f82009-02-17 19:57:48 +0000373 pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
374 nsamples_req);
375 stream->play_buf_count = stream->samples_per_frame - nsamples_req;
376 pjmedia_move_samples(stream->play_buf,
377 stream->play_buf+nsamples_req,
378 stream->play_buf_count);
379 nsamples_req = 0;
380 }
Benny Prijono598b01d2009-02-18 13:55:03 +0000381
382 stream->play_timestamp.u64 += stream->samples_per_frame /
383 stream->channel_count;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000384 }
385
386 if (status==0)
387 return paContinue;
388
389on_break:
390 stream->play_thread_exited = 1;
391 return paAbort;
392}
393
394
395static int PaRecorderPlayerCallback( const void *input,
396 void *output,
397 unsigned long frameCount,
398 const PaStreamCallbackTimeInfo* timeInfo,
399 PaStreamCallbackFlags statusFlags,
400 void *userData )
401{
402 int rc;
403
404 rc = PaRecorderCallback(input, output, frameCount, timeInfo,
405 statusFlags, userData);
406 if (rc != paContinue)
407 return rc;
408
409 rc = PaPlayerCallback(input, output, frameCount, timeInfo,
410 statusFlags, userData);
411 return rc;
412}
413
Benny Prijonofe0c1272010-01-13 16:28:15 +0000414#ifdef USE_PA_DEBUG_PRINT
Benny Prijono2cd64f82009-02-17 19:57:48 +0000415/* Logging callback from PA */
416static void pa_log_cb(const char *log)
417{
418 PJ_LOG(5,(THIS_FILE, "PA message: %s", log));
419}
420
421/* We should include pa_debugprint.h for this, but the header
422 * is not available publicly. :(
423 */
424typedef void (*PaUtilLogCallback ) (const char *log);
425void PaUtil_SetDebugPrintFunction(PaUtilLogCallback cb);
Benny Prijonofe0c1272010-01-13 16:28:15 +0000426#endif
Benny Prijono2cd64f82009-02-17 19:57:48 +0000427
428
429/*
430 * Init PortAudio audio driver.
431 */
432pjmedia_aud_dev_factory* pjmedia_pa_factory(pj_pool_factory *pf)
433{
434 struct pa_aud_factory *f;
435 pj_pool_t *pool;
436
437 pool = pj_pool_create(pf, "portaudio", 64, 64, NULL);
438 f = PJ_POOL_ZALLOC_T(pool, struct pa_aud_factory);
439 f->pf = pf;
440 f->pool = pool;
441 f->base.op = &pa_op;
442
443 return &f->base;
444}
445
446
447/* API: Init factory */
448static pj_status_t pa_init(pjmedia_aud_dev_factory *f)
449{
450 int err;
451
452 PJ_UNUSED_ARG(f);
453
Benny Prijonofe0c1272010-01-13 16:28:15 +0000454#ifdef USE_PA_DEBUG_PRINT
Benny Prijono2cd64f82009-02-17 19:57:48 +0000455 PaUtil_SetDebugPrintFunction(&pa_log_cb);
Benny Prijonofe0c1272010-01-13 16:28:15 +0000456#endif
Benny Prijono2cd64f82009-02-17 19:57:48 +0000457
458 err = Pa_Initialize();
459
460 PJ_LOG(4,(THIS_FILE,
461 "PortAudio sound library initialized, status=%d", err));
462 PJ_LOG(4,(THIS_FILE, "PortAudio host api count=%d",
463 Pa_GetHostApiCount()));
464 PJ_LOG(4,(THIS_FILE, "Sound device count=%d",
465 pa_get_dev_count(f)));
466
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000467 return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000468}
469
470
471/* API: Destroy factory */
472static pj_status_t pa_destroy(pjmedia_aud_dev_factory *f)
473{
474 struct pa_aud_factory *pa = (struct pa_aud_factory*)f;
475 pj_pool_t *pool;
476 int err;
477
478 PJ_LOG(4,(THIS_FILE, "PortAudio sound library shutting down.."));
479
480 err = Pa_Terminate();
481
482 pool = pa->pool;
483 pa->pool = NULL;
484 pj_pool_release(pool);
485
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000486 return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000487}
488
489
490/* API: Get device count. */
491static unsigned pa_get_dev_count(pjmedia_aud_dev_factory *f)
492{
493 int count = Pa_GetDeviceCount();
494 PJ_UNUSED_ARG(f);
495 return count < 0 ? 0 : count;
496}
497
498
499/* API: Get device info. */
500static pj_status_t pa_get_dev_info(pjmedia_aud_dev_factory *f,
Benny Prijono598b01d2009-02-18 13:55:03 +0000501 unsigned index,
Benny Prijono2cd64f82009-02-17 19:57:48 +0000502 pjmedia_aud_dev_info *info)
503{
504 const PaDeviceInfo *pa_info;
505
506 PJ_UNUSED_ARG(f);
507
Benny Prijono598b01d2009-02-18 13:55:03 +0000508 pa_info = Pa_GetDeviceInfo(index);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000509 if (!pa_info)
510 return PJMEDIA_EAUD_INVDEV;
511
512 pj_bzero(info, sizeof(*info));
513 strncpy(info->name, pa_info->name, sizeof(info->name));
514 info->name[sizeof(info->name)-1] = '\0';
515 info->input_count = pa_info->maxInputChannels;
516 info->output_count = pa_info->maxOutputChannels;
517 info->default_samples_per_sec = (unsigned)pa_info->defaultSampleRate;
518 strncpy(info->driver, DRIVER_NAME, sizeof(info->driver));
519 info->driver[sizeof(info->driver)-1] = '\0';
520 info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
521 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
522
523 return PJ_SUCCESS;
524}
525
526
527/* API: fill in with default parameter. */
528static pj_status_t pa_default_param(pjmedia_aud_dev_factory *f,
Benny Prijono598b01d2009-02-18 13:55:03 +0000529 unsigned index,
Benny Prijono10454dc2009-02-21 14:21:59 +0000530 pjmedia_aud_param *param)
Benny Prijono2cd64f82009-02-17 19:57:48 +0000531{
532 pjmedia_aud_dev_info adi;
533 pj_status_t status;
534
535 PJ_UNUSED_ARG(f);
536
Benny Prijono598b01d2009-02-18 13:55:03 +0000537 status = pa_get_dev_info(f, index, &adi);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000538 if (status != PJ_SUCCESS)
539 return status;
540
541 pj_bzero(param, sizeof(*param));
542 if (adi.input_count && adi.output_count) {
543 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
Benny Prijono598b01d2009-02-18 13:55:03 +0000544 param->rec_id = index;
545 param->play_id = index;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000546 } else if (adi.input_count) {
547 param->dir = PJMEDIA_DIR_CAPTURE;
Benny Prijono598b01d2009-02-18 13:55:03 +0000548 param->rec_id = index;
Benny Prijono96e74f32009-02-22 12:00:12 +0000549 param->play_id = PJMEDIA_AUD_INVALID_DEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000550 } else if (adi.output_count) {
551 param->dir = PJMEDIA_DIR_PLAYBACK;
Benny Prijono598b01d2009-02-18 13:55:03 +0000552 param->play_id = index;
Benny Prijono96e74f32009-02-22 12:00:12 +0000553 param->rec_id = PJMEDIA_AUD_INVALID_DEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000554 } else {
555 return PJMEDIA_EAUD_INVDEV;
556 }
557
558 param->clock_rate = adi.default_samples_per_sec;
559 param->channel_count = 1;
560 param->samples_per_frame = adi.default_samples_per_sec * 20 / 1000;
561 param->bits_per_sample = 16;
562 param->flags = adi.caps;
563 param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
564 param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
565
566 return PJ_SUCCESS;
567}
568
569
570/* Internal: Get PortAudio default input device ID */
571static int pa_get_default_input_dev(int channel_count)
572{
573 int i, count;
574
575 /* Special for Windows - try to use the DirectSound implementation
576 * first since it provides better latency.
577 */
578#if PJMEDIA_PREFER_DIRECT_SOUND
579 if (Pa_HostApiTypeIdToHostApiIndex(paDirectSound) >= 0) {
580 const PaHostApiInfo *pHI;
581 int index = Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
582 pHI = Pa_GetHostApiInfo(index);
583 if (pHI) {
584 const PaDeviceInfo *paDevInfo = NULL;
585 paDevInfo = Pa_GetDeviceInfo(pHI->defaultInputDevice);
586 if (paDevInfo && paDevInfo->maxInputChannels >= channel_count)
587 return pHI->defaultInputDevice;
588 }
589 }
590#endif
591
592 /* Enumerate the host api's for the default devices, and return
593 * the device with suitable channels.
594 */
595 count = Pa_GetHostApiCount();
596 for (i=0; i < count; ++i) {
597 const PaHostApiInfo *pHAInfo;
598
599 pHAInfo = Pa_GetHostApiInfo(i);
600 if (!pHAInfo)
601 continue;
602
603 if (pHAInfo->defaultInputDevice >= 0) {
604 const PaDeviceInfo *paDevInfo;
605
606 paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultInputDevice);
607
608 if (paDevInfo->maxInputChannels >= channel_count)
609 return pHAInfo->defaultInputDevice;
610 }
611 }
612
613 /* If still no device is found, enumerate all devices */
614 count = Pa_GetDeviceCount();
615 for (i=0; i<count; ++i) {
616 const PaDeviceInfo *paDevInfo;
617
618 paDevInfo = Pa_GetDeviceInfo(i);
619 if (paDevInfo->maxInputChannels >= channel_count)
620 return i;
621 }
622
623 return -1;
624}
625
626/* Internal: Get PortAudio default output device ID */
627static int pa_get_default_output_dev(int channel_count)
628{
629 int i, count;
630
631 /* Special for Windows - try to use the DirectSound implementation
632 * first since it provides better latency.
633 */
634#if PJMEDIA_PREFER_DIRECT_SOUND
635 if (Pa_HostApiTypeIdToHostApiIndex(paDirectSound) >= 0) {
636 const PaHostApiInfo *pHI;
637 int index = Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
638 pHI = Pa_GetHostApiInfo(index);
639 if (pHI) {
640 const PaDeviceInfo *paDevInfo = NULL;
641 paDevInfo = Pa_GetDeviceInfo(pHI->defaultOutputDevice);
642 if (paDevInfo && paDevInfo->maxOutputChannels >= channel_count)
643 return pHI->defaultOutputDevice;
644 }
645 }
646#endif
647
648 /* Enumerate the host api's for the default devices, and return
649 * the device with suitable channels.
650 */
651 count = Pa_GetHostApiCount();
652 for (i=0; i < count; ++i) {
653 const PaHostApiInfo *pHAInfo;
654
655 pHAInfo = Pa_GetHostApiInfo(i);
656 if (!pHAInfo)
657 continue;
658
659 if (pHAInfo->defaultOutputDevice >= 0) {
660 const PaDeviceInfo *paDevInfo;
661
662 paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultOutputDevice);
663
664 if (paDevInfo->maxOutputChannels >= channel_count)
665 return pHAInfo->defaultOutputDevice;
666 }
667 }
668
669 /* If still no device is found, enumerate all devices */
670 count = Pa_GetDeviceCount();
671 for (i=0; i<count; ++i) {
672 const PaDeviceInfo *paDevInfo;
673
674 paDevInfo = Pa_GetDeviceInfo(i);
675 if (paDevInfo->maxOutputChannels >= channel_count)
676 return i;
677 }
678
679 return -1;
680}
681
682
683/* Internal: create capture/recorder stream */
684static pj_status_t create_rec_stream( struct pa_aud_factory *pa,
Benny Prijono10454dc2009-02-21 14:21:59 +0000685 const pjmedia_aud_param *param,
Benny Prijono2cd64f82009-02-17 19:57:48 +0000686 pjmedia_aud_rec_cb rec_cb,
687 void *user_data,
688 pjmedia_aud_stream **p_snd_strm)
689{
690 pj_pool_t *pool;
Benny Prijono10454dc2009-02-21 14:21:59 +0000691 pjmedia_aud_dev_index rec_id;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000692 struct pa_aud_stream *stream;
693 PaStreamParameters inputParam;
694 int sampleFormat;
695 const PaDeviceInfo *paDevInfo = NULL;
696 const PaHostApiInfo *paHostApiInfo = NULL;
697 unsigned paFrames, paRate, paLatency;
698 const PaStreamInfo *paSI;
699 PaError err;
700
Benny Prijono555139d2009-02-19 12:08:19 +0000701 PJ_ASSERT_RETURN(rec_cb && p_snd_strm, PJ_EINVAL);
702
Benny Prijono2cd64f82009-02-17 19:57:48 +0000703 rec_id = param->rec_id;
704 if (rec_id < 0) {
705 rec_id = pa_get_default_input_dev(param->channel_count);
706 if (rec_id < 0) {
707 /* No such device. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000708 return PJMEDIA_EAUD_NODEFDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000709 }
710 }
711
712 paDevInfo = Pa_GetDeviceInfo(rec_id);
713 if (!paDevInfo) {
714 /* Assumed it is "No such device" error. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000715 return PJMEDIA_EAUD_INVDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000716 }
717
718 if (param->bits_per_sample == 8)
719 sampleFormat = paUInt8;
720 else if (param->bits_per_sample == 16)
721 sampleFormat = paInt16;
722 else if (param->bits_per_sample == 32)
723 sampleFormat = paInt32;
724 else
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000725 return PJMEDIA_EAUD_SAMPFORMAT;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000726
727 pool = pj_pool_create(pa->pf, "recstrm", 1024, 1024, NULL);
728 if (!pool)
729 return PJ_ENOMEM;
730
731 stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
732 stream->pool = pool;
733 pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
734 stream->dir = PJMEDIA_DIR_CAPTURE;
735 stream->rec_id = rec_id;
736 stream->play_id = -1;
737 stream->user_data = user_data;
738 stream->samples_per_sec = param->clock_rate;
739 stream->samples_per_frame = param->samples_per_frame;
740 stream->bytes_per_sample = param->bits_per_sample / 8;
741 stream->channel_count = param->channel_count;
742 stream->rec_cb = rec_cb;
743
744 stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool,
745 stream->samples_per_frame * stream->bytes_per_sample);
746 stream->rec_buf_count = 0;
747
748 pj_bzero(&inputParam, sizeof(inputParam));
749 inputParam.device = rec_id;
750 inputParam.channelCount = param->channel_count;
751 inputParam.hostApiSpecificStreamInfo = NULL;
752 inputParam.sampleFormat = sampleFormat;
753 if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
754 inputParam.suggestedLatency = param->input_latency_ms / 1000.0;
755 else
756 inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0;
757
758 paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
759
760 /* Frames in PortAudio is number of samples in a single channel */
761 paFrames = param->samples_per_frame / param->channel_count;
762
763 err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
764 param->clock_rate, paFrames,
765 paClipOff, &PaRecorderCallback, stream );
766 if (err != paNoError) {
767 pj_pool_release(pool);
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000768 return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000769 }
770
771 paSI = Pa_GetStreamInfo(stream->rec_strm);
772 paRate = (unsigned)paSI->sampleRate;
773 paLatency = (unsigned)(paSI->inputLatency * 1000);
774
775 PJ_LOG(5,(THIS_FILE, "Opened device %s (%s) for recording, sample "
776 "rate=%d, ch=%d, "
777 "bits=%d, %d samples per frame, latency=%d ms",
778 paDevInfo->name, paHostApiInfo->name,
779 paRate, param->channel_count,
780 param->bits_per_sample, param->samples_per_frame,
781 paLatency));
782
783 *p_snd_strm = &stream->base;
784 return PJ_SUCCESS;
785}
786
787
788/* Internal: create playback stream */
789static pj_status_t create_play_stream(struct pa_aud_factory *pa,
Benny Prijono10454dc2009-02-21 14:21:59 +0000790 const pjmedia_aud_param *param,
Benny Prijono2cd64f82009-02-17 19:57:48 +0000791 pjmedia_aud_play_cb play_cb,
792 void *user_data,
793 pjmedia_aud_stream **p_snd_strm)
794{
795 pj_pool_t *pool;
Benny Prijono10454dc2009-02-21 14:21:59 +0000796 pjmedia_aud_dev_index play_id;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000797 struct pa_aud_stream *stream;
798 PaStreamParameters outputParam;
799 int sampleFormat;
800 const PaDeviceInfo *paDevInfo = NULL;
801 const PaHostApiInfo *paHostApiInfo = NULL;
802 const PaStreamInfo *paSI;
803 unsigned paFrames, paRate, paLatency;
804 PaError err;
805
Benny Prijono555139d2009-02-19 12:08:19 +0000806 PJ_ASSERT_RETURN(play_cb && p_snd_strm, PJ_EINVAL);
807
Benny Prijono2cd64f82009-02-17 19:57:48 +0000808 play_id = param->play_id;
809 if (play_id < 0) {
810 play_id = pa_get_default_output_dev(param->channel_count);
811 if (play_id < 0) {
812 /* No such device. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000813 return PJMEDIA_EAUD_NODEFDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000814 }
815 }
816
817 paDevInfo = Pa_GetDeviceInfo(play_id);
818 if (!paDevInfo) {
819 /* Assumed it is "No such device" error. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000820 return PJMEDIA_EAUD_INVDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000821 }
822
823 if (param->bits_per_sample == 8)
824 sampleFormat = paUInt8;
825 else if (param->bits_per_sample == 16)
826 sampleFormat = paInt16;
827 else if (param->bits_per_sample == 32)
828 sampleFormat = paInt32;
829 else
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000830 return PJMEDIA_EAUD_SAMPFORMAT;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000831
832 pool = pj_pool_create(pa->pf, "playstrm", 1024, 1024, NULL);
833 if (!pool)
834 return PJ_ENOMEM;
835
836 stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
837 stream->pool = pool;
838 pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
839 stream->dir = PJMEDIA_DIR_PLAYBACK;
840 stream->play_id = play_id;
841 stream->rec_id = -1;
842 stream->user_data = user_data;
843 stream->samples_per_sec = param->clock_rate;
844 stream->samples_per_frame = param->samples_per_frame;
845 stream->bytes_per_sample = param->bits_per_sample / 8;
846 stream->channel_count = param->channel_count;
847 stream->play_cb = play_cb;
848
849 stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool,
850 stream->samples_per_frame *
851 stream->bytes_per_sample);
852 stream->play_buf_count = 0;
853
854 pj_bzero(&outputParam, sizeof(outputParam));
855 outputParam.device = play_id;
856 outputParam.channelCount = param->channel_count;
857 outputParam.hostApiSpecificStreamInfo = NULL;
858 outputParam.sampleFormat = sampleFormat;
859 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
860 outputParam.suggestedLatency=param->output_latency_ms / 1000.0;
861 else
862 outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0;
863
864 paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
865
866 /* Frames in PortAudio is number of samples in a single channel */
867 paFrames = param->samples_per_frame / param->channel_count;
868
869 err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
870 param->clock_rate, paFrames,
871 paClipOff, &PaPlayerCallback, stream );
872 if (err != paNoError) {
873 pj_pool_release(pool);
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000874 return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000875 }
876
877 paSI = Pa_GetStreamInfo(stream->play_strm);
878 paRate = (unsigned)(paSI->sampleRate);
879 paLatency = (unsigned)(paSI->outputLatency * 1000);
880
881 PJ_LOG(5,(THIS_FILE, "Opened device %d: %s(%s) for playing, sample rate=%d"
882 ", ch=%d, "
883 "bits=%d, %d samples per frame, latency=%d ms",
884 play_id, paDevInfo->name, paHostApiInfo->name,
885 paRate, param->channel_count,
886 param->bits_per_sample, param->samples_per_frame,
887 paLatency));
888
889 *p_snd_strm = &stream->base;
890
891 return PJ_SUCCESS;
892}
893
894
895/* Internal: Create both player and recorder stream */
896static pj_status_t create_bidir_stream(struct pa_aud_factory *pa,
Benny Prijono10454dc2009-02-21 14:21:59 +0000897 const pjmedia_aud_param *param,
Benny Prijono2cd64f82009-02-17 19:57:48 +0000898 pjmedia_aud_rec_cb rec_cb,
899 pjmedia_aud_play_cb play_cb,
900 void *user_data,
901 pjmedia_aud_stream **p_snd_strm)
902{
903 pj_pool_t *pool;
Benny Prijono10454dc2009-02-21 14:21:59 +0000904 pjmedia_aud_dev_index rec_id, play_id;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000905 struct pa_aud_stream *stream;
906 PaStream *paStream = NULL;
907 PaStreamParameters inputParam;
908 PaStreamParameters outputParam;
909 int sampleFormat;
910 const PaDeviceInfo *paRecDevInfo = NULL;
911 const PaDeviceInfo *paPlayDevInfo = NULL;
912 const PaHostApiInfo *paRecHostApiInfo = NULL;
913 const PaHostApiInfo *paPlayHostApiInfo = NULL;
914 const PaStreamInfo *paSI;
915 unsigned paFrames, paRate, paInputLatency, paOutputLatency;
916 PaError err;
917
Benny Prijono555139d2009-02-19 12:08:19 +0000918 PJ_ASSERT_RETURN(play_cb && rec_cb && p_snd_strm, PJ_EINVAL);
919
Benny Prijono2cd64f82009-02-17 19:57:48 +0000920 rec_id = param->rec_id;
921 if (rec_id < 0) {
922 rec_id = pa_get_default_input_dev(param->channel_count);
923 if (rec_id < 0) {
924 /* No such device. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000925 return PJMEDIA_EAUD_NODEFDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000926 }
927 }
928
929 paRecDevInfo = Pa_GetDeviceInfo(rec_id);
930 if (!paRecDevInfo) {
931 /* Assumed it is "No such device" error. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000932 return PJMEDIA_EAUD_INVDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000933 }
934
935 play_id = param->play_id;
936 if (play_id < 0) {
937 play_id = pa_get_default_output_dev(param->channel_count);
938 if (play_id < 0) {
939 /* No such device. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000940 return PJMEDIA_EAUD_NODEFDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000941 }
942 }
943
944 paPlayDevInfo = Pa_GetDeviceInfo(play_id);
945 if (!paPlayDevInfo) {
946 /* Assumed it is "No such device" error. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000947 return PJMEDIA_EAUD_INVDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000948 }
949
950
951 if (param->bits_per_sample == 8)
952 sampleFormat = paUInt8;
953 else if (param->bits_per_sample == 16)
954 sampleFormat = paInt16;
955 else if (param->bits_per_sample == 32)
956 sampleFormat = paInt32;
957 else
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000958 return PJMEDIA_EAUD_SAMPFORMAT;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000959
960 pool = pj_pool_create(pa->pf, "sndstream", 1024, 1024, NULL);
961 if (!pool)
962 return PJ_ENOMEM;
963
964 stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
965 stream->pool = pool;
966 pj_strdup2_with_null(pool, &stream->name, paRecDevInfo->name);
967 stream->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
968 stream->play_id = play_id;
969 stream->rec_id = rec_id;
970 stream->user_data = user_data;
971 stream->samples_per_sec = param->clock_rate;
972 stream->samples_per_frame = param->samples_per_frame;
973 stream->bytes_per_sample = param->bits_per_sample / 8;
974 stream->channel_count = param->channel_count;
975 stream->rec_cb = rec_cb;
976 stream->play_cb = play_cb;
977
978 stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool,
979 stream->samples_per_frame * stream->bytes_per_sample);
980 stream->rec_buf_count = 0;
981
982 stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool,
983 stream->samples_per_frame * stream->bytes_per_sample);
984 stream->play_buf_count = 0;
985
986 pj_bzero(&inputParam, sizeof(inputParam));
987 inputParam.device = rec_id;
988 inputParam.channelCount = param->channel_count;
989 inputParam.hostApiSpecificStreamInfo = NULL;
990 inputParam.sampleFormat = sampleFormat;
991 if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
992 inputParam.suggestedLatency = param->input_latency_ms / 1000.0;
993 else
994 inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0;
995
996 paRecHostApiInfo = Pa_GetHostApiInfo(paRecDevInfo->hostApi);
997
998 pj_bzero(&outputParam, sizeof(outputParam));
999 outputParam.device = play_id;
1000 outputParam.channelCount = param->channel_count;
1001 outputParam.hostApiSpecificStreamInfo = NULL;
1002 outputParam.sampleFormat = sampleFormat;
1003 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
1004 outputParam.suggestedLatency=param->output_latency_ms / 1000.0;
1005 else
1006 outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0;
1007
1008 paPlayHostApiInfo = Pa_GetHostApiInfo(paPlayDevInfo->hostApi);
1009
1010 /* Frames in PortAudio is number of samples in a single channel */
1011 paFrames = param->samples_per_frame / param->channel_count;
1012
1013 /* If both input and output are on the same device, open a single stream
1014 * for both input and output.
1015 */
1016 if (rec_id == play_id) {
1017 err = Pa_OpenStream( &paStream, &inputParam, &outputParam,
1018 param->clock_rate, paFrames,
1019 paClipOff, &PaRecorderPlayerCallback, stream );
1020 if (err == paNoError) {
1021 /* Set play stream and record stream to the same stream */
1022 stream->play_strm = stream->rec_strm = paStream;
1023 }
1024 } else {
1025 err = -1;
1026 }
1027
1028 /* .. otherwise if input and output are on the same device, OR if we're
1029 * unable to open a bidirectional stream, then open two separate
1030 * input and output stream.
1031 */
1032 if (paStream == NULL) {
1033 /* Open input stream */
1034 err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
1035 param->clock_rate, paFrames,
1036 paClipOff, &PaRecorderCallback, stream );
1037 if (err == paNoError) {
1038 /* Open output stream */
1039 err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
1040 param->clock_rate, paFrames,
1041 paClipOff, &PaPlayerCallback, stream );
1042 if (err != paNoError)
1043 Pa_CloseStream(stream->rec_strm);
1044 }
1045 }
1046
1047 if (err != paNoError) {
1048 pj_pool_release(pool);
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001049 return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001050 }
1051
1052 paSI = Pa_GetStreamInfo(stream->rec_strm);
1053 paRate = (unsigned)(paSI->sampleRate);
1054 paInputLatency = (unsigned)(paSI->inputLatency * 1000);
1055 paSI = Pa_GetStreamInfo(stream->play_strm);
1056 paOutputLatency = (unsigned)(paSI->outputLatency * 1000);
1057
1058 PJ_LOG(5,(THIS_FILE, "Opened device %s(%s)/%s(%s) for recording and "
1059 "playback, sample rate=%d, ch=%d, "
1060 "bits=%d, %d samples per frame, input latency=%d ms, "
1061 "output latency=%d ms",
1062 paRecDevInfo->name, paRecHostApiInfo->name,
1063 paPlayDevInfo->name, paPlayHostApiInfo->name,
1064 paRate, param->channel_count,
1065 param->bits_per_sample, param->samples_per_frame,
1066 paInputLatency, paOutputLatency));
1067
1068 *p_snd_strm = &stream->base;
1069
1070 return PJ_SUCCESS;
1071}
1072
1073
1074/* API: create stream */
1075static pj_status_t pa_create_stream(pjmedia_aud_dev_factory *f,
Benny Prijono10454dc2009-02-21 14:21:59 +00001076 const pjmedia_aud_param *param,
Benny Prijono2cd64f82009-02-17 19:57:48 +00001077 pjmedia_aud_rec_cb rec_cb,
1078 pjmedia_aud_play_cb play_cb,
1079 void *user_data,
1080 pjmedia_aud_stream **p_aud_strm)
1081{
1082 struct pa_aud_factory *pa = (struct pa_aud_factory*)f;
1083 pj_status_t status;
1084
1085 if (param->dir == PJMEDIA_DIR_CAPTURE) {
1086 status = create_rec_stream(pa, param, rec_cb, user_data, p_aud_strm);
1087 } else if (param->dir == PJMEDIA_DIR_PLAYBACK) {
1088 status = create_play_stream(pa, param, play_cb, user_data, p_aud_strm);
1089 } else if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
1090 status = create_bidir_stream(pa, param, rec_cb, play_cb, user_data,
1091 p_aud_strm);
1092 } else {
1093 return PJ_EINVAL;
1094 }
1095
1096 if (status != PJ_SUCCESS)
1097 return status;
1098
1099 (*p_aud_strm)->op = &pa_strm_op;
1100
1101 return PJ_SUCCESS;
1102}
1103
1104
1105/* API: Get stream parameters */
1106static pj_status_t strm_get_param(pjmedia_aud_stream *s,
Benny Prijono10454dc2009-02-21 14:21:59 +00001107 pjmedia_aud_param *pi)
Benny Prijono2cd64f82009-02-17 19:57:48 +00001108{
1109 struct pa_aud_stream *strm = (struct pa_aud_stream*)s;
1110 const PaStreamInfo *paPlaySI = NULL, *paRecSI = NULL;
1111
1112 PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
1113 PJ_ASSERT_RETURN(strm->play_strm || strm->rec_strm, PJ_EINVALIDOP);
1114
1115 if (strm->play_strm) {
1116 paPlaySI = Pa_GetStreamInfo(strm->play_strm);
1117 }
1118 if (strm->rec_strm) {
1119 paRecSI = Pa_GetStreamInfo(strm->rec_strm);
1120 }
1121
1122 pj_bzero(pi, sizeof(*pi));
1123 pi->dir = strm->dir;
1124 pi->play_id = strm->play_id;
1125 pi->rec_id = strm->rec_id;
1126 pi->clock_rate = (unsigned)(paPlaySI ? paPlaySI->sampleRate :
1127 paRecSI->sampleRate);
1128 pi->channel_count = strm->channel_count;
1129 pi->samples_per_frame = strm->samples_per_frame;
1130 pi->bits_per_sample = strm->bytes_per_sample * 8;
1131 if (paRecSI) {
1132 pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
1133 pi->input_latency_ms = (unsigned)(paRecSI ? paRecSI->inputLatency *
Benny Prijono598b01d2009-02-18 13:55:03 +00001134 1000 : 0);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001135 }
1136 if (paPlaySI) {
1137 pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
1138 pi->output_latency_ms = (unsigned)(paPlaySI? paPlaySI->outputLatency *
Benny Prijono598b01d2009-02-18 13:55:03 +00001139 1000 : 0);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001140 }
1141
1142 return PJ_SUCCESS;
1143}
1144
1145
1146/* API: get capability */
1147static pj_status_t strm_get_cap(pjmedia_aud_stream *s,
1148 pjmedia_aud_dev_cap cap,
1149 void *pval)
1150{
1151 struct pa_aud_stream *strm = (struct pa_aud_stream*)s;
1152
1153 PJ_ASSERT_RETURN(strm && pval, PJ_EINVAL);
1154
1155 if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && strm->rec_strm) {
1156 const PaStreamInfo *si = Pa_GetStreamInfo(strm->rec_strm);
1157 if (!si)
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001158 return PJMEDIA_EAUD_SYSERR;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001159
Benny Prijono598b01d2009-02-18 13:55:03 +00001160 *(unsigned*)pval = (unsigned)(si->inputLatency * 1000);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001161 return PJ_SUCCESS;
1162 } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && strm->play_strm) {
1163 const PaStreamInfo *si = Pa_GetStreamInfo(strm->play_strm);
1164 if (!si)
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001165 return PJMEDIA_EAUD_SYSERR;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001166
Benny Prijono598b01d2009-02-18 13:55:03 +00001167 *(unsigned*)pval = (unsigned)(si->outputLatency * 1000);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001168 return PJ_SUCCESS;
1169 } else {
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001170 return PJMEDIA_EAUD_INVCAP;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001171 }
1172}
1173
1174
1175/* API: set capability */
1176static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
1177 pjmedia_aud_dev_cap cap,
1178 const void *value)
1179{
1180 PJ_UNUSED_ARG(strm);
1181 PJ_UNUSED_ARG(cap);
1182 PJ_UNUSED_ARG(value);
1183
1184 /* Nothing is supported */
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001185 return PJMEDIA_EAUD_INVCAP;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001186}
1187
1188
1189/* API: start stream. */
1190static pj_status_t strm_start(pjmedia_aud_stream *s)
1191{
1192 struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
1193 int err = 0;
1194
1195 PJ_LOG(5,(THIS_FILE, "Starting %s stream..", stream->name.ptr));
1196
1197 if (stream->play_strm)
1198 err = Pa_StartStream(stream->play_strm);
1199
1200 if (err==0 && stream->rec_strm && stream->rec_strm != stream->play_strm) {
1201 err = Pa_StartStream(stream->rec_strm);
1202 if (err != 0)
1203 Pa_StopStream(stream->play_strm);
1204 }
1205
1206 PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
1207
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001208 return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001209}
1210
1211
1212/* API: stop stream. */
1213static pj_status_t strm_stop(pjmedia_aud_stream *s)
1214{
1215 struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
1216 int i, err = 0;
1217
1218 stream->quit_flag = 1;
1219 for (i=0; !stream->rec_thread_exited && i<100; ++i)
1220 pj_thread_sleep(10);
1221 for (i=0; !stream->play_thread_exited && i<100; ++i)
1222 pj_thread_sleep(10);
1223
1224 pj_thread_sleep(1);
1225
1226 PJ_LOG(5,(THIS_FILE, "Stopping stream.."));
1227
1228 if (stream->play_strm)
1229 err = Pa_StopStream(stream->play_strm);
1230
1231 if (stream->rec_strm && stream->rec_strm != stream->play_strm)
1232 err = Pa_StopStream(stream->rec_strm);
1233
1234 stream->play_thread_initialized = 0;
1235 stream->rec_thread_initialized = 0;
1236
1237 PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
1238
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001239 return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001240}
1241
1242
1243/* API: destroy stream. */
1244static pj_status_t strm_destroy(pjmedia_aud_stream *s)
1245{
1246 struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
1247 int i, err = 0;
1248
1249 stream->quit_flag = 1;
1250 for (i=0; !stream->rec_thread_exited && i<100; ++i) {
1251 pj_thread_sleep(1);
1252 }
1253 for (i=0; !stream->play_thread_exited && i<100; ++i) {
1254 pj_thread_sleep(1);
1255 }
1256
1257 PJ_LOG(5,(THIS_FILE, "Closing %.*s: %lu underflow, %lu overflow",
1258 (int)stream->name.slen,
1259 stream->name.ptr,
1260 stream->underflow, stream->overflow));
1261
1262 if (stream->play_strm)
1263 err = Pa_CloseStream(stream->play_strm);
1264
1265 if (stream->rec_strm && stream->rec_strm != stream->play_strm)
1266 err = Pa_CloseStream(stream->rec_strm);
1267
1268 pj_pool_release(stream->pool);
1269
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001270 return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001271}
1272
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001273#endif /* PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO */
1274