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