Add support for ICE/STUN/TURN in res_rtp_asterisk and chan_sip.
[asterisk/asterisk.git] / res / pjproject / pjmedia / src / pjmedia / sound_port.c
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
66 /*
67  * The callback called by sound player when it needs more samples to be
68  * played.
69  */
70 static 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 = 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
106 no_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  */
131 static 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  */
157 static 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  */
177 static 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) */
192 PJ_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  */
201 static 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  */
319 static 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  */
341 PJ_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  */
376 PJ_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  */
410 PJ_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  */
444 PJ_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  */
493 PJ_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  */
504 PJ_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  */
515 PJ_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 */
635 PJ_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  */
678 PJ_DEF(pjmedia_clock_src *)
679 pjmedia_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  */
690 PJ_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  */
723 PJ_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  */
733 PJ_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