blob: 06d273c3c616b126640bb7d0b2afce561567f3d8 [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 Prijono2cd64f82009-02-17 19:57:48 +0000108 pjmedia_aud_dev_param *param);
109static pj_status_t pa_create_stream(pjmedia_aud_dev_factory *f,
110 const pjmedia_aud_dev_param *param,
111 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,
118 pjmedia_aud_dev_param *param);
119static 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 Prijono2cd64f82009-02-17 19:57:48 +0000517 pjmedia_aud_dev_param *param)
518{
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 Prijono2cd64f82009-02-17 19:57:48 +0000536 param->play_id = PJMEDIA_AUD_DEV_DEFAULT_ID;
537 } else if (adi.output_count) {
538 param->dir = PJMEDIA_DIR_PLAYBACK;
Benny Prijono598b01d2009-02-18 13:55:03 +0000539 param->play_id = index;
Benny Prijono2cd64f82009-02-17 19:57:48 +0000540 param->rec_id = PJMEDIA_AUD_DEV_DEFAULT_ID;
541 } 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,
672 const pjmedia_aud_dev_param *param,
673 pjmedia_aud_rec_cb rec_cb,
674 void *user_data,
675 pjmedia_aud_stream **p_snd_strm)
676{
677 pj_pool_t *pool;
678 pjmedia_aud_dev_id rec_id;
679 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
688 rec_id = param->rec_id;
689 if (rec_id < 0) {
690 rec_id = pa_get_default_input_dev(param->channel_count);
691 if (rec_id < 0) {
692 /* No such device. */
693 return PJMEDIA_ENOSNDREC;
694 }
695 }
696
697 paDevInfo = Pa_GetDeviceInfo(rec_id);
698 if (!paDevInfo) {
699 /* Assumed it is "No such device" error. */
700 return PJMEDIA_ESNDINDEVID;
701 }
702
703 if (param->bits_per_sample == 8)
704 sampleFormat = paUInt8;
705 else if (param->bits_per_sample == 16)
706 sampleFormat = paInt16;
707 else if (param->bits_per_sample == 32)
708 sampleFormat = paInt32;
709 else
710 return PJMEDIA_ESNDINSAMPLEFMT;
711
712 pool = pj_pool_create(pa->pf, "recstrm", 1024, 1024, NULL);
713 if (!pool)
714 return PJ_ENOMEM;
715
716 stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
717 stream->pool = pool;
718 pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
719 stream->dir = PJMEDIA_DIR_CAPTURE;
720 stream->rec_id = rec_id;
721 stream->play_id = -1;
722 stream->user_data = user_data;
723 stream->samples_per_sec = param->clock_rate;
724 stream->samples_per_frame = param->samples_per_frame;
725 stream->bytes_per_sample = param->bits_per_sample / 8;
726 stream->channel_count = param->channel_count;
727 stream->rec_cb = rec_cb;
728
729 stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool,
730 stream->samples_per_frame * stream->bytes_per_sample);
731 stream->rec_buf_count = 0;
732
733 pj_bzero(&inputParam, sizeof(inputParam));
734 inputParam.device = rec_id;
735 inputParam.channelCount = param->channel_count;
736 inputParam.hostApiSpecificStreamInfo = NULL;
737 inputParam.sampleFormat = sampleFormat;
738 if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
739 inputParam.suggestedLatency = param->input_latency_ms / 1000.0;
740 else
741 inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0;
742
743 paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
744
745 /* Frames in PortAudio is number of samples in a single channel */
746 paFrames = param->samples_per_frame / param->channel_count;
747
748 err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
749 param->clock_rate, paFrames,
750 paClipOff, &PaRecorderCallback, stream );
751 if (err != paNoError) {
752 pj_pool_release(pool);
753 return PJMEDIA_ERRNO_FROM_PORTAUDIO(err);
754 }
755
756 paSI = Pa_GetStreamInfo(stream->rec_strm);
757 paRate = (unsigned)paSI->sampleRate;
758 paLatency = (unsigned)(paSI->inputLatency * 1000);
759
760 PJ_LOG(5,(THIS_FILE, "Opened device %s (%s) for recording, sample "
761 "rate=%d, ch=%d, "
762 "bits=%d, %d samples per frame, latency=%d ms",
763 paDevInfo->name, paHostApiInfo->name,
764 paRate, param->channel_count,
765 param->bits_per_sample, param->samples_per_frame,
766 paLatency));
767
768 *p_snd_strm = &stream->base;
769 return PJ_SUCCESS;
770}
771
772
773/* Internal: create playback stream */
774static pj_status_t create_play_stream(struct pa_aud_factory *pa,
775 const pjmedia_aud_dev_param *param,
776 pjmedia_aud_play_cb play_cb,
777 void *user_data,
778 pjmedia_aud_stream **p_snd_strm)
779{
780 pj_pool_t *pool;
781 pjmedia_aud_dev_id play_id;
782 struct pa_aud_stream *stream;
783 PaStreamParameters outputParam;
784 int sampleFormat;
785 const PaDeviceInfo *paDevInfo = NULL;
786 const PaHostApiInfo *paHostApiInfo = NULL;
787 const PaStreamInfo *paSI;
788 unsigned paFrames, paRate, paLatency;
789 PaError err;
790
791 play_id = param->play_id;
792 if (play_id < 0) {
793 play_id = pa_get_default_output_dev(param->channel_count);
794 if (play_id < 0) {
795 /* No such device. */
796 return PJMEDIA_ENOSNDPLAY;
797 }
798 }
799
800 paDevInfo = Pa_GetDeviceInfo(play_id);
801 if (!paDevInfo) {
802 /* Assumed it is "No such device" error. */
803 return PJMEDIA_ESNDINDEVID;
804 }
805
806 if (param->bits_per_sample == 8)
807 sampleFormat = paUInt8;
808 else if (param->bits_per_sample == 16)
809 sampleFormat = paInt16;
810 else if (param->bits_per_sample == 32)
811 sampleFormat = paInt32;
812 else
813 return PJMEDIA_ESNDINSAMPLEFMT;
814
815 pool = pj_pool_create(pa->pf, "playstrm", 1024, 1024, NULL);
816 if (!pool)
817 return PJ_ENOMEM;
818
819 stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
820 stream->pool = pool;
821 pj_strdup2_with_null(pool, &stream->name, paDevInfo->name);
822 stream->dir = PJMEDIA_DIR_PLAYBACK;
823 stream->play_id = play_id;
824 stream->rec_id = -1;
825 stream->user_data = user_data;
826 stream->samples_per_sec = param->clock_rate;
827 stream->samples_per_frame = param->samples_per_frame;
828 stream->bytes_per_sample = param->bits_per_sample / 8;
829 stream->channel_count = param->channel_count;
830 stream->play_cb = play_cb;
831
832 stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool,
833 stream->samples_per_frame *
834 stream->bytes_per_sample);
835 stream->play_buf_count = 0;
836
837 pj_bzero(&outputParam, sizeof(outputParam));
838 outputParam.device = play_id;
839 outputParam.channelCount = param->channel_count;
840 outputParam.hostApiSpecificStreamInfo = NULL;
841 outputParam.sampleFormat = sampleFormat;
842 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
843 outputParam.suggestedLatency=param->output_latency_ms / 1000.0;
844 else
845 outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0;
846
847 paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi);
848
849 /* Frames in PortAudio is number of samples in a single channel */
850 paFrames = param->samples_per_frame / param->channel_count;
851
852 err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
853 param->clock_rate, paFrames,
854 paClipOff, &PaPlayerCallback, stream );
855 if (err != paNoError) {
856 pj_pool_release(pool);
857 return PJMEDIA_ERRNO_FROM_PORTAUDIO(err);
858 }
859
860 paSI = Pa_GetStreamInfo(stream->play_strm);
861 paRate = (unsigned)(paSI->sampleRate);
862 paLatency = (unsigned)(paSI->outputLatency * 1000);
863
864 PJ_LOG(5,(THIS_FILE, "Opened device %d: %s(%s) for playing, sample rate=%d"
865 ", ch=%d, "
866 "bits=%d, %d samples per frame, latency=%d ms",
867 play_id, paDevInfo->name, paHostApiInfo->name,
868 paRate, param->channel_count,
869 param->bits_per_sample, param->samples_per_frame,
870 paLatency));
871
872 *p_snd_strm = &stream->base;
873
874 return PJ_SUCCESS;
875}
876
877
878/* Internal: Create both player and recorder stream */
879static pj_status_t create_bidir_stream(struct pa_aud_factory *pa,
880 const pjmedia_aud_dev_param *param,
881 pjmedia_aud_rec_cb rec_cb,
882 pjmedia_aud_play_cb play_cb,
883 void *user_data,
884 pjmedia_aud_stream **p_snd_strm)
885{
886 pj_pool_t *pool;
887 pjmedia_aud_dev_id rec_id, play_id;
888 struct pa_aud_stream *stream;
889 PaStream *paStream = NULL;
890 PaStreamParameters inputParam;
891 PaStreamParameters outputParam;
892 int sampleFormat;
893 const PaDeviceInfo *paRecDevInfo = NULL;
894 const PaDeviceInfo *paPlayDevInfo = NULL;
895 const PaHostApiInfo *paRecHostApiInfo = NULL;
896 const PaHostApiInfo *paPlayHostApiInfo = NULL;
897 const PaStreamInfo *paSI;
898 unsigned paFrames, paRate, paInputLatency, paOutputLatency;
899 PaError err;
900
901 rec_id = param->rec_id;
902 if (rec_id < 0) {
903 rec_id = pa_get_default_input_dev(param->channel_count);
904 if (rec_id < 0) {
905 /* No such device. */
906 return PJMEDIA_ENOSNDREC;
907 }
908 }
909
910 paRecDevInfo = Pa_GetDeviceInfo(rec_id);
911 if (!paRecDevInfo) {
912 /* Assumed it is "No such device" error. */
913 return PJMEDIA_ESNDINDEVID;
914 }
915
916 play_id = param->play_id;
917 if (play_id < 0) {
918 play_id = pa_get_default_output_dev(param->channel_count);
919 if (play_id < 0) {
920 /* No such device. */
921 return PJMEDIA_ENOSNDPLAY;
922 }
923 }
924
925 paPlayDevInfo = Pa_GetDeviceInfo(play_id);
926 if (!paPlayDevInfo) {
927 /* Assumed it is "No such device" error. */
928 return PJMEDIA_ESNDINDEVID;
929 }
930
931
932 if (param->bits_per_sample == 8)
933 sampleFormat = paUInt8;
934 else if (param->bits_per_sample == 16)
935 sampleFormat = paInt16;
936 else if (param->bits_per_sample == 32)
937 sampleFormat = paInt32;
938 else
939 return PJMEDIA_ESNDINSAMPLEFMT;
940
941 pool = pj_pool_create(pa->pf, "sndstream", 1024, 1024, NULL);
942 if (!pool)
943 return PJ_ENOMEM;
944
945 stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream);
946 stream->pool = pool;
947 pj_strdup2_with_null(pool, &stream->name, paRecDevInfo->name);
948 stream->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
949 stream->play_id = play_id;
950 stream->rec_id = rec_id;
951 stream->user_data = user_data;
952 stream->samples_per_sec = param->clock_rate;
953 stream->samples_per_frame = param->samples_per_frame;
954 stream->bytes_per_sample = param->bits_per_sample / 8;
955 stream->channel_count = param->channel_count;
956 stream->rec_cb = rec_cb;
957 stream->play_cb = play_cb;
958
959 stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool,
960 stream->samples_per_frame * stream->bytes_per_sample);
961 stream->rec_buf_count = 0;
962
963 stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool,
964 stream->samples_per_frame * stream->bytes_per_sample);
965 stream->play_buf_count = 0;
966
967 pj_bzero(&inputParam, sizeof(inputParam));
968 inputParam.device = rec_id;
969 inputParam.channelCount = param->channel_count;
970 inputParam.hostApiSpecificStreamInfo = NULL;
971 inputParam.sampleFormat = sampleFormat;
972 if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)
973 inputParam.suggestedLatency = param->input_latency_ms / 1000.0;
974 else
975 inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0;
976
977 paRecHostApiInfo = Pa_GetHostApiInfo(paRecDevInfo->hostApi);
978
979 pj_bzero(&outputParam, sizeof(outputParam));
980 outputParam.device = play_id;
981 outputParam.channelCount = param->channel_count;
982 outputParam.hostApiSpecificStreamInfo = NULL;
983 outputParam.sampleFormat = sampleFormat;
984 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)
985 outputParam.suggestedLatency=param->output_latency_ms / 1000.0;
986 else
987 outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0;
988
989 paPlayHostApiInfo = Pa_GetHostApiInfo(paPlayDevInfo->hostApi);
990
991 /* Frames in PortAudio is number of samples in a single channel */
992 paFrames = param->samples_per_frame / param->channel_count;
993
994 /* If both input and output are on the same device, open a single stream
995 * for both input and output.
996 */
997 if (rec_id == play_id) {
998 err = Pa_OpenStream( &paStream, &inputParam, &outputParam,
999 param->clock_rate, paFrames,
1000 paClipOff, &PaRecorderPlayerCallback, stream );
1001 if (err == paNoError) {
1002 /* Set play stream and record stream to the same stream */
1003 stream->play_strm = stream->rec_strm = paStream;
1004 }
1005 } else {
1006 err = -1;
1007 }
1008
1009 /* .. otherwise if input and output are on the same device, OR if we're
1010 * unable to open a bidirectional stream, then open two separate
1011 * input and output stream.
1012 */
1013 if (paStream == NULL) {
1014 /* Open input stream */
1015 err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL,
1016 param->clock_rate, paFrames,
1017 paClipOff, &PaRecorderCallback, stream );
1018 if (err == paNoError) {
1019 /* Open output stream */
1020 err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam,
1021 param->clock_rate, paFrames,
1022 paClipOff, &PaPlayerCallback, stream );
1023 if (err != paNoError)
1024 Pa_CloseStream(stream->rec_strm);
1025 }
1026 }
1027
1028 if (err != paNoError) {
1029 pj_pool_release(pool);
1030 return PJMEDIA_ERRNO_FROM_PORTAUDIO(err);
1031 }
1032
1033 paSI = Pa_GetStreamInfo(stream->rec_strm);
1034 paRate = (unsigned)(paSI->sampleRate);
1035 paInputLatency = (unsigned)(paSI->inputLatency * 1000);
1036 paSI = Pa_GetStreamInfo(stream->play_strm);
1037 paOutputLatency = (unsigned)(paSI->outputLatency * 1000);
1038
1039 PJ_LOG(5,(THIS_FILE, "Opened device %s(%s)/%s(%s) for recording and "
1040 "playback, sample rate=%d, ch=%d, "
1041 "bits=%d, %d samples per frame, input latency=%d ms, "
1042 "output latency=%d ms",
1043 paRecDevInfo->name, paRecHostApiInfo->name,
1044 paPlayDevInfo->name, paPlayHostApiInfo->name,
1045 paRate, param->channel_count,
1046 param->bits_per_sample, param->samples_per_frame,
1047 paInputLatency, paOutputLatency));
1048
1049 *p_snd_strm = &stream->base;
1050
1051 return PJ_SUCCESS;
1052}
1053
1054
1055/* API: create stream */
1056static pj_status_t pa_create_stream(pjmedia_aud_dev_factory *f,
1057 const pjmedia_aud_dev_param *param,
1058 pjmedia_aud_rec_cb rec_cb,
1059 pjmedia_aud_play_cb play_cb,
1060 void *user_data,
1061 pjmedia_aud_stream **p_aud_strm)
1062{
1063 struct pa_aud_factory *pa = (struct pa_aud_factory*)f;
1064 pj_status_t status;
1065
1066 if (param->dir == PJMEDIA_DIR_CAPTURE) {
1067 status = create_rec_stream(pa, param, rec_cb, user_data, p_aud_strm);
1068 } else if (param->dir == PJMEDIA_DIR_PLAYBACK) {
1069 status = create_play_stream(pa, param, play_cb, user_data, p_aud_strm);
1070 } else if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
1071 status = create_bidir_stream(pa, param, rec_cb, play_cb, user_data,
1072 p_aud_strm);
1073 } else {
1074 return PJ_EINVAL;
1075 }
1076
1077 if (status != PJ_SUCCESS)
1078 return status;
1079
1080 (*p_aud_strm)->op = &pa_strm_op;
1081
1082 return PJ_SUCCESS;
1083}
1084
1085
1086/* API: Get stream parameters */
1087static pj_status_t strm_get_param(pjmedia_aud_stream *s,
1088 pjmedia_aud_dev_param *pi)
1089{
1090 struct pa_aud_stream *strm = (struct pa_aud_stream*)s;
1091 const PaStreamInfo *paPlaySI = NULL, *paRecSI = NULL;
1092
1093 PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
1094 PJ_ASSERT_RETURN(strm->play_strm || strm->rec_strm, PJ_EINVALIDOP);
1095
1096 if (strm->play_strm) {
1097 paPlaySI = Pa_GetStreamInfo(strm->play_strm);
1098 }
1099 if (strm->rec_strm) {
1100 paRecSI = Pa_GetStreamInfo(strm->rec_strm);
1101 }
1102
1103 pj_bzero(pi, sizeof(*pi));
1104 pi->dir = strm->dir;
1105 pi->play_id = strm->play_id;
1106 pi->rec_id = strm->rec_id;
1107 pi->clock_rate = (unsigned)(paPlaySI ? paPlaySI->sampleRate :
1108 paRecSI->sampleRate);
1109 pi->channel_count = strm->channel_count;
1110 pi->samples_per_frame = strm->samples_per_frame;
1111 pi->bits_per_sample = strm->bytes_per_sample * 8;
1112 if (paRecSI) {
1113 pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
1114 pi->input_latency_ms = (unsigned)(paRecSI ? paRecSI->inputLatency *
Benny Prijono598b01d2009-02-18 13:55:03 +00001115 1000 : 0);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001116 }
1117 if (paPlaySI) {
1118 pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
1119 pi->output_latency_ms = (unsigned)(paPlaySI? paPlaySI->outputLatency *
Benny Prijono598b01d2009-02-18 13:55:03 +00001120 1000 : 0);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001121 }
1122
1123 return PJ_SUCCESS;
1124}
1125
1126
1127/* API: get capability */
1128static pj_status_t strm_get_cap(pjmedia_aud_stream *s,
1129 pjmedia_aud_dev_cap cap,
1130 void *pval)
1131{
1132 struct pa_aud_stream *strm = (struct pa_aud_stream*)s;
1133
1134 PJ_ASSERT_RETURN(strm && pval, PJ_EINVAL);
1135
1136 if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && strm->rec_strm) {
1137 const PaStreamInfo *si = Pa_GetStreamInfo(strm->rec_strm);
1138 if (!si)
1139 return PJ_EINVALIDOP;
1140
Benny Prijono598b01d2009-02-18 13:55:03 +00001141 *(unsigned*)pval = (unsigned)(si->inputLatency * 1000);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001142 return PJ_SUCCESS;
1143 } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && strm->play_strm) {
1144 const PaStreamInfo *si = Pa_GetStreamInfo(strm->play_strm);
1145 if (!si)
1146 return PJ_EINVALIDOP;
1147
Benny Prijono598b01d2009-02-18 13:55:03 +00001148 *(unsigned*)pval = (unsigned)(si->outputLatency * 1000);
Benny Prijono2cd64f82009-02-17 19:57:48 +00001149 return PJ_SUCCESS;
1150 } else {
1151 return PJ_ENOTSUP;
1152 }
1153}
1154
1155
1156/* API: set capability */
1157static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
1158 pjmedia_aud_dev_cap cap,
1159 const void *value)
1160{
1161 PJ_UNUSED_ARG(strm);
1162 PJ_UNUSED_ARG(cap);
1163 PJ_UNUSED_ARG(value);
1164
1165 /* Nothing is supported */
1166 return PJ_ENOTSUP;
1167}
1168
1169
1170/* API: start stream. */
1171static pj_status_t strm_start(pjmedia_aud_stream *s)
1172{
1173 struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
1174 int err = 0;
1175
1176 PJ_LOG(5,(THIS_FILE, "Starting %s stream..", stream->name.ptr));
1177
1178 if (stream->play_strm)
1179 err = Pa_StartStream(stream->play_strm);
1180
1181 if (err==0 && stream->rec_strm && stream->rec_strm != stream->play_strm) {
1182 err = Pa_StartStream(stream->rec_strm);
1183 if (err != 0)
1184 Pa_StopStream(stream->play_strm);
1185 }
1186
1187 PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
1188
1189 return err ? PJMEDIA_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
1190}
1191
1192
1193/* API: stop stream. */
1194static pj_status_t strm_stop(pjmedia_aud_stream *s)
1195{
1196 struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
1197 int i, err = 0;
1198
1199 stream->quit_flag = 1;
1200 for (i=0; !stream->rec_thread_exited && i<100; ++i)
1201 pj_thread_sleep(10);
1202 for (i=0; !stream->play_thread_exited && i<100; ++i)
1203 pj_thread_sleep(10);
1204
1205 pj_thread_sleep(1);
1206
1207 PJ_LOG(5,(THIS_FILE, "Stopping stream.."));
1208
1209 if (stream->play_strm)
1210 err = Pa_StopStream(stream->play_strm);
1211
1212 if (stream->rec_strm && stream->rec_strm != stream->play_strm)
1213 err = Pa_StopStream(stream->rec_strm);
1214
1215 stream->play_thread_initialized = 0;
1216 stream->rec_thread_initialized = 0;
1217
1218 PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));
1219
1220 return err ? PJMEDIA_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
1221}
1222
1223
1224/* API: destroy stream. */
1225static pj_status_t strm_destroy(pjmedia_aud_stream *s)
1226{
1227 struct pa_aud_stream *stream = (struct pa_aud_stream*)s;
1228 int i, err = 0;
1229
1230 stream->quit_flag = 1;
1231 for (i=0; !stream->rec_thread_exited && i<100; ++i) {
1232 pj_thread_sleep(1);
1233 }
1234 for (i=0; !stream->play_thread_exited && i<100; ++i) {
1235 pj_thread_sleep(1);
1236 }
1237
1238 PJ_LOG(5,(THIS_FILE, "Closing %.*s: %lu underflow, %lu overflow",
1239 (int)stream->name.slen,
1240 stream->name.ptr,
1241 stream->underflow, stream->overflow));
1242
1243 if (stream->play_strm)
1244 err = Pa_CloseStream(stream->play_strm);
1245
1246 if (stream->rec_strm && stream->rec_strm != stream->play_strm)
1247 err = Pa_CloseStream(stream->rec_strm);
1248
1249 pj_pool_release(stream->pool);
1250
1251 return err ? PJMEDIA_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS;
1252}
1253