blob: 73305d837fb016be75af8eb23e7c9008a0ab34fd [file] [log] [blame]
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001/* $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/sound_port.h>
21#include <pjmedia/alaw_ulaw.h>
22#include <pjmedia/delaybuf.h>
23#include <pjmedia/echo.h>
24#include <pjmedia/errno.h>
25#include <pj/assert.h>
26#include <pj/log.h>
27#include <pj/rand.h>
28#include <pj/string.h> /* pj_memset() */
29
30#define AEC_TAIL 128 /* default AEC length in ms */
31#define AEC_SUSPEND_LIMIT 5 /* seconds of no activity */
32
33#define THIS_FILE "sound_port.c"
34
35//#define TEST_OVERFLOW_UNDERFLOW
36
37struct pjmedia_snd_port
38{
39 int rec_id;
40 int play_id;
41 pj_uint32_t aud_caps;
42 pjmedia_aud_param aud_param;
43 pjmedia_aud_stream *aud_stream;
44 pjmedia_dir dir;
45 pjmedia_port *port;
46
47 pjmedia_clock_src cap_clocksrc,
48 play_clocksrc;
49
50 unsigned clock_rate;
51 unsigned channel_count;
52 unsigned samples_per_frame;
53 unsigned bits_per_sample;
54 unsigned options;
55 unsigned prm_ec_options;
56
57 /* software ec */
58 pjmedia_echo_state *ec_state;
59 unsigned ec_options;
60 unsigned ec_tail_len;
61 pj_bool_t ec_suspended;
62 unsigned ec_suspend_count;
63 unsigned ec_suspend_limit;
64};
65
66/*
67 * The callback called by sound player when it needs more samples to be
68 * played.
69 */
70static pj_status_t play_cb(void *user_data, pjmedia_frame *frame)
71{
72 pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
73 pjmedia_port *port;
74 const unsigned required_size = (unsigned)frame->size;
75 pj_status_t status;
76
77 pjmedia_clock_src_update(&snd_port->play_clocksrc, &frame->timestamp);
78
79 port = snd_port->port;
80 if (port == NULL)
81 goto no_frame;
82
83 status = pjmedia_port_get_frame(port, frame);
84 if (status != PJ_SUCCESS)
85 goto no_frame;
86
87 if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO)
88 goto no_frame;
89
90 /* Must supply the required samples */
91 pj_assert(frame->size == required_size);
92
93 if (snd_port->ec_state) {
94 if (snd_port->ec_suspended) {
95 snd_port->ec_suspended = PJ_FALSE;
96 //pjmedia_echo_state_reset(snd_port->ec_state);
97 PJ_LOG(4,(THIS_FILE, "EC activated"));
98 }
99 snd_port->ec_suspend_count = 0;
100 pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf);
101 }
102
103
104 return PJ_SUCCESS;
105
106no_frame:
107 frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
108 frame->size = required_size;
109 pj_bzero(frame->buf, frame->size);
110
111 if (snd_port->ec_state && !snd_port->ec_suspended) {
112 ++snd_port->ec_suspend_count;
113 if (snd_port->ec_suspend_count > snd_port->ec_suspend_limit) {
114 snd_port->ec_suspended = PJ_TRUE;
115 PJ_LOG(4,(THIS_FILE, "EC suspended because of inactivity"));
116 }
117 if (snd_port->ec_state) {
118 /* To maintain correct delay in EC */
119 pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf);
120 }
121 }
122
123 return PJ_SUCCESS;
124}
125
126
127/*
128 * The callback called by sound recorder when it has finished capturing a
129 * frame.
130 */
131static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame)
132{
133 pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
134 pjmedia_port *port;
135
136 pjmedia_clock_src_update(&snd_port->cap_clocksrc, &frame->timestamp);
137
138 port = snd_port->port;
139 if (port == NULL)
140 return PJ_SUCCESS;
141
142 /* Cancel echo */
143 if (snd_port->ec_state && !snd_port->ec_suspended) {
144 pjmedia_echo_capture(snd_port->ec_state, (pj_int16_t*) frame->buf, 0);
145 }
146
147 pjmedia_port_put_frame(port, frame);
148
149
150 return PJ_SUCCESS;
151}
152
153/*
154 * The callback called by sound player when it needs more samples to be
155 * played. This version is for non-PCM data.
156 */
157static pj_status_t play_cb_ext(void *user_data, pjmedia_frame *frame)
158{
159 pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
160 pjmedia_port *port = snd_port->port;
161
162 if (port == NULL) {
163 frame->type = PJMEDIA_FRAME_TYPE_NONE;
164 return PJ_SUCCESS;
165 }
166
167 pjmedia_port_get_frame(port, frame);
168
169 return PJ_SUCCESS;
170}
171
172
173/*
174 * The callback called by sound recorder when it has finished capturing a
175 * frame. This version is for non-PCM data.
176 */
177static pj_status_t rec_cb_ext(void *user_data, pjmedia_frame *frame)
178{
179 pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
180 pjmedia_port *port;
181
182 port = snd_port->port;
183 if (port == NULL)
184 return PJ_SUCCESS;
185
186 pjmedia_port_put_frame(port, frame);
187
188 return PJ_SUCCESS;
189}
190
191/* Initialize with default values (zero) */
192PJ_DEF(void) pjmedia_snd_port_param_default(pjmedia_snd_port_param *prm)
193{
194 pj_bzero(prm, sizeof(*prm));
195}
196
197/*
198 * Start the sound stream.
199 * This may be called even when the sound stream has already been started.
200 */
201static pj_status_t start_sound_device( pj_pool_t *pool,
202 pjmedia_snd_port *snd_port )
203{
204 pjmedia_aud_rec_cb snd_rec_cb;
205 pjmedia_aud_play_cb snd_play_cb;
206 pjmedia_aud_param param_copy;
207 pj_status_t status;
208
209 /* Check if sound has been started. */
210 if (snd_port->aud_stream != NULL)
211 return PJ_SUCCESS;
212
213 PJ_ASSERT_RETURN(snd_port->dir == PJMEDIA_DIR_CAPTURE ||
214 snd_port->dir == PJMEDIA_DIR_PLAYBACK ||
215 snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK,
216 PJ_EBUG);
217
218 /* Get device caps */
219 if (snd_port->aud_param.dir & PJMEDIA_DIR_CAPTURE) {
220 pjmedia_aud_dev_info dev_info;
221
222 status = pjmedia_aud_dev_get_info(snd_port->aud_param.rec_id,
223 &dev_info);
224 if (status != PJ_SUCCESS)
225 return status;
226
227 snd_port->aud_caps = dev_info.caps;
228 } else {
229 snd_port->aud_caps = 0;
230 }
231
232 /* Process EC settings */
233 pj_memcpy(&param_copy, &snd_port->aud_param, sizeof(param_copy));
234 if (param_copy.flags & PJMEDIA_AUD_DEV_CAP_EC) {
235 /* EC is wanted */
236 if ((snd_port->prm_ec_options & PJMEDIA_ECHO_USE_SW_ECHO) == 0 &&
237 (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC))
238 {
239 /* Device supports EC */
240 /* Nothing to do */
241 } else {
242 /* Application wants to use software EC or device
243 * doesn't support EC, remove EC settings from
244 * device parameters
245 */
246 param_copy.flags &= ~(PJMEDIA_AUD_DEV_CAP_EC |
247 PJMEDIA_AUD_DEV_CAP_EC_TAIL);
248 }
249 }
250
251 /* Use different callback if format is not PCM */
252 if (snd_port->aud_param.ext_fmt.id == PJMEDIA_FORMAT_L16) {
253 snd_rec_cb = &rec_cb;
254 snd_play_cb = &play_cb;
255 } else {
256 snd_rec_cb = &rec_cb_ext;
257 snd_play_cb = &play_cb_ext;
258 }
259
260 /* Open the device */
261 status = pjmedia_aud_stream_create(&param_copy,
262 snd_rec_cb,
263 snd_play_cb,
264 snd_port,
265 &snd_port->aud_stream);
266
267 if (status != PJ_SUCCESS)
268 return status;
269
270 /* Inactivity limit before EC is suspended. */
271 snd_port->ec_suspend_limit = AEC_SUSPEND_LIMIT *
272 (snd_port->clock_rate /
273 snd_port->samples_per_frame);
274
275 /* Create software EC if parameter specifies EC and
276 * (app specifically requests software EC or device
277 * doesn't support EC). Only do this if the format is PCM!
278 */
279 if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC) &&
280 ((snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)==0 ||
281 (snd_port->prm_ec_options & PJMEDIA_ECHO_USE_SW_ECHO) != 0) &&
282 param_copy.ext_fmt.id == PJMEDIA_FORMAT_PCM)
283 {
284 if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) {
285 snd_port->aud_param.flags |= PJMEDIA_AUD_DEV_CAP_EC_TAIL;
286 snd_port->aud_param.ec_tail_ms = AEC_TAIL;
287 PJ_LOG(4,(THIS_FILE, "AEC tail is set to default %u ms",
288 snd_port->aud_param.ec_tail_ms));
289 }
290
291 status = pjmedia_snd_port_set_ec(snd_port, pool,
292 snd_port->aud_param.ec_tail_ms,
293 snd_port->prm_ec_options);
294 if (status != PJ_SUCCESS) {
295 pjmedia_aud_stream_destroy(snd_port->aud_stream);
296 snd_port->aud_stream = NULL;
297 return status;
298 }
299 }
300
301 /* Start sound stream. */
302 if (!(snd_port->options & PJMEDIA_SND_PORT_NO_AUTO_START)) {
303 status = pjmedia_aud_stream_start(snd_port->aud_stream);
304 }
305 if (status != PJ_SUCCESS) {
306 pjmedia_aud_stream_destroy(snd_port->aud_stream);
307 snd_port->aud_stream = NULL;
308 return status;
309 }
310
311 return PJ_SUCCESS;
312}
313
314
315/*
316 * Stop the sound device.
317 * This may be called even when there's no sound device in the port.
318 */
319static pj_status_t stop_sound_device( pjmedia_snd_port *snd_port )
320{
321 /* Check if we have sound stream device. */
322 if (snd_port->aud_stream) {
323 pjmedia_aud_stream_stop(snd_port->aud_stream);
324 pjmedia_aud_stream_destroy(snd_port->aud_stream);
325 snd_port->aud_stream = NULL;
326 }
327
328 /* Destroy AEC */
329 if (snd_port->ec_state) {
330 pjmedia_echo_destroy(snd_port->ec_state);
331 snd_port->ec_state = NULL;
332 }
333
334 return PJ_SUCCESS;
335}
336
337
338/*
339 * Create bidirectional port.
340 */
341PJ_DEF(pj_status_t) pjmedia_snd_port_create( pj_pool_t *pool,
342 int rec_id,
343 int play_id,
344 unsigned clock_rate,
345 unsigned channel_count,
346 unsigned samples_per_frame,
347 unsigned bits_per_sample,
348 unsigned options,
349 pjmedia_snd_port **p_port)
350{
351 pjmedia_snd_port_param param;
352 pj_status_t status;
353
354 pjmedia_snd_port_param_default(&param);
355
356 status = pjmedia_aud_dev_default_param(rec_id, &param.base);
357 if (status != PJ_SUCCESS)
358 return status;
359
360 param.base.dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
361 param.base.rec_id = rec_id;
362 param.base.play_id = play_id;
363 param.base.clock_rate = clock_rate;
364 param.base.channel_count = channel_count;
365 param.base.samples_per_frame = samples_per_frame;
366 param.base.bits_per_sample = bits_per_sample;
367 param.options = options;
368 param.ec_options = 0;
369
370 return pjmedia_snd_port_create2(pool, &param, p_port);
371}
372
373/*
374 * Create sound recorder AEC.
375 */
376PJ_DEF(pj_status_t) pjmedia_snd_port_create_rec( pj_pool_t *pool,
377 int dev_id,
378 unsigned clock_rate,
379 unsigned channel_count,
380 unsigned samples_per_frame,
381 unsigned bits_per_sample,
382 unsigned options,
383 pjmedia_snd_port **p_port)
384{
385 pjmedia_snd_port_param param;
386 pj_status_t status;
387
388 pjmedia_snd_port_param_default(&param);
389
390 status = pjmedia_aud_dev_default_param(dev_id, &param.base);
391 if (status != PJ_SUCCESS)
392 return status;
393
394 param.base.dir = PJMEDIA_DIR_CAPTURE;
395 param.base.rec_id = dev_id;
396 param.base.clock_rate = clock_rate;
397 param.base.channel_count = channel_count;
398 param.base.samples_per_frame = samples_per_frame;
399 param.base.bits_per_sample = bits_per_sample;
400 param.options = options;
401 param.ec_options = 0;
402
403 return pjmedia_snd_port_create2(pool, &param, p_port);
404}
405
406
407/*
408 * Create sound player port.
409 */
410PJ_DEF(pj_status_t) pjmedia_snd_port_create_player( pj_pool_t *pool,
411 int dev_id,
412 unsigned clock_rate,
413 unsigned channel_count,
414 unsigned samples_per_frame,
415 unsigned bits_per_sample,
416 unsigned options,
417 pjmedia_snd_port **p_port)
418{
419 pjmedia_snd_port_param param;
420 pj_status_t status;
421
422 pjmedia_snd_port_param_default(&param);
423
424 status = pjmedia_aud_dev_default_param(dev_id, &param.base);
425 if (status != PJ_SUCCESS)
426 return status;
427
428 param.base.dir = PJMEDIA_DIR_PLAYBACK;
429 param.base.play_id = dev_id;
430 param.base.clock_rate = clock_rate;
431 param.base.channel_count = channel_count;
432 param.base.samples_per_frame = samples_per_frame;
433 param.base.bits_per_sample = bits_per_sample;
434 param.options = options;
435 param.ec_options = 0;
436
437 return pjmedia_snd_port_create2(pool, &param, p_port);
438}
439
440
441/*
442 * Create sound port.
443 */
444PJ_DEF(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool,
445 const pjmedia_snd_port_param *prm,
446 pjmedia_snd_port **p_port)
447{
448 pjmedia_snd_port *snd_port;
449 pj_status_t status;
450 unsigned ptime_usec;
451
452 PJ_ASSERT_RETURN(pool && prm && p_port, PJ_EINVAL);
453
454 snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port);
455 PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM);
456
457 snd_port->dir = prm->base.dir;
458 snd_port->rec_id = prm->base.rec_id;
459 snd_port->play_id = prm->base.play_id;
460 snd_port->clock_rate = prm->base.clock_rate;
461 snd_port->channel_count = prm->base.channel_count;
462 snd_port->samples_per_frame = prm->base.samples_per_frame;
463 snd_port->bits_per_sample = prm->base.bits_per_sample;
464 pj_memcpy(&snd_port->aud_param, &prm->base, sizeof(snd_port->aud_param));
465 snd_port->options = prm->options;
466 snd_port->prm_ec_options = prm->ec_options;
467
468 ptime_usec = prm->base.samples_per_frame * 1000 / prm->base.channel_count /
469 prm->base.clock_rate * 1000;
470 pjmedia_clock_src_init(&snd_port->cap_clocksrc, PJMEDIA_TYPE_AUDIO,
471 snd_port->clock_rate, ptime_usec);
472 pjmedia_clock_src_init(&snd_port->play_clocksrc, PJMEDIA_TYPE_AUDIO,
473 snd_port->clock_rate, ptime_usec);
474
475 /* Start sound device immediately.
476 * If there's no port connected, the sound callback will return
477 * empty signal.
478 */
479 status = start_sound_device( pool, snd_port );
480 if (status != PJ_SUCCESS) {
481 pjmedia_snd_port_destroy(snd_port);
482 return status;
483 }
484
485 *p_port = snd_port;
486 return PJ_SUCCESS;
487}
488
489
490/*
491 * Destroy port (also destroys the sound device).
492 */
493PJ_DEF(pj_status_t) pjmedia_snd_port_destroy(pjmedia_snd_port *snd_port)
494{
495 PJ_ASSERT_RETURN(snd_port, PJ_EINVAL);
496
497 return stop_sound_device(snd_port);
498}
499
500
501/*
502 * Retrieve the sound stream associated by this sound device port.
503 */
504PJ_DEF(pjmedia_aud_stream*) pjmedia_snd_port_get_snd_stream(
505 pjmedia_snd_port *snd_port)
506{
507 PJ_ASSERT_RETURN(snd_port, NULL);
508 return snd_port->aud_stream;
509}
510
511
512/*
513 * Change EC settings.
514 */
515PJ_DEF(pj_status_t) pjmedia_snd_port_set_ec( pjmedia_snd_port *snd_port,
516 pj_pool_t *pool,
517 unsigned tail_ms,
518 unsigned options)
519{
520 pjmedia_aud_param prm;
521 pj_status_t status;
522
523 /* Sound must be opened in full-duplex mode */
524 PJ_ASSERT_RETURN(snd_port &&
525 snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK,
526 PJ_EINVALIDOP);
527
528 /* Determine whether we use device or software EC */
529 if ((snd_port->prm_ec_options & PJMEDIA_ECHO_USE_SW_ECHO) == 0 &&
530 (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC))
531 {
532 /* We use device EC */
533 pj_bool_t ec_enabled;
534
535 /* Query EC status */
536 status = pjmedia_aud_stream_get_cap(snd_port->aud_stream,
537 PJMEDIA_AUD_DEV_CAP_EC,
538 &ec_enabled);
539 if (status != PJ_SUCCESS)
540 return status;
541
542 if (tail_ms != 0) {
543 /* Change EC setting */
544
545 if (!ec_enabled) {
546 /* Enable EC first */
547 pj_bool_t value = PJ_TRUE;
548 status = pjmedia_aud_stream_set_cap(snd_port->aud_stream,
549 PJMEDIA_AUD_DEV_CAP_EC,
550 &value);
551 if (status != PJ_SUCCESS)
552 return status;
553 }
554
555 if ((snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) {
556 /* Device does not support setting EC tail */
557 return PJMEDIA_EAUD_INVCAP;
558 }
559
560 return pjmedia_aud_stream_set_cap(snd_port->aud_stream,
561 PJMEDIA_AUD_DEV_CAP_EC_TAIL,
562 &tail_ms);
563
564 } else if (ec_enabled) {
565 /* Disable EC */
566 pj_bool_t value = PJ_FALSE;
567 return pjmedia_aud_stream_set_cap(snd_port->aud_stream,
568 PJMEDIA_AUD_DEV_CAP_EC,
569 &value);
570 } else {
571 /* Request to disable EC but EC has been disabled */
572 /* Do nothing */
573 return PJ_SUCCESS;
574 }
575
576 } else {
577 /* We use software EC */
578
579 /* Check if there is change in parameters */
580 if (tail_ms==snd_port->ec_tail_len && options==snd_port->ec_options) {
581 PJ_LOG(5,(THIS_FILE, "pjmedia_snd_port_set_ec() ignored, no "
582 "change in settings"));
583 return PJ_SUCCESS;
584 }
585
586 status = pjmedia_aud_stream_get_param(snd_port->aud_stream, &prm);
587 if (status != PJ_SUCCESS)
588 return status;
589
590 /* Audio stream must be in PCM format */
591 PJ_ASSERT_RETURN(prm.ext_fmt.id == PJMEDIA_FORMAT_PCM,
592 PJ_EINVALIDOP);
593
594 /* Destroy AEC */
595 if (snd_port->ec_state) {
596 pjmedia_echo_destroy(snd_port->ec_state);
597 snd_port->ec_state = NULL;
598 }
599
600 if (tail_ms != 0) {
601 unsigned delay_ms;
602
603 //No need to add input latency in the latency calculation,
604 //since actual input latency should be zero.
605 //delay_ms = (si.rec_latency + si.play_latency) * 1000 /
606 // snd_port->clock_rate;
607 /* Set EC latency to 3/4 of output latency to reduce the
608 * possibility of missing/late reference frame.
609 */
610 delay_ms = prm.output_latency_ms * 3/4;
611 status = pjmedia_echo_create2(pool, snd_port->clock_rate,
612 snd_port->channel_count,
613 snd_port->samples_per_frame,
614 tail_ms, delay_ms,
615 options, &snd_port->ec_state);
616 if (status != PJ_SUCCESS)
617 snd_port->ec_state = NULL;
618 else
619 snd_port->ec_suspended = PJ_FALSE;
620 } else {
621 PJ_LOG(4,(THIS_FILE, "Echo canceller is now disabled in the "
622 "sound port"));
623 status = PJ_SUCCESS;
624 }
625
626 snd_port->ec_options = options;
627 snd_port->ec_tail_len = tail_ms;
628 }
629
630 return status;
631}
632
633
634/* Get AEC tail length */
635PJ_DEF(pj_status_t) pjmedia_snd_port_get_ec_tail( pjmedia_snd_port *snd_port,
636 unsigned *p_length)
637{
638 PJ_ASSERT_RETURN(snd_port && p_length, PJ_EINVAL);
639
640 /* Determine whether we use device or software EC */
641 if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) {
642 /* We use device EC */
643 pj_bool_t ec_enabled;
644 pj_status_t status;
645
646 /* Query EC status */
647 status = pjmedia_aud_stream_get_cap(snd_port->aud_stream,
648 PJMEDIA_AUD_DEV_CAP_EC,
649 &ec_enabled);
650 if (status != PJ_SUCCESS)
651 return status;
652
653 if (!ec_enabled) {
654 *p_length = 0;
655 } else if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL) {
656 /* Get device EC tail */
657 status = pjmedia_aud_stream_get_cap(snd_port->aud_stream,
658 PJMEDIA_AUD_DEV_CAP_EC_TAIL,
659 p_length);
660 if (status != PJ_SUCCESS)
661 return status;
662 } else {
663 /* Just use default */
664 *p_length = AEC_TAIL;
665 }
666
667 } else {
668 /* We use software EC */
669 *p_length = snd_port->ec_state ? snd_port->ec_tail_len : 0;
670 }
671 return PJ_SUCCESS;
672}
673
674
675/*
676 * Get clock source.
677 */
678PJ_DEF(pjmedia_clock_src *)
679pjmedia_snd_port_get_clock_src( pjmedia_snd_port *snd_port,
680 pjmedia_dir dir )
681{
682 return (dir == PJMEDIA_DIR_CAPTURE? &snd_port->cap_clocksrc:
683 &snd_port->play_clocksrc);
684}
685
686
687/*
688 * Connect a port.
689 */
690PJ_DEF(pj_status_t) pjmedia_snd_port_connect( pjmedia_snd_port *snd_port,
691 pjmedia_port *port)
692{
693 pjmedia_audio_format_detail *afd;
694
695 PJ_ASSERT_RETURN(snd_port && port, PJ_EINVAL);
696
697 afd = pjmedia_format_get_audio_format_detail(&port->info.fmt, PJ_TRUE);
698
699 /* Check that port has the same configuration as the sound device
700 * port.
701 */
702 if (afd->clock_rate != snd_port->clock_rate)
703 return PJMEDIA_ENCCLOCKRATE;
704
705 if (PJMEDIA_AFD_SPF(afd) != snd_port->samples_per_frame)
706 return PJMEDIA_ENCSAMPLESPFRAME;
707
708 if (afd->channel_count != snd_port->channel_count)
709 return PJMEDIA_ENCCHANNEL;
710
711 if (afd->bits_per_sample != snd_port->bits_per_sample)
712 return PJMEDIA_ENCBITS;
713
714 /* Port is okay. */
715 snd_port->port = port;
716 return PJ_SUCCESS;
717}
718
719
720/*
721 * Get the connected port.
722 */
723PJ_DEF(pjmedia_port*) pjmedia_snd_port_get_port(pjmedia_snd_port *snd_port)
724{
725 PJ_ASSERT_RETURN(snd_port, NULL);
726 return snd_port->port;
727}
728
729
730/*
731 * Disconnect port.
732 */
733PJ_DEF(pj_status_t) pjmedia_snd_port_disconnect(pjmedia_snd_port *snd_port)
734{
735 PJ_ASSERT_RETURN(snd_port, PJ_EINVAL);
736
737 snd_port->port = NULL;
738
739 return PJ_SUCCESS;
740}
741
742