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