diff options
author | Nanang Izzuddin <nanang@teluu.com> | 2009-06-04 18:48:49 +0000 |
---|---|---|
committer | Nanang Izzuddin <nanang@teluu.com> | 2009-06-04 18:48:49 +0000 |
commit | 0fb94a0354d037db6c49992a29b7bb97da4bdb53 (patch) | |
tree | 262daf9a93d919c7d5ef6d9d2003e0a9a8c1404b /pjmedia/src | |
parent | 79a95cd0bbe6183c90e267686f9f9e5f23735ebe (diff) |
Ticket #879:
- Added pjmedia synchronizer port.
- Updated affected components, i.e: sound port, AEC, conference bridge.
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2747 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjmedia/src')
-rw-r--r-- | pjmedia/src/pjmedia/conference.c | 34 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/echo_common.c | 81 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/sound_port.c | 201 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/sync_port.c | 274 |
4 files changed, 410 insertions, 180 deletions
diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index fd237aa4..5a23d11d 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -236,6 +236,7 @@ struct pjmedia_conf unsigned channel_count;/**< Number of channels (1=mono). */ unsigned samples_per_frame; /**< Samples per frame. */ unsigned bits_per_sample; /**< Bits per sample. */ + pj_int16_t *master_port_buf; }; @@ -457,7 +458,7 @@ static pj_status_t create_sound_port( pj_pool_t *pool, pj_status_t status; - status = create_pasv_port(conf, pool, &name, NULL, &conf_port); + status = create_conf_port(conf, pool, &name, NULL, &conf_port); if (status != PJ_SUCCESS) return status; @@ -556,6 +557,10 @@ PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool, /* Create and initialize the master port interface. */ conf->master_port = PJ_POOL_ZALLOC_T(pool, pjmedia_port); PJ_ASSERT_RETURN(conf->master_port, PJ_ENOMEM); + + conf->master_port_buf = (pj_int16_t*) + pj_pool_zalloc(pool, conf->samples_per_frame<<1); + PJ_ASSERT_RETURN(conf->master_port_buf, PJ_ENOMEM); pjmedia_port_info_init(&conf->master_port->info, &name, SIGNATURE, clock_rate, channel_count, bits_per_sample, @@ -1051,8 +1056,13 @@ PJ_DEF(pj_status_t) pjmedia_conf_disconnect_port( pjmedia_conf *conf, dst_port->name.ptr)); /* if source port is passive port and has no listener, reset delaybuf */ - if (src_port->delay_buf && src_port->listener_cnt == 0) - pjmedia_delay_buf_reset(src_port->delay_buf); + if (src_port->listener_cnt == 0) { + if (src_port->delay_buf) + pjmedia_delay_buf_reset(src_port->delay_buf); + if (src_port->port == NULL) + pjmedia_zero_samples(conf->master_port_buf, + conf->samples_per_frame); + } } pj_mutex_unlock(conf->mutex); @@ -1841,10 +1851,17 @@ static pj_status_t get_frame(pjmedia_port *this_port, } /* Get frame from this port. + * For port 0 (master port), get the frame from master_port_buf. * For passive ports, get the frame from the delay_buf. * For other ports, get the frame from the port. */ - if (conf_port->delay_buf != NULL) { + if (conf_port->port == NULL) { + + pjmedia_copy_samples((pj_int16_t*)frame->buf, conf->master_port_buf, + conf->samples_per_frame); + + } else if (conf_port->delay_buf != NULL) { + pj_status_t status; status = pjmedia_delay_buf_get(conf_port->delay_buf, @@ -2054,16 +2071,12 @@ static pj_status_t put_frame(pjmedia_port *this_port, { pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; struct conf_port *port = conf->ports[this_port->port_data.ldata]; - pj_status_t status; /* Check for correct size. */ PJ_ASSERT_RETURN( frame->size == conf->samples_per_frame * conf->bits_per_sample / 8, PJMEDIA_ENCSAMPLESPFRAME); - /* Check existance of delay_buf instance */ - PJ_ASSERT_RETURN( port->delay_buf, PJ_EBUG ); - /* Skip if this port is muted/disabled. */ if (port->rx_setting != PJMEDIA_PORT_ENABLE) { return PJ_SUCCESS; @@ -2074,9 +2087,10 @@ static pj_status_t put_frame(pjmedia_port *this_port, return PJ_SUCCESS; } - status = pjmedia_delay_buf_put(port->delay_buf, (pj_int16_t*)frame->buf); + pjmedia_copy_samples(conf->master_port_buf, (pj_int16_t*)frame->buf, + conf->samples_per_frame); - return status; + return PJ_SUCCESS; } #endif diff --git a/pjmedia/src/pjmedia/echo_common.c b/pjmedia/src/pjmedia/echo_common.c index 920988d2..2b016b9f 100644 --- a/pjmedia/src/pjmedia/echo_common.c +++ b/pjmedia/src/pjmedia/echo_common.c @@ -19,7 +19,6 @@ */ #include <pjmedia/echo.h> -#include <pjmedia/delaybuf.h> #include <pjmedia/errno.h> #include <pj/assert.h> #include <pj/list.h> @@ -50,8 +49,6 @@ struct pjmedia_echo_state unsigned lat_buf_cnt; /* Actual number of frames in lat_buf */ struct frame lat_buf; /* Frame queue for delayed playback */ struct frame lat_free; /* Free frame list. */ - - pjmedia_delay_buf *delay_buf; }; @@ -146,6 +143,7 @@ PJ_DEF(pj_status_t) pjmedia_echo_create2(pj_pool_t *pool, unsigned ptime; pjmedia_echo_state *ec; pj_status_t status; + unsigned i; /* Create new pool and instantiate and init the EC */ pool = pj_pool_create(pool->factory, "ec%p", 256, 256, NULL); @@ -191,32 +189,17 @@ PJ_DEF(pj_status_t) pjmedia_echo_create2(pj_pool_t *pool, /* Create latency buffers */ ptime = samples_per_frame * 1000 / clock_rate; - if (latency_ms == 0) { - /* Give at least one frame delay to simplify programming */ + /* Give at least one frame delay to simplify programming */ + if (latency_ms < ptime) { latency_ms = ptime; } ec->lat_target_cnt = latency_ms / ptime; - if (ec->lat_target_cnt != 0) { - unsigned i; - for (i=0; i < ec->lat_target_cnt; ++i) { - struct frame *frm; - - frm = (struct frame*) pj_pool_alloc(pool, (samples_per_frame<<1) + - sizeof(struct frame)); - pj_list_push_back(&ec->lat_free, frm); - } - } else { - ec->lat_ready = PJ_TRUE; - } + for (i=0; i < ec->lat_target_cnt; ++i) { + struct frame *frm; - /* Create delay buffer to compensate drifts */ - status = pjmedia_delay_buf_create(ec->pool, ec->obj_name, clock_rate, - samples_per_frame, channel_count, - (PJMEDIA_SOUND_BUFFER_COUNT+1) * ptime, - 0, &ec->delay_buf); - if (status != PJ_SUCCESS) { - pj_pool_release(pool); - return status; + frm = (struct frame*) pj_pool_alloc(pool, (samples_per_frame<<1) + + sizeof(struct frame)); + pj_list_push_back(&ec->lat_free, frm); } PJ_LOG(4,(ec->obj_name, @@ -240,11 +223,6 @@ PJ_DEF(pj_status_t) pjmedia_echo_destroy(pjmedia_echo_state *echo ) { (*echo->op->ec_destroy)(echo->state); - if (echo->delay_buf) { - pjmedia_delay_buf_destroy(echo->delay_buf); - echo->delay_buf = NULL; - } - pj_pool_release(echo->pool); return PJ_SUCCESS; } @@ -262,7 +240,6 @@ PJ_DEF(pj_status_t) pjmedia_echo_reset(pjmedia_echo_state *echo ) pj_list_push_back(&echo->lat_free, frm); } echo->lat_ready = PJ_FALSE; - pjmedia_delay_buf_reset(echo->delay_buf); echo->op->ec_reset(echo->state); return PJ_SUCCESS; } @@ -274,32 +251,27 @@ PJ_DEF(pj_status_t) pjmedia_echo_reset(pjmedia_echo_state *echo ) PJ_DEF(pj_status_t) pjmedia_echo_playback( pjmedia_echo_state *echo, pj_int16_t *play_frm ) { - if (!echo->lat_ready) { + struct frame *frm; + + if (echo->lat_ready) { + frm = echo->lat_buf.next; + pj_list_erase(frm); + } else { /* We've not built enough latency in the buffer, so put this frame * in the latency buffer list. */ - struct frame *frm; - + frm = echo->lat_free.prev; + pj_list_erase(frm); if (pj_list_empty(&echo->lat_free)) { echo->lat_ready = PJ_TRUE; PJ_LOG(5,(echo->obj_name, "Latency bufferring complete")); - pjmedia_delay_buf_put(echo->delay_buf, play_frm); - return PJ_SUCCESS; } - - frm = echo->lat_free.prev; - pj_list_erase(frm); - - pjmedia_copy_samples(frm->buf, play_frm, echo->samples_per_frame); - pj_list_push_back(&echo->lat_buf, frm); - - } else { - /* Latency buffer is ready (full), so we put this frame in the - * delay buffer. - */ - pjmedia_delay_buf_put(echo->delay_buf, play_frm); } + /* Put the incoming frame into the end of latency buffer */ + pjmedia_copy_samples(frm->buf, play_frm, echo->samples_per_frame); + pj_list_push_back(&echo->lat_buf, frm); + return PJ_SUCCESS; } @@ -313,7 +285,7 @@ PJ_DEF(pj_status_t) pjmedia_echo_capture( pjmedia_echo_state *echo, unsigned options ) { struct frame *oldest_frm; - pj_status_t status, rc; + pj_status_t status; if (!echo->lat_ready) { /* Prefetching to fill in the desired latency */ @@ -323,22 +295,11 @@ PJ_DEF(pj_status_t) pjmedia_echo_capture( pjmedia_echo_state *echo, /* Retrieve oldest frame from the latency buffer */ oldest_frm = echo->lat_buf.next; - pj_list_erase(oldest_frm); /* Cancel echo using this reference frame */ status = pjmedia_echo_cancel(echo, rec_frm, oldest_frm->buf, options, NULL); - /* Move one frame from delay buffer to the latency buffer. */ - rc = pjmedia_delay_buf_get(echo->delay_buf, oldest_frm->buf); - if (rc != PJ_SUCCESS) { - /* Ooops.. no frame! */ - PJ_LOG(5,(echo->obj_name, - "No frame from delay buffer. This will upset EC later")); - pjmedia_zero_samples(oldest_frm->buf, echo->samples_per_frame); - } - pj_list_push_back(&echo->lat_buf, oldest_frm); - return status; } diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c index 70c71215..db554b48 100644 --- a/pjmedia/src/pjmedia/sound_port.c +++ b/pjmedia/src/pjmedia/sound_port.c @@ -19,23 +19,21 @@ */ #include <pjmedia/sound_port.h> #include <pjmedia/alaw_ulaw.h> -#include <pjmedia/delaybuf.h> -#include <pjmedia/echo.h> +#include <pjmedia/echo_port.h> #include <pjmedia/errno.h> +#include <pjmedia/sync_port.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 { + pj_pool_t *pool; int rec_id; int play_id; pj_uint32_t aud_caps; @@ -43,6 +41,8 @@ struct pjmedia_snd_port pjmedia_aud_stream *aud_stream; pjmedia_dir dir; pjmedia_port *port; + pjmedia_port *dn_port; + pjmedia_port *sync_port; unsigned clock_rate; unsigned channel_count; @@ -50,12 +50,9 @@ struct pjmedia_snd_port unsigned bits_per_sample; /* software ec */ - pjmedia_echo_state *ec_state; + pjmedia_port *echo_port; unsigned ec_options; unsigned ec_tail_len; - pj_bool_t ec_suspended; - unsigned ec_suspend_count; - unsigned ec_suspend_limit; }; /* @@ -69,7 +66,7 @@ static pj_status_t play_cb(void *user_data, pjmedia_frame *frame) const unsigned required_size = frame->size; pj_status_t status; - port = snd_port->port; + port = snd_port->dn_port; if (port == NULL) goto no_frame; @@ -83,16 +80,6 @@ static pj_status_t play_cb(void *user_data, pjmedia_frame *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; @@ -101,18 +88,6 @@ no_frame: 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; } @@ -126,15 +101,10 @@ 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; - port = snd_port->port; + port = snd_port->dn_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; @@ -182,12 +152,11 @@ static pj_status_t rec_cb_ext(void *user_data, pjmedia_frame *frame) * 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 ) +static pj_status_t start_sound_device( pjmedia_snd_port *snd_port ) { pjmedia_aud_rec_cb snd_rec_cb; pjmedia_aud_play_cb snd_play_cb; - pjmedia_aud_param param_copy; + pjmedia_aud_param *param; pj_status_t status; /* Check if sound has been started. */ @@ -199,12 +168,13 @@ static pj_status_t start_sound_device( pj_pool_t *pool, snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK, PJ_EBUG); + param = &snd_port->aud_param; + /* Get device caps */ - if (snd_port->aud_param.dir & PJMEDIA_DIR_CAPTURE) { + if (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); + status = pjmedia_aud_dev_get_info(param->rec_id, &dev_info); if (status != PJ_SUCCESS) return status; @@ -213,24 +183,8 @@ static pj_status_t start_sound_device( pj_pool_t *pool, 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->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) { - /* Device supports EC */ - /* Nothing to do */ - } else { - /* 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) { + if (param->ext_fmt.id == PJMEDIA_FORMAT_L16) { snd_rec_cb = &rec_cb; snd_play_cb = &play_cb; } else { @@ -239,7 +193,7 @@ static pj_status_t start_sound_device( pj_pool_t *pool, } /* Open the device */ - status = pjmedia_aud_stream_create(¶m_copy, + status = pjmedia_aud_stream_create(param, snd_rec_cb, snd_play_cb, snd_port, @@ -248,34 +202,6 @@ static pj_status_t start_sound_device( pj_pool_t *pool, 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 but 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 && - 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, 0); - if (status != PJ_SUCCESS) { - pjmedia_aud_stream_destroy(snd_port->aud_stream); - snd_port->aud_stream = NULL; - return status; - } - } - /* Start sound stream. */ status = pjmedia_aud_stream_start(snd_port->aud_stream); if (status != PJ_SUCCESS) { @@ -301,12 +227,6 @@ static pj_status_t stop_sound_device( pjmedia_snd_port *snd_port ) 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; } @@ -420,9 +340,11 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool, PJ_ASSERT_RETURN(pool && prm && p_port, PJ_EINVAL); + pool = pj_pool_create(pool->factory, pool->obj_name, 256, 256, NULL); snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port); PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM); + snd_port->pool = pool; snd_port->dir = prm->dir; snd_port->rec_id = prm->rec_id; snd_port->play_id = prm->play_id; @@ -437,7 +359,7 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool, * If there's no port connected, the sound callback will return * empty signal. */ - status = start_sound_device( pool, snd_port ); + status = start_sound_device( snd_port ); if (status != PJ_SUCCESS) { pjmedia_snd_port_destroy(snd_port); return status; @@ -453,9 +375,19 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool, */ PJ_DEF(pj_status_t) pjmedia_snd_port_destroy(pjmedia_snd_port *snd_port) { + pj_status_t status; + PJ_ASSERT_RETURN(snd_port, PJ_EINVAL); - return stop_sound_device(snd_port); + /* Stop sound port */ + status = stop_sound_device(snd_port); + + /* Disconnect sound port */ + pjmedia_snd_port_disconnect(snd_port); + + pj_pool_release(snd_port->pool); + + return status; } @@ -551,9 +483,9 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_set_ec( pjmedia_snd_port *snd_port, PJ_EINVALIDOP); /* Destroy AEC */ - if (snd_port->ec_state) { - pjmedia_echo_destroy(snd_port->ec_state); - snd_port->ec_state = NULL; + if (snd_port->echo_port) { + pjmedia_port_destroy(snd_port->echo_port); + snd_port->echo_port = NULL; } if (tail_ms != 0) { @@ -564,15 +496,11 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_set_ec( pjmedia_snd_port *snd_port, //delay_ms = (si.rec_latency + si.play_latency) * 1000 / // snd_port->clock_rate; delay_ms = prm.output_latency_ms; - 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); + status = pjmedia_echo_port_create(pool, snd_port->port, + tail_ms, delay_ms, + options, &snd_port->echo_port); if (status != PJ_SUCCESS) - snd_port->ec_state = NULL; - else - snd_port->ec_suspended = PJ_FALSE; + snd_port->echo_port = NULL; } else { PJ_LOG(4,(THIS_FILE, "Echo canceller is now disabled in the " "sound port")); @@ -622,7 +550,7 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_get_ec_tail( pjmedia_snd_port *snd_port, } else { /* We use software EC */ - *p_length = snd_port->ec_state ? snd_port->ec_tail_len : 0; + *p_length = snd_port->echo_port ? snd_port->ec_tail_len : 0; } return PJ_SUCCESS; } @@ -635,6 +563,9 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_connect( pjmedia_snd_port *snd_port, pjmedia_port *port) { pjmedia_port_info *pinfo; + pjmedia_aud_param *param; + pjmedia_sync_param sync_param; + pj_status_t status; PJ_ASSERT_RETURN(snd_port && port, PJ_EINVAL); @@ -656,6 +587,41 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_connect( pjmedia_snd_port *snd_port, /* Port is okay. */ snd_port->port = port; + + /* Create software EC if parameter specifies EC but device + * doesn't support EC. Only do this if the format is PCM! + */ + param = &snd_port->aud_param; + if ((param->flags & PJMEDIA_AUD_DEV_CAP_EC) && + (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)==0 && + param->ext_fmt.id == PJMEDIA_FORMAT_PCM) + { + if ((param->flags & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) { + param->flags |= PJMEDIA_AUD_DEV_CAP_EC_TAIL; + param->ec_tail_ms = AEC_TAIL; + PJ_LOG(4,(THIS_FILE, "AEC tail is set to default %u ms", + param->ec_tail_ms)); + } + + status = pjmedia_snd_port_set_ec(snd_port, snd_port->pool, + param->ec_tail_ms, 0); + if (status != PJ_SUCCESS) + return status; + } + + /* Create sync port */ + pj_bzero(&sync_param, sizeof(sync_param)); + sync_param.options = PJMEDIA_SYNC_DONT_DESTROY_DN; + status = pjmedia_sync_port_create(snd_port->pool, + (snd_port->echo_port? + snd_port->echo_port:snd_port->port), + &sync_param, &snd_port->sync_port); + if (status != PJ_SUCCESS) + return status; + + /* Update down port of sound port */ + snd_port->dn_port = snd_port->sync_port; + return PJ_SUCCESS; } @@ -678,6 +644,21 @@ 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; + snd_port->dn_port = NULL; + + /* Destroy sync port */ + if (snd_port->sync_port) { + pjmedia_port_destroy(snd_port->sync_port); + snd_port->sync_port = NULL; + } + + /* Destroy EC port */ + if (snd_port->echo_port) { + pjmedia_port_destroy(snd_port->echo_port); + snd_port->echo_port = NULL; + snd_port->ec_tail_len = 0; + snd_port->ec_options = 0; + } return PJ_SUCCESS; } diff --git a/pjmedia/src/pjmedia/sync_port.c b/pjmedia/src/pjmedia/sync_port.c new file mode 100644 index 00000000..1508df22 --- /dev/null +++ b/pjmedia/src/pjmedia/sync_port.c @@ -0,0 +1,274 @@ +/* $Id$ */ +/* + * Copyright (C) 2009 Teluu Inc. (http://www.teluu.com) + * + * 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/sync_port.h> +#include <pjmedia/clock.h> +#include <pjmedia/delaybuf.h> +#include <pjmedia/errno.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/pool.h> + +#define THIS_FILE "sync_port.c" +#define SIGNATURE PJMEDIA_PORT_SIGNATURE('S', 'Y', 'N', 'C') + +typedef struct pjmedia_sync_port +{ + pjmedia_port base; + pjmedia_sync_param param; + pjmedia_port *dn_port; + pj_int16_t *framebuf; + pjmedia_clock *clock; + pjmedia_delay_buf *delay_buf; + pjmedia_delay_buf *delay_buf2; + +} pjmedia_sync_port; + + +/* + * CALLBACKS + */ + +/* Clock callback */ +static void clock_cb(const pj_timestamp *ts, void *user_data) +{ + pjmedia_sync_port *port = (pjmedia_sync_port*)user_data; + pjmedia_frame f; + pj_status_t status; + + /* call dn_port.put_frame() */ + pj_bzero(&f, sizeof(f)); + f.buf = port->framebuf; + f.size = port->base.info.samples_per_frame << 1; + f.type = PJMEDIA_TYPE_AUDIO; + f.timestamp = *ts; + + status = pjmedia_delay_buf_get(port->delay_buf, (pj_int16_t*)f.buf); + if (status == PJ_SUCCESS) { + pjmedia_port_put_frame(port->dn_port, &f); + } + + /* call dn_port.get_frame() */ + pj_bzero(&f, sizeof(f)); + f.buf = port->framebuf; + f.size = port->base.info.samples_per_frame << 1; + f.type = PJMEDIA_TYPE_AUDIO; + f.timestamp = *ts; + + status = pjmedia_port_get_frame(port->dn_port, &f); + if (status != PJ_SUCCESS || f.type != PJMEDIA_FRAME_TYPE_AUDIO) { + pjmedia_zero_samples((pj_int16_t*)f.buf, + port->base.info.samples_per_frame); + } + pjmedia_delay_buf_put(port->delay_buf2, (pj_int16_t*)f.buf); +} + +/* Get frame (from this port) when port is using own clock. */ +static pj_status_t get_frame_clock(pjmedia_port *this_port, + pjmedia_frame *frame) +{ + pjmedia_sync_port *port = (pjmedia_sync_port*)this_port; + pj_status_t status; + + PJ_ASSERT_RETURN(port, PJ_EINVAL); + + /* get frame from delay buf */ + status = pjmedia_delay_buf_get(port->delay_buf2, (pj_int16_t*)frame->buf); + if (status != PJ_SUCCESS) { + return status; + } + + frame->size = this_port->info.samples_per_frame << 1; + frame->type = PJMEDIA_TYPE_AUDIO; + + return PJ_SUCCESS; +} + +/* Get frame (from this port). */ +static pj_status_t get_frame(pjmedia_port *this_port, + pjmedia_frame *frame) +{ + pjmedia_sync_port *port = (pjmedia_sync_port*)this_port; + pjmedia_frame f; + pj_status_t status; + + PJ_ASSERT_RETURN(port, PJ_EINVAL); + + /* get frame */ + status = pjmedia_port_get_frame(port->dn_port, frame); + if (status != PJ_SUCCESS) + return status; + + /* put frame from delay buf */ + pj_bzero(&f, sizeof(f)); + f.buf = port->framebuf; + f.size = this_port->info.samples_per_frame << 1; + f.type = PJMEDIA_TYPE_AUDIO; + f.timestamp = frame->timestamp; + + status = pjmedia_delay_buf_get(port->delay_buf, (pj_int16_t*)f.buf); + if (status != PJ_SUCCESS) + return status; + + status = pjmedia_port_put_frame(port->dn_port, &f); + if (status != PJ_SUCCESS) + return status; + + return PJ_SUCCESS; +} + +/* Put frame (to this port). */ +static pj_status_t put_frame(pjmedia_port *this_port, + const pjmedia_frame *frame) +{ + pjmedia_sync_port *port = (pjmedia_sync_port*)this_port; + + PJ_ASSERT_RETURN(port, PJ_EINVAL); + + /* put frame to delay buf */ + if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO) { + pjmedia_zero_samples((pj_int16_t*)frame->buf, + this_port->info.samples_per_frame); + } + + return pjmedia_delay_buf_put(port->delay_buf, (pj_int16_t*)frame->buf); +} + +/* Destroy the port. */ +static pj_status_t on_destroy(pjmedia_port *this_port) +{ + pjmedia_sync_port *port = (pjmedia_sync_port*)this_port; + + PJ_ASSERT_RETURN(port, PJ_EINVAL); + + if (port->clock) { + pjmedia_clock_stop(port->clock); + pjmedia_clock_destroy(port->clock); + port->clock = NULL; + } + + if (port->delay_buf) { + pjmedia_delay_buf_destroy(port->delay_buf); + port->delay_buf = NULL; + } + + if (port->delay_buf2) { + pjmedia_delay_buf_destroy(port->delay_buf2); + port->delay_buf2 = NULL; + } + + if (port->dn_port && + (port->param.options & PJMEDIA_SYNC_DONT_DESTROY_DN) == 0) + { + pjmedia_port_destroy(port->dn_port); + } + + return PJ_SUCCESS; +} + + +/* + * API FUNCTIONS + */ + +/* Create the sync port. */ +PJ_DEF(pj_status_t) pjmedia_sync_port_create(pj_pool_t *pool, + pjmedia_port *dn_port, + const pjmedia_sync_param *param, + pjmedia_port **p_port ) +{ + pjmedia_sync_port *sync; + pj_str_t name; + unsigned ptime; + pj_status_t status; + + PJ_ASSERT_RETURN(pool && dn_port && p_port, PJ_EINVAL); + PJ_ASSERT_RETURN(dn_port->info.bits_per_sample==16, PJ_EINVAL); + + sync = PJ_POOL_ZALLOC_T(pool, pjmedia_sync_port); + sync->framebuf = (pj_int16_t*) + pj_pool_zalloc(pool, dn_port->info.samples_per_frame<<1); + + /* Init port */ + if (param) + sync->param = *param; + sync->dn_port = dn_port; + + /* Init port info */ + name = pj_str(pool->obj_name); + pjmedia_port_info_init(&sync->base.info, + &name, SIGNATURE, + dn_port->info.clock_rate, + dn_port->info.channel_count, + dn_port->info.bits_per_sample, + dn_port->info.samples_per_frame); + + /* Init port op */ + sync->base.get_frame = &get_frame; + sync->base.put_frame = &put_frame; + sync->base.on_destroy = &on_destroy; + + /* Create delay buffer to compensate drifts */ + ptime = dn_port->info.samples_per_frame * 1000 / + dn_port->info.channel_count / + dn_port->info.clock_rate; + status = pjmedia_delay_buf_create(pool, name.ptr, + dn_port->info.clock_rate, + dn_port->info.samples_per_frame, + dn_port->info.channel_count, + PJMEDIA_SOUND_BUFFER_COUNT * ptime, + 0, + &sync->delay_buf); + if (status != PJ_SUCCESS) + goto on_error; + + /* Create clock if specified */ + if (sync->param.options & PJMEDIA_SYNC_USE_EXT_CLOCK) { + status = pjmedia_delay_buf_create(pool, name.ptr, + dn_port->info.clock_rate, + dn_port->info.samples_per_frame, + dn_port->info.channel_count, + PJMEDIA_SOUND_BUFFER_COUNT * ptime, + 0, + &sync->delay_buf2); + if (status != PJ_SUCCESS) + goto on_error; + + status = pjmedia_clock_create(pool, + dn_port->info.clock_rate, + dn_port->info.channel_count, + dn_port->info.samples_per_frame, + 0, + &clock_cb, + sync, + &sync->clock); + if (status != PJ_SUCCESS) + goto on_error; + + sync->base.get_frame = &get_frame_clock; + } + + /* Done */ + *p_port = &sync->base; + return PJ_SUCCESS; + +on_error: + on_destroy(&sync->base); + return status; +} |