| /* $Id$ */ |
| /* |
| * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) |
| * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| #include <pjmedia/sound_port.h> |
| #include <pjmedia/alaw_ulaw.h> |
| #include <pjmedia/delaybuf.h> |
| #include <pjmedia/echo.h> |
| #include <pjmedia/errno.h> |
| #include <pj/assert.h> |
| #include <pj/log.h> |
| #include <pj/rand.h> |
| #include <pj/string.h> /* pj_memset() */ |
| |
| #define AEC_TAIL 128 /* default AEC length in ms */ |
| #define AEC_SUSPEND_LIMIT 5 /* seconds of no activity */ |
| |
| #define THIS_FILE "sound_port.c" |
| |
| //#define TEST_OVERFLOW_UNDERFLOW |
| |
| struct pjmedia_snd_port |
| { |
| int rec_id; |
| int play_id; |
| pj_uint32_t aud_caps; |
| pjmedia_aud_param aud_param; |
| pjmedia_aud_stream *aud_stream; |
| pjmedia_dir dir; |
| pjmedia_port *port; |
| |
| pjmedia_clock_src cap_clocksrc, |
| play_clocksrc; |
| |
| unsigned clock_rate; |
| unsigned channel_count; |
| unsigned samples_per_frame; |
| unsigned bits_per_sample; |
| unsigned options; |
| unsigned prm_ec_options; |
| |
| /* software ec */ |
| pjmedia_echo_state *ec_state; |
| unsigned ec_options; |
| unsigned ec_tail_len; |
| pj_bool_t ec_suspended; |
| unsigned ec_suspend_count; |
| unsigned ec_suspend_limit; |
| }; |
| |
| /* |
| * The callback called by sound player when it needs more samples to be |
| * played. |
| */ |
| static pj_status_t play_cb(void *user_data, pjmedia_frame *frame) |
| { |
| pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; |
| pjmedia_port *port; |
| const unsigned required_size = (unsigned)frame->size; |
| pj_status_t status; |
| |
| pjmedia_clock_src_update(&snd_port->play_clocksrc, &frame->timestamp); |
| |
| port = snd_port->port; |
| if (port == NULL) |
| goto no_frame; |
| |
| status = pjmedia_port_get_frame(port, frame); |
| if (status != PJ_SUCCESS) |
| goto no_frame; |
| |
| if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO) |
| goto no_frame; |
| |
| /* Must supply the required samples */ |
| pj_assert(frame->size == required_size); |
| |
| if (snd_port->ec_state) { |
| if (snd_port->ec_suspended) { |
| snd_port->ec_suspended = PJ_FALSE; |
| //pjmedia_echo_state_reset(snd_port->ec_state); |
| PJ_LOG(4,(THIS_FILE, "EC activated")); |
| } |
| snd_port->ec_suspend_count = 0; |
| pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf); |
| } |
| |
| |
| return PJ_SUCCESS; |
| |
| no_frame: |
| frame->type = PJMEDIA_FRAME_TYPE_AUDIO; |
| frame->size = required_size; |
| pj_bzero(frame->buf, frame->size); |
| |
| if (snd_port->ec_state && !snd_port->ec_suspended) { |
| ++snd_port->ec_suspend_count; |
| if (snd_port->ec_suspend_count > snd_port->ec_suspend_limit) { |
| snd_port->ec_suspended = PJ_TRUE; |
| PJ_LOG(4,(THIS_FILE, "EC suspended because of inactivity")); |
| } |
| if (snd_port->ec_state) { |
| /* To maintain correct delay in EC */ |
| pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf); |
| } |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * The callback called by sound recorder when it has finished capturing a |
| * frame. |
| */ |
| static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame) |
| { |
| pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; |
| pjmedia_port *port; |
| |
| pjmedia_clock_src_update(&snd_port->cap_clocksrc, &frame->timestamp); |
| |
| port = snd_port->port; |
| if (port == NULL) |
| return PJ_SUCCESS; |
| |
| /* Cancel echo */ |
| if (snd_port->ec_state && !snd_port->ec_suspended) { |
| pjmedia_echo_capture(snd_port->ec_state, (pj_int16_t*) frame->buf, 0); |
| } |
| |
| pjmedia_port_put_frame(port, frame); |
| |
| |
| return PJ_SUCCESS; |
| } |
| |
| /* |
| * The callback called by sound player when it needs more samples to be |
| * played. This version is for non-PCM data. |
| */ |
| static pj_status_t play_cb_ext(void *user_data, pjmedia_frame *frame) |
| { |
| pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; |
| pjmedia_port *port = snd_port->port; |
| |
| if (port == NULL) { |
| frame->type = PJMEDIA_FRAME_TYPE_NONE; |
| return PJ_SUCCESS; |
| } |
| |
| pjmedia_port_get_frame(port, frame); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * The callback called by sound recorder when it has finished capturing a |
| * frame. This version is for non-PCM data. |
| */ |
| static pj_status_t rec_cb_ext(void *user_data, pjmedia_frame *frame) |
| { |
| pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; |
| pjmedia_port *port; |
| |
| port = snd_port->port; |
| if (port == NULL) |
| return PJ_SUCCESS; |
| |
| pjmedia_port_put_frame(port, frame); |
| |
| return PJ_SUCCESS; |
| } |
| |
| /* Initialize with default values (zero) */ |
| PJ_DEF(void) pjmedia_snd_port_param_default(pjmedia_snd_port_param *prm) |
| { |
| pj_bzero(prm, sizeof(*prm)); |
| } |
| |
| /* |
| * Start the sound stream. |
| * This may be called even when the sound stream has already been started. |
| */ |
| static pj_status_t start_sound_device( pj_pool_t *pool, |
| pjmedia_snd_port *snd_port ) |
| { |
| pjmedia_aud_rec_cb snd_rec_cb; |
| pjmedia_aud_play_cb snd_play_cb; |
| pjmedia_aud_param param_copy; |
| pj_status_t status; |
| |
| /* Check if sound has been started. */ |
| if (snd_port->aud_stream != NULL) |
| return PJ_SUCCESS; |
| |
| PJ_ASSERT_RETURN(snd_port->dir == PJMEDIA_DIR_CAPTURE || |
| snd_port->dir == PJMEDIA_DIR_PLAYBACK || |
| snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK, |
| PJ_EBUG); |
| |
| /* Get device caps */ |
| if (snd_port->aud_param.dir & PJMEDIA_DIR_CAPTURE) { |
| pjmedia_aud_dev_info dev_info; |
| |
| status = pjmedia_aud_dev_get_info(snd_port->aud_param.rec_id, |
| &dev_info); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| snd_port->aud_caps = dev_info.caps; |
| } else { |
| snd_port->aud_caps = 0; |
| } |
| |
| /* Process EC settings */ |
| pj_memcpy(¶m_copy, &snd_port->aud_param, sizeof(param_copy)); |
| if (param_copy.flags & PJMEDIA_AUD_DEV_CAP_EC) { |
| /* EC is wanted */ |
| if ((snd_port->prm_ec_options & PJMEDIA_ECHO_USE_SW_ECHO) == 0 && |
| (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)) |
| { |
| /* Device supports EC */ |
| /* Nothing to do */ |
| } else { |
| /* Application wants to use software EC or device |
| * doesn't support EC, remove EC settings from |
| * device parameters |
| */ |
| param_copy.flags &= ~(PJMEDIA_AUD_DEV_CAP_EC | |
| PJMEDIA_AUD_DEV_CAP_EC_TAIL); |
| } |
| } |
| |
| /* Use different callback if format is not PCM */ |
| if (snd_port->aud_param.ext_fmt.id == PJMEDIA_FORMAT_L16) { |
| snd_rec_cb = &rec_cb; |
| snd_play_cb = &play_cb; |
| } else { |
| snd_rec_cb = &rec_cb_ext; |
| snd_play_cb = &play_cb_ext; |
| } |
| |
| /* Open the device */ |
| status = pjmedia_aud_stream_create(¶m_copy, |
| snd_rec_cb, |
| snd_play_cb, |
| snd_port, |
| &snd_port->aud_stream); |
| |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| /* Inactivity limit before EC is suspended. */ |
| snd_port->ec_suspend_limit = AEC_SUSPEND_LIMIT * |
| (snd_port->clock_rate / |
| snd_port->samples_per_frame); |
| |
| /* Create software EC if parameter specifies EC and |
| * (app specifically requests software EC or device |
| * doesn't support EC). Only do this if the format is PCM! |
| */ |
| if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC) && |
| ((snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)==0 || |
| (snd_port->prm_ec_options & PJMEDIA_ECHO_USE_SW_ECHO) != 0) && |
| param_copy.ext_fmt.id == PJMEDIA_FORMAT_PCM) |
| { |
| if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) { |
| snd_port->aud_param.flags |= PJMEDIA_AUD_DEV_CAP_EC_TAIL; |
| snd_port->aud_param.ec_tail_ms = AEC_TAIL; |
| PJ_LOG(4,(THIS_FILE, "AEC tail is set to default %u ms", |
| snd_port->aud_param.ec_tail_ms)); |
| } |
| |
| status = pjmedia_snd_port_set_ec(snd_port, pool, |
| snd_port->aud_param.ec_tail_ms, |
| snd_port->prm_ec_options); |
| if (status != PJ_SUCCESS) { |
| pjmedia_aud_stream_destroy(snd_port->aud_stream); |
| snd_port->aud_stream = NULL; |
| return status; |
| } |
| } |
| |
| /* Start sound stream. */ |
| if (!(snd_port->options & PJMEDIA_SND_PORT_NO_AUTO_START)) { |
| status = pjmedia_aud_stream_start(snd_port->aud_stream); |
| } |
| if (status != PJ_SUCCESS) { |
| pjmedia_aud_stream_destroy(snd_port->aud_stream); |
| snd_port->aud_stream = NULL; |
| return status; |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Stop the sound device. |
| * This may be called even when there's no sound device in the port. |
| */ |
| static pj_status_t stop_sound_device( pjmedia_snd_port *snd_port ) |
| { |
| /* Check if we have sound stream device. */ |
| if (snd_port->aud_stream) { |
| pjmedia_aud_stream_stop(snd_port->aud_stream); |
| pjmedia_aud_stream_destroy(snd_port->aud_stream); |
| snd_port->aud_stream = NULL; |
| } |
| |
| /* Destroy AEC */ |
| if (snd_port->ec_state) { |
| pjmedia_echo_destroy(snd_port->ec_state); |
| snd_port->ec_state = NULL; |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Create bidirectional port. |
| */ |
| PJ_DEF(pj_status_t) 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) |
| { |
| pjmedia_snd_port_param param; |
| pj_status_t status; |
| |
| pjmedia_snd_port_param_default(¶m); |
| |
| status = pjmedia_aud_dev_default_param(rec_id, ¶m.base); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| param.base.dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; |
| param.base.rec_id = rec_id; |
| param.base.play_id = play_id; |
| param.base.clock_rate = clock_rate; |
| param.base.channel_count = channel_count; |
| param.base.samples_per_frame = samples_per_frame; |
| param.base.bits_per_sample = bits_per_sample; |
| param.options = options; |
| param.ec_options = 0; |
| |
| return pjmedia_snd_port_create2(pool, ¶m, p_port); |
| } |
| |
| /* |
| * Create sound recorder AEC. |
| */ |
| PJ_DEF(pj_status_t) 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) |
| { |
| pjmedia_snd_port_param param; |
| pj_status_t status; |
| |
| pjmedia_snd_port_param_default(¶m); |
| |
| status = pjmedia_aud_dev_default_param(dev_id, ¶m.base); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| param.base.dir = PJMEDIA_DIR_CAPTURE; |
| param.base.rec_id = dev_id; |
| param.base.clock_rate = clock_rate; |
| param.base.channel_count = channel_count; |
| param.base.samples_per_frame = samples_per_frame; |
| param.base.bits_per_sample = bits_per_sample; |
| param.options = options; |
| param.ec_options = 0; |
| |
| return pjmedia_snd_port_create2(pool, ¶m, p_port); |
| } |
| |
| |
| /* |
| * Create sound player port. |
| */ |
| PJ_DEF(pj_status_t) 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) |
| { |
| pjmedia_snd_port_param param; |
| pj_status_t status; |
| |
| pjmedia_snd_port_param_default(¶m); |
| |
| status = pjmedia_aud_dev_default_param(dev_id, ¶m.base); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| param.base.dir = PJMEDIA_DIR_PLAYBACK; |
| param.base.play_id = dev_id; |
| param.base.clock_rate = clock_rate; |
| param.base.channel_count = channel_count; |
| param.base.samples_per_frame = samples_per_frame; |
| param.base.bits_per_sample = bits_per_sample; |
| param.options = options; |
| param.ec_options = 0; |
| |
| return pjmedia_snd_port_create2(pool, ¶m, p_port); |
| } |
| |
| |
| /* |
| * Create sound port. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool, |
| const pjmedia_snd_port_param *prm, |
| pjmedia_snd_port **p_port) |
| { |
| pjmedia_snd_port *snd_port; |
| pj_status_t status; |
| unsigned ptime_usec; |
| |
| PJ_ASSERT_RETURN(pool && prm && p_port, PJ_EINVAL); |
| |
| snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port); |
| PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM); |
| |
| snd_port->dir = prm->base.dir; |
| snd_port->rec_id = prm->base.rec_id; |
| snd_port->play_id = prm->base.play_id; |
| snd_port->clock_rate = prm->base.clock_rate; |
| snd_port->channel_count = prm->base.channel_count; |
| snd_port->samples_per_frame = prm->base.samples_per_frame; |
| snd_port->bits_per_sample = prm->base.bits_per_sample; |
| pj_memcpy(&snd_port->aud_param, &prm->base, sizeof(snd_port->aud_param)); |
| snd_port->options = prm->options; |
| snd_port->prm_ec_options = prm->ec_options; |
| |
| ptime_usec = prm->base.samples_per_frame * 1000 / prm->base.channel_count / |
| prm->base.clock_rate * 1000; |
| pjmedia_clock_src_init(&snd_port->cap_clocksrc, PJMEDIA_TYPE_AUDIO, |
| snd_port->clock_rate, ptime_usec); |
| pjmedia_clock_src_init(&snd_port->play_clocksrc, PJMEDIA_TYPE_AUDIO, |
| snd_port->clock_rate, ptime_usec); |
| |
| /* Start sound device immediately. |
| * If there's no port connected, the sound callback will return |
| * empty signal. |
| */ |
| status = start_sound_device( pool, snd_port ); |
| if (status != PJ_SUCCESS) { |
| pjmedia_snd_port_destroy(snd_port); |
| return status; |
| } |
| |
| *p_port = snd_port; |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Destroy port (also destroys the sound device). |
| */ |
| PJ_DEF(pj_status_t) pjmedia_snd_port_destroy(pjmedia_snd_port *snd_port) |
| { |
| PJ_ASSERT_RETURN(snd_port, PJ_EINVAL); |
| |
| return stop_sound_device(snd_port); |
| } |
| |
| |
| /* |
| * Retrieve the sound stream associated by this sound device port. |
| */ |
| PJ_DEF(pjmedia_aud_stream*) pjmedia_snd_port_get_snd_stream( |
| pjmedia_snd_port *snd_port) |
| { |
| PJ_ASSERT_RETURN(snd_port, NULL); |
| return snd_port->aud_stream; |
| } |
| |
| |
| /* |
| * Change EC settings. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_snd_port_set_ec( pjmedia_snd_port *snd_port, |
| pj_pool_t *pool, |
| unsigned tail_ms, |
| unsigned options) |
| { |
| pjmedia_aud_param prm; |
| pj_status_t status; |
| |
| /* Sound must be opened in full-duplex mode */ |
| PJ_ASSERT_RETURN(snd_port && |
| snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK, |
| PJ_EINVALIDOP); |
| |
| /* Determine whether we use device or software EC */ |
| if ((snd_port->prm_ec_options & PJMEDIA_ECHO_USE_SW_ECHO) == 0 && |
| (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)) |
| { |
| /* We use device EC */ |
| pj_bool_t ec_enabled; |
| |
| /* Query EC status */ |
| status = pjmedia_aud_stream_get_cap(snd_port->aud_stream, |
| PJMEDIA_AUD_DEV_CAP_EC, |
| &ec_enabled); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| if (tail_ms != 0) { |
| /* Change EC setting */ |
| |
| if (!ec_enabled) { |
| /* Enable EC first */ |
| pj_bool_t value = PJ_TRUE; |
| status = pjmedia_aud_stream_set_cap(snd_port->aud_stream, |
| PJMEDIA_AUD_DEV_CAP_EC, |
| &value); |
| if (status != PJ_SUCCESS) |
| return status; |
| } |
| |
| if ((snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) { |
| /* Device does not support setting EC tail */ |
| return PJMEDIA_EAUD_INVCAP; |
| } |
| |
| return pjmedia_aud_stream_set_cap(snd_port->aud_stream, |
| PJMEDIA_AUD_DEV_CAP_EC_TAIL, |
| &tail_ms); |
| |
| } else if (ec_enabled) { |
| /* Disable EC */ |
| pj_bool_t value = PJ_FALSE; |
| return pjmedia_aud_stream_set_cap(snd_port->aud_stream, |
| PJMEDIA_AUD_DEV_CAP_EC, |
| &value); |
| } else { |
| /* Request to disable EC but EC has been disabled */ |
| /* Do nothing */ |
| return PJ_SUCCESS; |
| } |
| |
| } else { |
| /* We use software EC */ |
| |
| /* Check if there is change in parameters */ |
| if (tail_ms==snd_port->ec_tail_len && options==snd_port->ec_options) { |
| PJ_LOG(5,(THIS_FILE, "pjmedia_snd_port_set_ec() ignored, no " |
| "change in settings")); |
| return PJ_SUCCESS; |
| } |
| |
| status = pjmedia_aud_stream_get_param(snd_port->aud_stream, &prm); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| /* Audio stream must be in PCM format */ |
| PJ_ASSERT_RETURN(prm.ext_fmt.id == PJMEDIA_FORMAT_PCM, |
| PJ_EINVALIDOP); |
| |
| /* Destroy AEC */ |
| if (snd_port->ec_state) { |
| pjmedia_echo_destroy(snd_port->ec_state); |
| snd_port->ec_state = NULL; |
| } |
| |
| if (tail_ms != 0) { |
| unsigned delay_ms; |
| |
| //No need to add input latency in the latency calculation, |
| //since actual input latency should be zero. |
| //delay_ms = (si.rec_latency + si.play_latency) * 1000 / |
| // snd_port->clock_rate; |
| /* Set EC latency to 3/4 of output latency to reduce the |
| * possibility of missing/late reference frame. |
| */ |
| delay_ms = prm.output_latency_ms * 3/4; |
| status = pjmedia_echo_create2(pool, snd_port->clock_rate, |
| snd_port->channel_count, |
| snd_port->samples_per_frame, |
| tail_ms, delay_ms, |
| options, &snd_port->ec_state); |
| if (status != PJ_SUCCESS) |
| snd_port->ec_state = NULL; |
| else |
| snd_port->ec_suspended = PJ_FALSE; |
| } else { |
| PJ_LOG(4,(THIS_FILE, "Echo canceller is now disabled in the " |
| "sound port")); |
| status = PJ_SUCCESS; |
| } |
| |
| snd_port->ec_options = options; |
| snd_port->ec_tail_len = tail_ms; |
| } |
| |
| return status; |
| } |
| |
| |
| /* Get AEC tail length */ |
| PJ_DEF(pj_status_t) pjmedia_snd_port_get_ec_tail( pjmedia_snd_port *snd_port, |
| unsigned *p_length) |
| { |
| PJ_ASSERT_RETURN(snd_port && p_length, PJ_EINVAL); |
| |
| /* Determine whether we use device or software EC */ |
| if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) { |
| /* We use device EC */ |
| pj_bool_t ec_enabled; |
| pj_status_t status; |
| |
| /* Query EC status */ |
| status = pjmedia_aud_stream_get_cap(snd_port->aud_stream, |
| PJMEDIA_AUD_DEV_CAP_EC, |
| &ec_enabled); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| if (!ec_enabled) { |
| *p_length = 0; |
| } else if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL) { |
| /* Get device EC tail */ |
| status = pjmedia_aud_stream_get_cap(snd_port->aud_stream, |
| PJMEDIA_AUD_DEV_CAP_EC_TAIL, |
| p_length); |
| if (status != PJ_SUCCESS) |
| return status; |
| } else { |
| /* Just use default */ |
| *p_length = AEC_TAIL; |
| } |
| |
| } else { |
| /* We use software EC */ |
| *p_length = snd_port->ec_state ? snd_port->ec_tail_len : 0; |
| } |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Get clock source. |
| */ |
| PJ_DEF(pjmedia_clock_src *) |
| pjmedia_snd_port_get_clock_src( pjmedia_snd_port *snd_port, |
| pjmedia_dir dir ) |
| { |
| return (dir == PJMEDIA_DIR_CAPTURE? &snd_port->cap_clocksrc: |
| &snd_port->play_clocksrc); |
| } |
| |
| |
| /* |
| * Connect a port. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_snd_port_connect( pjmedia_snd_port *snd_port, |
| pjmedia_port *port) |
| { |
| pjmedia_audio_format_detail *afd; |
| |
| PJ_ASSERT_RETURN(snd_port && port, PJ_EINVAL); |
| |
| afd = pjmedia_format_get_audio_format_detail(&port->info.fmt, PJ_TRUE); |
| |
| /* Check that port has the same configuration as the sound device |
| * port. |
| */ |
| if (afd->clock_rate != snd_port->clock_rate) |
| return PJMEDIA_ENCCLOCKRATE; |
| |
| if (PJMEDIA_AFD_SPF(afd) != snd_port->samples_per_frame) |
| return PJMEDIA_ENCSAMPLESPFRAME; |
| |
| if (afd->channel_count != snd_port->channel_count) |
| return PJMEDIA_ENCCHANNEL; |
| |
| if (afd->bits_per_sample != snd_port->bits_per_sample) |
| return PJMEDIA_ENCBITS; |
| |
| /* Port is okay. */ |
| snd_port->port = port; |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Get the connected port. |
| */ |
| PJ_DEF(pjmedia_port*) pjmedia_snd_port_get_port(pjmedia_snd_port *snd_port) |
| { |
| PJ_ASSERT_RETURN(snd_port, NULL); |
| return snd_port->port; |
| } |
| |
| |
| /* |
| * Disconnect port. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_snd_port_disconnect(pjmedia_snd_port *snd_port) |
| { |
| PJ_ASSERT_RETURN(snd_port, PJ_EINVAL); |
| |
| snd_port->port = NULL; |
| |
| return PJ_SUCCESS; |
| } |
| |
| |