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