blob: 5644e1f1374070bca11d26aae385d1c3a40e8236 [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>
25#include <portaudio.h>
26
Benny Prijono8eeab0b2009-03-04 19:00:28 +000027#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
28
29
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
33struct pa_aud_factory
34{
35 pjmedia_aud_dev_factory base;
36 pj_pool_factory *pf;
37 pj_pool_t *pool;
38};
39
40
41/*
42 * Sound stream descriptor.
43 * This struct may be used for both unidirectional or bidirectional sound
44 * streams.
45 */
46struct pa_aud_stream
47{
48 pjmedia_aud_stream base;
49
50 pj_pool_t *pool;
51 pj_str_t name;
52 pjmedia_dir dir;
53 int play_id;
54 int rec_id;
55 int bytes_per_sample;
56 pj_uint32_t samples_per_sec;
57 unsigned samples_per_frame;
58 int channel_count;
59
60 PaStream *rec_strm;
61 PaStream *play_strm;
62
63 void *user_data;
64 pjmedia_aud_rec_cb rec_cb;
65 pjmedia_aud_play_cb play_cb;
66
67 pj_timestamp play_timestamp;
68 pj_timestamp rec_timestamp;
69 pj_uint32_t underflow;
70 pj_uint32_t overflow;
71
72 pj_bool_t quit_flag;
73
74 pj_bool_t rec_thread_exited;
75 pj_bool_t rec_thread_initialized;
76 pj_thread_desc rec_thread_desc;
77 pj_thread_t *rec_thread;
78
79 pj_bool_t play_thread_exited;
80 pj_bool_t play_thread_initialized;
81 pj_thread_desc play_thread_desc;
82 pj_thread_t *play_thread;
83
84 /* Sometime the record callback does not return framesize as configured
85 * (e.g: in OSS), while this module must guarantee returning framesize
86 * as configured in the creation settings. In this case, we need a buffer
87 * for the recorded samples.
88 */
89 pj_int16_t *rec_buf;
90 unsigned rec_buf_count;
91
92 /* Sometime the player callback does not request framesize as configured
93 * (e.g: in Linux OSS) while sound device will always get samples from
94 * the other component as many as configured samples_per_frame.
95 */
96 pj_int16_t *play_buf;
97 unsigned play_buf_count;
98};
99
100
101/* Factory prototypes */
102static pj_status_t pa_init(pjmedia_aud_dev_factory *f);
103static pj_status_t pa_destroy(pjmedia_aud_dev_factory *f);
104static unsigned pa_get_dev_count(pjmedia_aud_dev_factory *f);
105static pj_status_t pa_get_dev_info(pjmedia_aud_dev_factory *f,
Benny Prijono598b01d2009-02-18 13:55:03 +0000106 unsigned index,
Benny Prijono2cd64f82009-02-17 19:57:48 +0000107 pjmedia_aud_dev_info *info);
108static pj_status_t pa_default_param(pjmedia_aud_dev_factory *f,
Benny Prijono598b01d2009-02-18 13:55:03 +0000109 unsigned index,
Benny Prijono10454dc2009-02-21 14:21:59 +0000110 pjmedia_aud_param *param);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000111static pj_status_t pa_create_stream(pjmedia_aud_dev_factory *f,
Benny Prijono10454dc2009-02-21 14:21:59 +0000112 const pjmedia_aud_param *param,
Benny Prijono2cd64f82009-02-17 19:57:48 +0000113 pjmedia_aud_rec_cb rec_cb,
114 pjmedia_aud_play_cb play_cb,
115 void *user_data,
116 pjmedia_aud_stream **p_aud_strm);
117
118/* Stream prototypes */
119static pj_status_t strm_get_param(pjmedia_aud_stream *strm,
Benny Prijono10454dc2009-02-21 14:21:59 +0000120 pjmedia_aud_param *param);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000121static pj_status_t strm_get_cap(pjmedia_aud_stream *strm,
122 pjmedia_aud_dev_cap cap,
123 void *value);
124static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
125 pjmedia_aud_dev_cap cap,
126 const void *value);
127static pj_status_t strm_start(pjmedia_aud_stream *strm);
128static pj_status_t strm_stop(pjmedia_aud_stream *strm);
129static pj_status_t strm_destroy(pjmedia_aud_stream *strm);
130
131
132static pjmedia_aud_dev_factory_op pa_op =
133{
134 &pa_init,
135 &pa_destroy,
136 &pa_get_dev_count,
137 &pa_get_dev_info,
138 &pa_default_param,
139 &pa_create_stream
140};
141
142static pjmedia_aud_stream_op pa_strm_op =
143{
144 &strm_get_param,
145 &strm_get_cap,
146 &strm_set_cap,
147 &strm_start,
148 &strm_stop,
149 &strm_destroy
150};
151
152
153
154static int PaRecorderCallback(const void *input,
155 void *output,
156 unsigned long frameCount,
157 const PaStreamCallbackTimeInfo* timeInfo,
158 PaStreamCallbackFlags statusFlags,
159 void *userData )
160{
161 struct pa_aud_stream *stream = (struct pa_aud_stream*) userData;
162 pj_status_t status = 0;
163 unsigned nsamples;
164
165 PJ_UNUSED_ARG(output);
166 PJ_UNUSED_ARG(timeInfo);
167
168 if (stream->quit_flag)
169 goto on_break;
170
171 if (input == NULL)
172 return paContinue;
173
174 /* Known cases of callback's thread:
175 * - The thread may be changed in the middle of a session, e.g: in MacOS
176 * it happens when plugging/unplugging headphone.
177 * - The same thread may be reused in consecutive sessions. The first
178 * session will leave TLS set, but release the TLS data address,
179 * so the second session must re-register the callback's thread.
180 */
181 if (stream->rec_thread_initialized == 0 || !pj_thread_is_registered())
182 {
183 status = pj_thread_register("pa_rec", stream->rec_thread_desc,
184 &stream->rec_thread);
185 stream->rec_thread_initialized = 1;
186 PJ_LOG(5,(THIS_FILE, "Recorder thread started"));
187 }
188
189 if (statusFlags & paInputUnderflow)
190 ++stream->underflow;
191 if (statusFlags & paInputOverflow)
192 ++stream->overflow;
193
Benny Prijono2cd64f82009-02-17 19:57:48 +0000194 /* Calculate number of samples we've got */
195 nsamples = frameCount * stream->channel_count + stream->rec_buf_count;
196
197 if (nsamples >= stream->samples_per_frame)
198 {
199 /* If buffer is not empty, combine the buffer with the just incoming
200 * samples, then call put_frame.
201 */
202 if (stream->rec_buf_count) {
203 unsigned chunk_count = 0;
Benny Prijono598b01d2009-02-18 13:55:03 +0000204 pjmedia_frame frame;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000205
206 chunk_count = stream->samples_per_frame - stream->rec_buf_count;
207 pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count,
208 (pj_int16_t*)input, chunk_count);
Benny Prijono598b01d2009-02-18 13:55:03 +0000209
210 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
211 frame.buf = (void*) stream->rec_buf;
212 frame.size = stream->samples_per_frame * stream->bytes_per_sample;
213 frame.timestamp.u64 = stream->rec_timestamp.u64;
214 frame.bit_info = 0;
215
216 status = (*stream->rec_cb)(stream->user_data, &frame);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000217
218 input = (pj_int16_t*) input + chunk_count;
219 nsamples -= stream->samples_per_frame;
220 stream->rec_buf_count = 0;
Benny Prijono598b01d2009-02-18 13:55:03 +0000221 stream->rec_timestamp.u64 += stream->samples_per_frame /
222 stream->channel_count;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000223 }
224
225 /* Give all frames we have */
226 while (nsamples >= stream->samples_per_frame && status == 0) {
Benny Prijono598b01d2009-02-18 13:55:03 +0000227 pjmedia_frame frame;
228
229 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
230 frame.buf = (void*) input;
231 frame.size = stream->samples_per_frame * stream->bytes_per_sample;
232 frame.timestamp.u64 = stream->rec_timestamp.u64;
233 frame.bit_info = 0;
234
235 status = (*stream->rec_cb)(stream->user_data, &frame);
236
Benny Prijono2cd64f82009-02-17 19:57:48 +0000237 input = (pj_int16_t*) input + stream->samples_per_frame;
238 nsamples -= stream->samples_per_frame;
Benny Prijono598b01d2009-02-18 13:55:03 +0000239 stream->rec_timestamp.u64 += stream->samples_per_frame /
240 stream->channel_count;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000241 }
242
243 /* Store the remaining samples into the buffer */
244 if (nsamples && status == 0) {
245 stream->rec_buf_count = nsamples;
246 pjmedia_copy_samples(stream->rec_buf, (pj_int16_t*)input,
247 nsamples);
248 }
249
250 } else {
251 /* Not enough samples, let's just store them in the buffer */
252 pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count,
253 (pj_int16_t*)input,
254 frameCount * stream->channel_count);
255 stream->rec_buf_count += frameCount * stream->channel_count;
256 }
257
258 if (status==0)
259 return paContinue;
260
261on_break:
262 stream->rec_thread_exited = 1;
263 return paAbort;
264}
265
266static int PaPlayerCallback( const void *input,
267 void *output,
268 unsigned long frameCount,
269 const PaStreamCallbackTimeInfo* timeInfo,
270 PaStreamCallbackFlags statusFlags,
271 void *userData )
272{
273 struct pa_aud_stream *stream = (struct pa_aud_stream*) userData;
274 pj_status_t status = 0;
275 unsigned nsamples_req = frameCount * stream->channel_count;
276
277 PJ_UNUSED_ARG(input);
278 PJ_UNUSED_ARG(timeInfo);
279
280 if (stream->quit_flag)
281 goto on_break;
282
283 if (output == NULL)
284 return paContinue;
285
286 /* Known cases of callback's thread:
287 * - The thread may be changed in the middle of a session, e.g: in MacOS
288 * it happens when plugging/unplugging headphone.
289 * - The same thread may be reused in consecutive sessions. The first
290 * session will leave TLS set, but release the TLS data address,
291 * so the second session must re-register the callback's thread.
292 */
293 if (stream->play_thread_initialized == 0 || !pj_thread_is_registered())
294 {
295 status = pj_thread_register("portaudio", stream->play_thread_desc,
296 &stream->play_thread);
297 stream->play_thread_initialized = 1;
298 PJ_LOG(5,(THIS_FILE, "Player thread started"));
299 }
300
301 if (statusFlags & paOutputUnderflow)
302 ++stream->underflow;
303 if (statusFlags & paOutputOverflow)
304 ++stream->overflow;
305
Benny Prijono2cd64f82009-02-17 19:57:48 +0000306
307 /* Check if any buffered samples */
308 if (stream->play_buf_count) {
309 /* samples buffered >= requested by sound device */
310 if (stream->play_buf_count >= nsamples_req) {
311 pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
312 nsamples_req);
313 stream->play_buf_count -= nsamples_req;
314 pjmedia_move_samples(stream->play_buf,
315 stream->play_buf + nsamples_req,
316 stream->play_buf_count);
317 nsamples_req = 0;
318
319 return paContinue;
320 }
321
322 /* samples buffered < requested by sound device */
323 pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
324 stream->play_buf_count);
325 nsamples_req -= stream->play_buf_count;
326 output = (pj_int16_t*)output + stream->play_buf_count;
327 stream->play_buf_count = 0;
328 }
329
330 /* Fill output buffer as requested */
331 while (nsamples_req && status == 0) {
332 if (nsamples_req >= stream->samples_per_frame) {
Benny Prijono598b01d2009-02-18 13:55:03 +0000333 pjmedia_frame frame;
334
335 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
336 frame.buf = output;
337 frame.size = stream->samples_per_frame * stream->bytes_per_sample;
338 frame.timestamp.u64 = stream->play_timestamp.u64;
339 frame.bit_info = 0;
340
341 status = (*stream->play_cb)(stream->user_data, &frame);
342 if (status != PJ_SUCCESS)
343 goto on_break;
344
345 if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
346 pj_bzero(frame.buf, frame.size);
347
Benny Prijono2cd64f82009-02-17 19:57:48 +0000348 nsamples_req -= stream->samples_per_frame;
349 output = (pj_int16_t*)output + stream->samples_per_frame;
350 } else {
Benny Prijono598b01d2009-02-18 13:55:03 +0000351 pjmedia_frame frame;
352
353 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
354 frame.buf = stream->play_buf;
355 frame.size = stream->samples_per_frame * stream->bytes_per_sample;
356 frame.timestamp.u64 = stream->play_timestamp.u64;
357 frame.bit_info = 0;
358
359 status = (*stream->play_cb)(stream->user_data, &frame);
360 if (status != PJ_SUCCESS)
361 goto on_break;
362
363 if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
364 pj_bzero(frame.buf, frame.size);
365
Benny Prijono2cd64f82009-02-17 19:57:48 +0000366 pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf,
367 nsamples_req);
368 stream->play_buf_count = stream->samples_per_frame - nsamples_req;
369 pjmedia_move_samples(stream->play_buf,
370 stream->play_buf+nsamples_req,
371 stream->play_buf_count);
372 nsamples_req = 0;
373 }
Benny Prijono598b01d2009-02-18 13:55:03 +0000374
375 stream->play_timestamp.u64 += stream->samples_per_frame /
376 stream->channel_count;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000377 }
378
379 if (status==0)
380 return paContinue;
381
382on_break:
383 stream->play_thread_exited = 1;
384 return paAbort;
385}
386
387
388static int PaRecorderPlayerCallback( const void *input,
389 void *output,
390 unsigned long frameCount,
391 const PaStreamCallbackTimeInfo* timeInfo,
392 PaStreamCallbackFlags statusFlags,
393 void *userData )
394{
395 int rc;
396
397 rc = PaRecorderCallback(input, output, frameCount, timeInfo,
398 statusFlags, userData);
399 if (rc != paContinue)
400 return rc;
401
402 rc = PaPlayerCallback(input, output, frameCount, timeInfo,
403 statusFlags, userData);
404 return rc;
405}
406
407/* Logging callback from PA */
408static void pa_log_cb(const char *log)
409{
410 PJ_LOG(5,(THIS_FILE, "PA message: %s", log));
411}
412
413/* We should include pa_debugprint.h for this, but the header
414 * is not available publicly. :(
415 */
416typedef void (*PaUtilLogCallback ) (const char *log);
417void PaUtil_SetDebugPrintFunction(PaUtilLogCallback cb);
418
419
420/*
421 * Init PortAudio audio driver.
422 */
423pjmedia_aud_dev_factory* pjmedia_pa_factory(pj_pool_factory *pf)
424{
425 struct pa_aud_factory *f;
426 pj_pool_t *pool;
427
428 pool = pj_pool_create(pf, "portaudio", 64, 64, NULL);
429 f = PJ_POOL_ZALLOC_T(pool, struct pa_aud_factory);
430 f->pf = pf;
431 f->pool = pool;
432 f->base.op = &pa_op;
433
434 return &f->base;
435}
436
437
438/* API: Init factory */
439static pj_status_t pa_init(pjmedia_aud_dev_factory *f)
440{
441 int err;
442
443 PJ_UNUSED_ARG(f);
444
445 PaUtil_SetDebugPrintFunction(&pa_log_cb);
446
447 err = Pa_Initialize();
448
449 PJ_LOG(4,(THIS_FILE,
450 "PortAudio sound library initialized, status=%d", err));
451 PJ_LOG(4,(THIS_FILE, "PortAudio host api count=%d",
452 Pa_GetHostApiCount()));
453 PJ_LOG(4,(THIS_FILE, "Sound device count=%d",
454 pa_get_dev_count(f)));
455
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000456 return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000457}
458
459
460/* API: Destroy factory */
461static pj_status_t pa_destroy(pjmedia_aud_dev_factory *f)
462{
463 struct pa_aud_factory *pa = (struct pa_aud_factory*)f;
464 pj_pool_t *pool;
465 int err;
466
467 PJ_LOG(4,(THIS_FILE, "PortAudio sound library shutting down.."));
468
469 err = Pa_Terminate();
470
471 pool = pa->pool;
472 pa->pool = NULL;
473 pj_pool_release(pool);
474
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000475 return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000476}
477
478
479/* API: Get device count. */
480static unsigned pa_get_dev_count(pjmedia_aud_dev_factory *f)
481{
482 int count = Pa_GetDeviceCount();
483 PJ_UNUSED_ARG(f);
484 return count < 0 ? 0 : count;
485}
486
487
488/* API: Get device info. */
489static pj_status_t pa_get_dev_info(pjmedia_aud_dev_factory *f,
Benny Prijono598b01d2009-02-18 13:55:03 +0000490 unsigned index,
Benny Prijono2cd64f82009-02-17 19:57:48 +0000491 pjmedia_aud_dev_info *info)
492{
493 const PaDeviceInfo *pa_info;
494
495 PJ_UNUSED_ARG(f);
496
Benny Prijono598b01d2009-02-18 13:55:03 +0000497 pa_info = Pa_GetDeviceInfo(index);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000498 if (!pa_info)
499 return PJMEDIA_EAUD_INVDEV;
500
501 pj_bzero(info, sizeof(*info));
502 strncpy(info->name, pa_info->name, sizeof(info->name));
503 info->name[sizeof(info->name)-1] = '\0';
504 info->input_count = pa_info->maxInputChannels;
505 info->output_count = pa_info->maxOutputChannels;
506 info->default_samples_per_sec = (unsigned)pa_info->defaultSampleRate;
507 strncpy(info->driver, DRIVER_NAME, sizeof(info->driver));
508 info->driver[sizeof(info->driver)-1] = '\0';
509 info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
510 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
511
512 return PJ_SUCCESS;
513}
514
515
516/* API: fill in with default parameter. */
517static pj_status_t pa_default_param(pjmedia_aud_dev_factory *f,
Benny Prijono598b01d2009-02-18 13:55:03 +0000518 unsigned index,
Benny Prijono10454dc2009-02-21 14:21:59 +0000519 pjmedia_aud_param *param)
Benny Prijono2cd64f82009-02-17 19:57:48 +0000520{
521 pjmedia_aud_dev_info adi;
522 pj_status_t status;
523
524 PJ_UNUSED_ARG(f);
525
Benny Prijono598b01d2009-02-18 13:55:03 +0000526 status = pa_get_dev_info(f, index, &adi);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000527 if (status != PJ_SUCCESS)
528 return status;
529
530 pj_bzero(param, sizeof(*param));
531 if (adi.input_count && adi.output_count) {
532 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
Benny Prijono598b01d2009-02-18 13:55:03 +0000533 param->rec_id = index;
534 param->play_id = index;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000535 } else if (adi.input_count) {
536 param->dir = PJMEDIA_DIR_CAPTURE;
Benny Prijono598b01d2009-02-18 13:55:03 +0000537 param->rec_id = index;
Benny Prijono96e74f32009-02-22 12:00:12 +0000538 param->play_id = PJMEDIA_AUD_INVALID_DEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000539 } else if (adi.output_count) {
540 param->dir = PJMEDIA_DIR_PLAYBACK;
Benny Prijono598b01d2009-02-18 13:55:03 +0000541 param->play_id = index;
Benny Prijono96e74f32009-02-22 12:00:12 +0000542 param->rec_id = PJMEDIA_AUD_INVALID_DEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000543 } else {
544 return PJMEDIA_EAUD_INVDEV;
545 }
546
547 param->clock_rate = adi.default_samples_per_sec;
548 param->channel_count = 1;
549 param->samples_per_frame = adi.default_samples_per_sec * 20 / 1000;
550 param->bits_per_sample = 16;
551 param->flags = adi.caps;
552 param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
553 param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
554
555 return PJ_SUCCESS;
556}
557
558
559/* Internal: Get PortAudio default input device ID */
560static int pa_get_default_input_dev(int channel_count)
561{
562 int i, count;
563
564 /* Special for Windows - try to use the DirectSound implementation
565 * first since it provides better latency.
566 */
567#if PJMEDIA_PREFER_DIRECT_SOUND
568 if (Pa_HostApiTypeIdToHostApiIndex(paDirectSound) >= 0) {
569 const PaHostApiInfo *pHI;
570 int index = Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
571 pHI = Pa_GetHostApiInfo(index);
572 if (pHI) {
573 const PaDeviceInfo *paDevInfo = NULL;
574 paDevInfo = Pa_GetDeviceInfo(pHI->defaultInputDevice);
575 if (paDevInfo && paDevInfo->maxInputChannels >= channel_count)
576 return pHI->defaultInputDevice;
577 }
578 }
579#endif
580
581 /* Enumerate the host api's for the default devices, and return
582 * the device with suitable channels.
583 */
584 count = Pa_GetHostApiCount();
585 for (i=0; i < count; ++i) {
586 const PaHostApiInfo *pHAInfo;
587
588 pHAInfo = Pa_GetHostApiInfo(i);
589 if (!pHAInfo)
590 continue;
591
592 if (pHAInfo->defaultInputDevice >= 0) {
593 const PaDeviceInfo *paDevInfo;
594
595 paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultInputDevice);
596
597 if (paDevInfo->maxInputChannels >= channel_count)
598 return pHAInfo->defaultInputDevice;
599 }
600 }
601
602 /* If still no device is found, enumerate all devices */
603 count = Pa_GetDeviceCount();
604 for (i=0; i<count; ++i) {
605 const PaDeviceInfo *paDevInfo;
606
607 paDevInfo = Pa_GetDeviceInfo(i);
608 if (paDevInfo->maxInputChannels >= channel_count)
609 return i;
610 }
611
612 return -1;
613}
614
615/* Internal: Get PortAudio default output device ID */
616static int pa_get_default_output_dev(int channel_count)
617{
618 int i, count;
619
620 /* Special for Windows - try to use the DirectSound implementation
621 * first since it provides better latency.
622 */
623#if PJMEDIA_PREFER_DIRECT_SOUND
624 if (Pa_HostApiTypeIdToHostApiIndex(paDirectSound) >= 0) {
625 const PaHostApiInfo *pHI;
626 int index = Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
627 pHI = Pa_GetHostApiInfo(index);
628 if (pHI) {
629 const PaDeviceInfo *paDevInfo = NULL;
630 paDevInfo = Pa_GetDeviceInfo(pHI->defaultOutputDevice);
631 if (paDevInfo && paDevInfo->maxOutputChannels >= channel_count)
632 return pHI->defaultOutputDevice;
633 }
634 }
635#endif
636
637 /* Enumerate the host api's for the default devices, and return
638 * the device with suitable channels.
639 */
640 count = Pa_GetHostApiCount();
641 for (i=0; i < count; ++i) {
642 const PaHostApiInfo *pHAInfo;
643
644 pHAInfo = Pa_GetHostApiInfo(i);
645 if (!pHAInfo)
646 continue;
647
648 if (pHAInfo->defaultOutputDevice >= 0) {
649 const PaDeviceInfo *paDevInfo;
650
651 paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultOutputDevice);
652
653 if (paDevInfo->maxOutputChannels >= channel_count)
654 return pHAInfo->defaultOutputDevice;
655 }
656 }
657
658 /* If still no device is found, enumerate all devices */
659 count = Pa_GetDeviceCount();
660 for (i=0; i<count; ++i) {
661 const PaDeviceInfo *paDevInfo;
662
663 paDevInfo = Pa_GetDeviceInfo(i);
664 if (paDevInfo->maxOutputChannels >= channel_count)
665 return i;
666 }
667
668 return -1;
669}
670
671
672/* Internal: create capture/recorder stream */
673static pj_status_t create_rec_stream( struct pa_aud_factory *pa,
Benny Prijono10454dc2009-02-21 14:21:59 +0000674 const pjmedia_aud_param *param,
Benny Prijono2cd64f82009-02-17 19:57:48 +0000675 pjmedia_aud_rec_cb rec_cb,
676 void *user_data,
677 pjmedia_aud_stream **p_snd_strm)
678{
679 pj_pool_t *pool;
Benny Prijono10454dc2009-02-21 14:21:59 +0000680 pjmedia_aud_dev_index rec_id;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000681 struct pa_aud_stream *stream;
682 PaStreamParameters inputParam;
683 int sampleFormat;
684 const PaDeviceInfo *paDevInfo = NULL;
685 const PaHostApiInfo *paHostApiInfo = NULL;
686 unsigned paFrames, paRate, paLatency;
687 const PaStreamInfo *paSI;
688 PaError err;
689
Benny Prijono555139d2009-02-19 12:08:19 +0000690 PJ_ASSERT_RETURN(rec_cb && p_snd_strm, PJ_EINVAL);
691
Benny Prijono2cd64f82009-02-17 19:57:48 +0000692 rec_id = param->rec_id;
693 if (rec_id < 0) {
694 rec_id = pa_get_default_input_dev(param->channel_count);
695 if (rec_id < 0) {
696 /* No such device. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000697 return PJMEDIA_EAUD_NODEFDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000698 }
699 }
700
701 paDevInfo = Pa_GetDeviceInfo(rec_id);
702 if (!paDevInfo) {
703 /* Assumed it is "No such device" error. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000704 return PJMEDIA_EAUD_INVDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000705 }
706
707 if (param->bits_per_sample == 8)
708 sampleFormat = paUInt8;
709 else if (param->bits_per_sample == 16)
710 sampleFormat = paInt16;
711 else if (param->bits_per_sample == 32)
712 sampleFormat = paInt32;
713 else
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000714 return PJMEDIA_EAUD_SAMPFORMAT;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000715
716 pool = pj_pool_create(pa->pf, "recstrm", 1024, 1024, NULL);
717 if (!pool)
718 return PJ_ENOMEM;
719
720 stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
721 stream->pool = pool;
722 pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
723 stream->dir = PJMEDIA_DIR_CAPTURE;
724 stream->rec_id = rec_id;
725 stream->play_id = -1;
726 stream->user_data = user_data;
727 stream->samples_per_sec = param->clock_rate;
728 stream->samples_per_frame = param->samples_per_frame;
729 stream->bytes_per_sample = param->bits_per_sample / 8;
730 stream->channel_count = param->channel_count;
731 stream->rec_cb = rec_cb;
732
733 stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool,
734 stream->samples_per_frame * stream->bytes_per_sample);
735 stream->rec_buf_count = 0;
736
737 pj_bzero(&inputParam, sizeof(inputParam));
738 inputParam.device = rec_id;
739 inputParam.channelCount = param->channel_count;
740 inputParam.hostApiSpecificStreamInfo = NULL;
741 inputParam.sampleFormat = sampleFormat;
742 if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
743 inputParam.suggestedLatency = param->input_latency_ms / 1000.0;
744 else
745 inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0;
746
747 paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
748
749 /* Frames in PortAudio is number of samples in a single channel */
750 paFrames = param->samples_per_frame / param->channel_count;
751
752 err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
753 param->clock_rate, paFrames,
754 paClipOff, &PaRecorderCallback, stream );
755 if (err != paNoError) {
756 pj_pool_release(pool);
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000757 return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000758 }
759
760 paSI = Pa_GetStreamInfo(stream->rec_strm);
761 paRate = (unsigned)paSI->sampleRate;
762 paLatency = (unsigned)(paSI->inputLatency * 1000);
763
764 PJ_LOG(5,(THIS_FILE, "Opened device %s (%s) for recording, sample "
765 "rate=%d, ch=%d, "
766 "bits=%d, %d samples per frame, latency=%d ms",
767 paDevInfo->name, paHostApiInfo->name,
768 paRate, param->channel_count,
769 param->bits_per_sample, param->samples_per_frame,
770 paLatency));
771
772 *p_snd_strm = &stream->base;
773 return PJ_SUCCESS;
774}
775
776
777/* Internal: create playback stream */
778static pj_status_t create_play_stream(struct pa_aud_factory *pa,
Benny Prijono10454dc2009-02-21 14:21:59 +0000779 const pjmedia_aud_param *param,
Benny Prijono2cd64f82009-02-17 19:57:48 +0000780 pjmedia_aud_play_cb play_cb,
781 void *user_data,
782 pjmedia_aud_stream **p_snd_strm)
783{
784 pj_pool_t *pool;
Benny Prijono10454dc2009-02-21 14:21:59 +0000785 pjmedia_aud_dev_index play_id;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000786 struct pa_aud_stream *stream;
787 PaStreamParameters outputParam;
788 int sampleFormat;
789 const PaDeviceInfo *paDevInfo = NULL;
790 const PaHostApiInfo *paHostApiInfo = NULL;
791 const PaStreamInfo *paSI;
792 unsigned paFrames, paRate, paLatency;
793 PaError err;
794
Benny Prijono555139d2009-02-19 12:08:19 +0000795 PJ_ASSERT_RETURN(play_cb && p_snd_strm, PJ_EINVAL);
796
Benny Prijono2cd64f82009-02-17 19:57:48 +0000797 play_id = param->play_id;
798 if (play_id < 0) {
799 play_id = pa_get_default_output_dev(param->channel_count);
800 if (play_id < 0) {
801 /* No such device. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000802 return PJMEDIA_EAUD_NODEFDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000803 }
804 }
805
806 paDevInfo = Pa_GetDeviceInfo(play_id);
807 if (!paDevInfo) {
808 /* Assumed it is "No such device" error. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000809 return PJMEDIA_EAUD_INVDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000810 }
811
812 if (param->bits_per_sample == 8)
813 sampleFormat = paUInt8;
814 else if (param->bits_per_sample == 16)
815 sampleFormat = paInt16;
816 else if (param->bits_per_sample == 32)
817 sampleFormat = paInt32;
818 else
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000819 return PJMEDIA_EAUD_SAMPFORMAT;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000820
821 pool = pj_pool_create(pa->pf, "playstrm", 1024, 1024, NULL);
822 if (!pool)
823 return PJ_ENOMEM;
824
825 stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
826 stream->pool = pool;
827 pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
828 stream->dir = PJMEDIA_DIR_PLAYBACK;
829 stream->play_id = play_id;
830 stream->rec_id = -1;
831 stream->user_data = user_data;
832 stream->samples_per_sec = param->clock_rate;
833 stream->samples_per_frame = param->samples_per_frame;
834 stream->bytes_per_sample = param->bits_per_sample / 8;
835 stream->channel_count = param->channel_count;
836 stream->play_cb = play_cb;
837
838 stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool,
839 stream->samples_per_frame *
840 stream->bytes_per_sample);
841 stream->play_buf_count = 0;
842
843 pj_bzero(&outputParam, sizeof(outputParam));
844 outputParam.device = play_id;
845 outputParam.channelCount = param->channel_count;
846 outputParam.hostApiSpecificStreamInfo = NULL;
847 outputParam.sampleFormat = sampleFormat;
848 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
849 outputParam.suggestedLatency=param->output_latency_ms / 1000.0;
850 else
851 outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0;
852
853 paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
854
855 /* Frames in PortAudio is number of samples in a single channel */
856 paFrames = param->samples_per_frame / param->channel_count;
857
858 err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
859 param->clock_rate, paFrames,
860 paClipOff, &PaPlayerCallback, stream );
861 if (err != paNoError) {
862 pj_pool_release(pool);
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000863 return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
Benny Prijono2cd64f82009-02-17 19:57:48 +0000864 }
865
866 paSI = Pa_GetStreamInfo(stream->play_strm);
867 paRate = (unsigned)(paSI->sampleRate);
868 paLatency = (unsigned)(paSI->outputLatency * 1000);
869
870 PJ_LOG(5,(THIS_FILE, "Opened device %d: %s(%s) for playing, sample rate=%d"
871 ", ch=%d, "
872 "bits=%d, %d samples per frame, latency=%d ms",
873 play_id, paDevInfo->name, paHostApiInfo->name,
874 paRate, param->channel_count,
875 param->bits_per_sample, param->samples_per_frame,
876 paLatency));
877
878 *p_snd_strm = &stream->base;
879
880 return PJ_SUCCESS;
881}
882
883
884/* Internal: Create both player and recorder stream */
885static pj_status_t create_bidir_stream(struct pa_aud_factory *pa,
Benny Prijono10454dc2009-02-21 14:21:59 +0000886 const pjmedia_aud_param *param,
Benny Prijono2cd64f82009-02-17 19:57:48 +0000887 pjmedia_aud_rec_cb rec_cb,
888 pjmedia_aud_play_cb play_cb,
889 void *user_data,
890 pjmedia_aud_stream **p_snd_strm)
891{
892 pj_pool_t *pool;
Benny Prijono10454dc2009-02-21 14:21:59 +0000893 pjmedia_aud_dev_index rec_id, play_id;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000894 struct pa_aud_stream *stream;
895 PaStream *paStream = NULL;
896 PaStreamParameters inputParam;
897 PaStreamParameters outputParam;
898 int sampleFormat;
899 const PaDeviceInfo *paRecDevInfo = NULL;
900 const PaDeviceInfo *paPlayDevInfo = NULL;
901 const PaHostApiInfo *paRecHostApiInfo = NULL;
902 const PaHostApiInfo *paPlayHostApiInfo = NULL;
903 const PaStreamInfo *paSI;
904 unsigned paFrames, paRate, paInputLatency, paOutputLatency;
905 PaError err;
906
Benny Prijono555139d2009-02-19 12:08:19 +0000907 PJ_ASSERT_RETURN(play_cb && rec_cb && p_snd_strm, PJ_EINVAL);
908
Benny Prijono2cd64f82009-02-17 19:57:48 +0000909 rec_id = param->rec_id;
910 if (rec_id < 0) {
911 rec_id = pa_get_default_input_dev(param->channel_count);
912 if (rec_id < 0) {
913 /* No such device. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000914 return PJMEDIA_EAUD_NODEFDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000915 }
916 }
917
918 paRecDevInfo = Pa_GetDeviceInfo(rec_id);
919 if (!paRecDevInfo) {
920 /* Assumed it is "No such device" error. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000921 return PJMEDIA_EAUD_INVDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000922 }
923
924 play_id = param->play_id;
925 if (play_id < 0) {
926 play_id = pa_get_default_output_dev(param->channel_count);
927 if (play_id < 0) {
928 /* No such device. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000929 return PJMEDIA_EAUD_NODEFDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000930 }
931 }
932
933 paPlayDevInfo = Pa_GetDeviceInfo(play_id);
934 if (!paPlayDevInfo) {
935 /* Assumed it is "No such device" error. */
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000936 return PJMEDIA_EAUD_INVDEV;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000937 }
938
939
940 if (param->bits_per_sample == 8)
941 sampleFormat = paUInt8;
942 else if (param->bits_per_sample == 16)
943 sampleFormat = paInt16;
944 else if (param->bits_per_sample == 32)
945 sampleFormat = paInt32;
946 else
Benny Prijono8eeab0b2009-03-04 19:00:28 +0000947 return PJMEDIA_EAUD_SAMPFORMAT;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000948
949 pool = pj_pool_create(pa->pf, "sndstream", 1024, 1024, NULL);
950 if (!pool)
951 return PJ_ENOMEM;
952
953 stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
954 stream->pool = pool;
955 pj_strdup2_with_null(pool, &stream->name, paRecDevInfo->name);
956 stream->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
957 stream->play_id = play_id;
958 stream->rec_id = rec_id;
959 stream->user_data = user_data;
960 stream->samples_per_sec = param->clock_rate;
961 stream->samples_per_frame = param->samples_per_frame;
962 stream->bytes_per_sample = param->bits_per_sample / 8;
963 stream->channel_count = param->channel_count;
964 stream->rec_cb = rec_cb;
965 stream->play_cb = play_cb;
966
967 stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool,
968 stream->samples_per_frame * stream->bytes_per_sample);
969 stream->rec_buf_count = 0;
970
971 stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool,
972 stream->samples_per_frame * stream->bytes_per_sample);
973 stream->play_buf_count = 0;
974
975 pj_bzero(&inputParam, sizeof(inputParam));
976 inputParam.device = rec_id;
977 inputParam.channelCount = param->channel_count;
978 inputParam.hostApiSpecificStreamInfo = NULL;
979 inputParam.sampleFormat = sampleFormat;
980 if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
981 inputParam.suggestedLatency = param->input_latency_ms / 1000.0;
982 else
983 inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0;
984
985 paRecHostApiInfo = Pa_GetHostApiInfo(paRecDevInfo->hostApi);
986
987 pj_bzero(&outputParam, sizeof(outputParam));
988 outputParam.device = play_id;
989 outputParam.channelCount = param->channel_count;
990 outputParam.hostApiSpecificStreamInfo = NULL;
991 outputParam.sampleFormat = sampleFormat;
992 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
993 outputParam.suggestedLatency=param->output_latency_ms / 1000.0;
994 else
995 outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0;
996
997 paPlayHostApiInfo = Pa_GetHostApiInfo(paPlayDevInfo->hostApi);
998
999 /* Frames in PortAudio is number of samples in a single channel */
1000 paFrames = param->samples_per_frame / param->channel_count;
1001
1002 /* If both input and output are on the same device, open a single stream
1003 * for both input and output.
1004 */
1005 if (rec_id == play_id) {
1006 err = Pa_OpenStream( &paStream, &inputParam, &outputParam,
1007 param->clock_rate, paFrames,
1008 paClipOff, &PaRecorderPlayerCallback, stream );
1009 if (err == paNoError) {
1010 /* Set play stream and record stream to the same stream */
1011 stream->play_strm = stream->rec_strm = paStream;
1012 }
1013 } else {
1014 err = -1;
1015 }
1016
1017 /* .. otherwise if input and output are on the same device, OR if we're
1018 * unable to open a bidirectional stream, then open two separate
1019 * input and output stream.
1020 */
1021 if (paStream == NULL) {
1022 /* Open input stream */
1023 err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
1024 param->clock_rate, paFrames,
1025 paClipOff, &PaRecorderCallback, stream );
1026 if (err == paNoError) {
1027 /* Open output stream */
1028 err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
1029 param->clock_rate, paFrames,
1030 paClipOff, &PaPlayerCallback, stream );
1031 if (err != paNoError)
1032 Pa_CloseStream(stream->rec_strm);
1033 }
1034 }
1035
1036 if (err != paNoError) {
1037 pj_pool_release(pool);
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001038 return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001039 }
1040
1041 paSI = Pa_GetStreamInfo(stream->rec_strm);
1042 paRate = (unsigned)(paSI->sampleRate);
1043 paInputLatency = (unsigned)(paSI->inputLatency * 1000);
1044 paSI = Pa_GetStreamInfo(stream->play_strm);
1045 paOutputLatency = (unsigned)(paSI->outputLatency * 1000);
1046
1047 PJ_LOG(5,(THIS_FILE, "Opened device %s(%s)/%s(%s) for recording and "
1048 "playback, sample rate=%d, ch=%d, "
1049 "bits=%d, %d samples per frame, input latency=%d ms, "
1050 "output latency=%d ms",
1051 paRecDevInfo->name, paRecHostApiInfo->name,
1052 paPlayDevInfo->name, paPlayHostApiInfo->name,
1053 paRate, param->channel_count,
1054 param->bits_per_sample, param->samples_per_frame,
1055 paInputLatency, paOutputLatency));
1056
1057 *p_snd_strm = &stream->base;
1058
1059 return PJ_SUCCESS;
1060}
1061
1062
1063/* API: create stream */
1064static pj_status_t pa_create_stream(pjmedia_aud_dev_factory *f,
Benny Prijono10454dc2009-02-21 14:21:59 +00001065 const pjmedia_aud_param *param,
Benny Prijono2cd64f82009-02-17 19:57:48 +00001066 pjmedia_aud_rec_cb rec_cb,
1067 pjmedia_aud_play_cb play_cb,
1068 void *user_data,
1069 pjmedia_aud_stream **p_aud_strm)
1070{
1071 struct pa_aud_factory *pa = (struct pa_aud_factory*)f;
1072 pj_status_t status;
1073
1074 if (param->dir == PJMEDIA_DIR_CAPTURE) {
1075 status = create_rec_stream(pa, param, rec_cb, user_data, p_aud_strm);
1076 } else if (param->dir == PJMEDIA_DIR_PLAYBACK) {
1077 status = create_play_stream(pa, param, play_cb, user_data, p_aud_strm);
1078 } else if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
1079 status = create_bidir_stream(pa, param, rec_cb, play_cb, user_data,
1080 p_aud_strm);
1081 } else {
1082 return PJ_EINVAL;
1083 }
1084
1085 if (status != PJ_SUCCESS)
1086 return status;
1087
1088 (*p_aud_strm)->op = &pa_strm_op;
1089
1090 return PJ_SUCCESS;
1091}
1092
1093
1094/* API: Get stream parameters */
1095static pj_status_t strm_get_param(pjmedia_aud_stream *s,
Benny Prijono10454dc2009-02-21 14:21:59 +00001096 pjmedia_aud_param *pi)
Benny Prijono2cd64f82009-02-17 19:57:48 +00001097{
1098 struct pa_aud_stream *strm = (struct pa_aud_stream*)s;
1099 const PaStreamInfo *paPlaySI = NULL, *paRecSI = NULL;
1100
1101 PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
1102 PJ_ASSERT_RETURN(strm->play_strm || strm->rec_strm, PJ_EINVALIDOP);
1103
1104 if (strm->play_strm) {
1105 paPlaySI = Pa_GetStreamInfo(strm->play_strm);
1106 }
1107 if (strm->rec_strm) {
1108 paRecSI = Pa_GetStreamInfo(strm->rec_strm);
1109 }
1110
1111 pj_bzero(pi, sizeof(*pi));
1112 pi->dir = strm->dir;
1113 pi->play_id = strm->play_id;
1114 pi->rec_id = strm->rec_id;
1115 pi->clock_rate = (unsigned)(paPlaySI ? paPlaySI->sampleRate :
1116 paRecSI->sampleRate);
1117 pi->channel_count = strm->channel_count;
1118 pi->samples_per_frame = strm->samples_per_frame;
1119 pi->bits_per_sample = strm->bytes_per_sample * 8;
1120 if (paRecSI) {
1121 pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
1122 pi->input_latency_ms = (unsigned)(paRecSI ? paRecSI->inputLatency *
Benny Prijono598b01d2009-02-18 13:55:03 +00001123 1000 : 0);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001124 }
1125 if (paPlaySI) {
1126 pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
1127 pi->output_latency_ms = (unsigned)(paPlaySI? paPlaySI->outputLatency *
Benny Prijono598b01d2009-02-18 13:55:03 +00001128 1000 : 0);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001129 }
1130
1131 return PJ_SUCCESS;
1132}
1133
1134
1135/* API: get capability */
1136static pj_status_t strm_get_cap(pjmedia_aud_stream *s,
1137 pjmedia_aud_dev_cap cap,
1138 void *pval)
1139{
1140 struct pa_aud_stream *strm = (struct pa_aud_stream*)s;
1141
1142 PJ_ASSERT_RETURN(strm && pval, PJ_EINVAL);
1143
1144 if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && strm->rec_strm) {
1145 const PaStreamInfo *si = Pa_GetStreamInfo(strm->rec_strm);
1146 if (!si)
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001147 return PJMEDIA_EAUD_SYSERR;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001148
Benny Prijono598b01d2009-02-18 13:55:03 +00001149 *(unsigned*)pval = (unsigned)(si->inputLatency * 1000);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001150 return PJ_SUCCESS;
1151 } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && strm->play_strm) {
1152 const PaStreamInfo *si = Pa_GetStreamInfo(strm->play_strm);
1153 if (!si)
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001154 return PJMEDIA_EAUD_SYSERR;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001155
Benny Prijono598b01d2009-02-18 13:55:03 +00001156 *(unsigned*)pval = (unsigned)(si->outputLatency * 1000);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001157 return PJ_SUCCESS;
1158 } else {
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001159 return PJMEDIA_EAUD_INVCAP;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001160 }
1161}
1162
1163
1164/* API: set capability */
1165static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
1166 pjmedia_aud_dev_cap cap,
1167 const void *value)
1168{
1169 PJ_UNUSED_ARG(strm);
1170 PJ_UNUSED_ARG(cap);
1171 PJ_UNUSED_ARG(value);
1172
1173 /* Nothing is supported */
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001174 return PJMEDIA_EAUD_INVCAP;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001175}
1176
1177
1178/* API: start stream. */
1179static pj_status_t strm_start(pjmedia_aud_stream *s)
1180{
1181 struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
1182 int err = 0;
1183
1184 PJ_LOG(5,(THIS_FILE, "Starting %s stream..", stream->name.ptr));
1185
1186 if (stream->play_strm)
1187 err = Pa_StartStream(stream->play_strm);
1188
1189 if (err==0 && stream->rec_strm && stream->rec_strm != stream->play_strm) {
1190 err = Pa_StartStream(stream->rec_strm);
1191 if (err != 0)
1192 Pa_StopStream(stream->play_strm);
1193 }
1194
1195 PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
1196
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001197 return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001198}
1199
1200
1201/* API: stop stream. */
1202static pj_status_t strm_stop(pjmedia_aud_stream *s)
1203{
1204 struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
1205 int i, err = 0;
1206
1207 stream->quit_flag = 1;
1208 for (i=0; !stream->rec_thread_exited && i<100; ++i)
1209 pj_thread_sleep(10);
1210 for (i=0; !stream->play_thread_exited && i<100; ++i)
1211 pj_thread_sleep(10);
1212
1213 pj_thread_sleep(1);
1214
1215 PJ_LOG(5,(THIS_FILE, "Stopping stream.."));
1216
1217 if (stream->play_strm)
1218 err = Pa_StopStream(stream->play_strm);
1219
1220 if (stream->rec_strm && stream->rec_strm != stream->play_strm)
1221 err = Pa_StopStream(stream->rec_strm);
1222
1223 stream->play_thread_initialized = 0;
1224 stream->rec_thread_initialized = 0;
1225
1226 PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
1227
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001228 return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001229}
1230
1231
1232/* API: destroy stream. */
1233static pj_status_t strm_destroy(pjmedia_aud_stream *s)
1234{
1235 struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
1236 int i, err = 0;
1237
1238 stream->quit_flag = 1;
1239 for (i=0; !stream->rec_thread_exited && i<100; ++i) {
1240 pj_thread_sleep(1);
1241 }
1242 for (i=0; !stream->play_thread_exited && i<100; ++i) {
1243 pj_thread_sleep(1);
1244 }
1245
1246 PJ_LOG(5,(THIS_FILE, "Closing %.*s: %lu underflow, %lu overflow",
1247 (int)stream->name.slen,
1248 stream->name.ptr,
1249 stream->underflow, stream->overflow));
1250
1251 if (stream->play_strm)
1252 err = Pa_CloseStream(stream->play_strm);
1253
1254 if (stream->rec_strm && stream->rec_strm != stream->play_strm)
1255 err = Pa_CloseStream(stream->rec_strm);
1256
1257 pj_pool_release(stream->pool);
1258
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001259 return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
Benny Prijono2cd64f82009-02-17 19:57:48 +00001260}
1261
Benny Prijono8eeab0b2009-03-04 19:00:28 +00001262#endif /* PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO */
1263