diff options
author | Benny Prijono <bennylp@teluu.com> | 2006-08-04 11:08:00 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2006-08-04 11:08:00 +0000 |
commit | 4a01731c11f29c2042459f37516ec313c4e8a495 (patch) | |
tree | d5049d0a6bad525526f93efed107b06097cec7ff | |
parent | 60fb5677579381a10db65cf32333fbabdd4a715e (diff) |
More experimentation with AEC: (1) added media port to create bidirectional port from two unidirectional ports, (2) split AEC functionality into AEC algorithm (aec.h) and AEC media port (aec_port.h), (3) Added the AEC functionality in the sound_port.c.
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@646 74dad513-b988-da41-8d7b-12977e46ad98
-rw-r--r-- | pjmedia/build/pjmedia.dsp | 16 | ||||
-rw-r--r-- | pjmedia/include/pjmedia.h | 2 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/aec.h | 153 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/bidirectional.h | 65 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/aec_port.c | 95 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/aec_speex.c | 257 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/bidirectional.c | 75 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/sound_port.c | 43 | ||||
-rw-r--r-- | pjsip-apps/build/samples.dsp | 4 | ||||
-rw-r--r-- | pjsip-apps/src/samples/aectest.c | 221 | ||||
-rw-r--r-- | pjsip-apps/src/samples/debug.c | 2 |
11 files changed, 868 insertions, 65 deletions
diff --git a/pjmedia/build/pjmedia.dsp b/pjmedia/build/pjmedia.dsp index 62a544d3..6a1a3d0b 100644 --- a/pjmedia/build/pjmedia.dsp +++ b/pjmedia/build/pjmedia.dsp @@ -91,10 +91,18 @@ SOURCE=..\src\pjmedia\aec_port.c # End Source File
# Begin Source File
+SOURCE=..\src\pjmedia\aec_speex.c
+# End Source File
+# Begin Source File
+
SOURCE=..\src\pjmedia\alaw_ulaw.c
# End Source File
# Begin Source File
+SOURCE=..\src\pjmedia\bidirectional.c
+# End Source File
+# Begin Source File
+
SOURCE=..\src\pjmedia\clock_thread.c
# End Source File
# Begin Source File
@@ -231,10 +239,18 @@ SOURCE=..\src\pjmedia\wave.c # PROP Default_Filter "h;hpp;hxx;hm;inl"
# Begin Source File
+SOURCE=..\include\pjmedia\aec.h
+# End Source File
+# Begin Source File
+
SOURCE=..\include\pjmedia\aec_port.h
# End Source File
# Begin Source File
+SOURCE=..\include\pjmedia\bidirectional.h
+# End Source File
+# Begin Source File
+
SOURCE=..\include\pjmedia\clock.h
# End Source File
# Begin Source File
diff --git a/pjmedia/include/pjmedia.h b/pjmedia/include/pjmedia.h index fc79c463..049e36c8 100644 --- a/pjmedia/include/pjmedia.h +++ b/pjmedia/include/pjmedia.h @@ -25,7 +25,9 @@ */ #include <pjmedia/types.h> +#include <pjmedia/aec.h> #include <pjmedia/aec_port.h> +#include <pjmedia/bidirectional.h> #include <pjmedia/clock.h> #include <pjmedia/codec.h> #include <pjmedia/conference.h> diff --git a/pjmedia/include/pjmedia/aec.h b/pjmedia/include/pjmedia/aec.h new file mode 100644 index 00000000..a2354ef3 --- /dev/null +++ b/pjmedia/include/pjmedia/aec.h @@ -0,0 +1,153 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 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 + */ +#ifndef __PJMEDIA_AEC_H__ +#define __PJMEDIA_AEC_H__ + + +/** + * @file aec.h + * @brief AEC (Accoustic Echo Cancellation) API. + */ +#include <pjmedia/types.h> + + + +/** + * @defgroup PJMEDIA_AEC AEC AEC (Accoustic Echo Cancellation) + * @ingroup PJMEDIA_PORT + * @brief AEC (Accoustic Echo Cancellation) API. + * @{ + */ + + +PJ_BEGIN_DECL + + +/** + * Opaque type for PJMEDIA AEC. + */ +typedef struct pjmedia_aec pjmedia_aec; + + +/** + * Create the AEC. + * + * @param pool Pool to allocate memory. + * @param clock_rate Media clock rate/sampling rate. + * @param samples_per_frame Number of samples per frame. + * @param tail_size Tail length, in number of samples. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_aec_create( pj_pool_t *pool, + unsigned clock_rate, + unsigned samples_per_frame, + unsigned tail_size, + unsigned options, + pjmedia_aec **p_aec ); + + +/** + * Destroy the AEC. + * + * @param aec The AEC. + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_aec_destroy(pjmedia_aec *aec ); + + +/** + * Let the AEC knows that a frame has been played to the speaker. + * The AEC will keep the frame in its internal buffer, to be used + * when cancelling the echo with #pjmedia_aec_capture(). + * + * @param aec The AEC. + * @param ts Optional timestamp to let the AEC knows the + * position of the frame relative to capture + * position. If NULL, the AEC assumes that + * application will supply the AEC with continuously + * increasing timestamp. + * @param play_frm Sample buffer containing frame to be played + * (or has been played) to the playback device. + * The frame must contain exactly samples_per_frame + * number of samples. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_aec_playback( pjmedia_aec *aec, + pj_int16_t *play_frm ); + + +/** + * Let the AEC knows that a frame has been captured from the microphone. + * The AEC will cancel the echo from the captured signal, using the + * internal buffer (supplied by #pjmedia_aec_playback()) as the + * FES (Far End Speech) reference. + * + * @param aec The AEC. + * @param rec_frm On input, it contains the input signal (captured + * from microphone) which echo is to be removed. + * Upon returning this function, this buffer contain + * the processed signal with the echo removed. + * The frame must contain exactly samples_per_frame + * number of samples. + * @param options Echo cancellation options, reserved for future use. + * Put zero for now. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_aec_capture( pjmedia_aec *aec, + pj_int16_t *rec_frm, + unsigned options ); + + +/** + * Perform echo cancellation. + * + * @param aec The AEC. + * @param rec_frm On input, it contains the input signal (captured + * from microphone) which echo is to be removed. + * Upon returning this function, this buffer contain + * the processed signal with the echo removed. + * @param play_frm Sample buffer containing frame to be played + * (or has been played) to the playback device. + * The frame must contain exactly samples_per_frame + * number of samples. + * @param options Echo cancellation options, reserved for future use. + * Put zero for now. + * @param reserved Reserved for future use, put NULL for now. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_aec_cancel_echo( pjmedia_aec *aec, + pj_int16_t *rec_frm, + const pj_int16_t *play_frm, + unsigned options, + void *reserved ); + + +PJ_END_DECL + +/** + * @} + */ + + +#endif /* __PJMEDIA_AEC_H__ */ + diff --git a/pjmedia/include/pjmedia/bidirectional.h b/pjmedia/include/pjmedia/bidirectional.h new file mode 100644 index 00000000..5b16d4c6 --- /dev/null +++ b/pjmedia/include/pjmedia/bidirectional.h @@ -0,0 +1,65 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 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 + */ +#ifndef __PJMEDIA_BIDIRECTIONAL_H__ +#define __PJMEDIA_BIDIRECTIONAL_H__ + +/** + * @file bidirectional.h + * @brief Create bidirectional port from two unidirectional ports. + */ +#include <pjmedia/port.h> + + +/** + * @defgroup PJMEDIA_BIDIRECTIONAL_PORT Bidirectional Port + * @ingroup PJMEDIA_PORT + * @brief Create bidirectional port from two unidirectional ports. + * @{ + */ + + +PJ_BEGIN_DECL + + +/** + * Create bidirectional port from two unidirectional ports + * + * @param pool Pool to allocate memory. + * @param get_port Port where get_frame() will be directed to. + * @param put_port Port where put_frame() will be directed to. + * @param p_port Pointer to receive the port instance. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_bidirectional_port_create(pj_pool_t *pool, + pjmedia_port *get_port, + pjmedia_port *put_port, + pjmedia_port **p_port ); + + + +PJ_END_DECL + +/** + * @} + */ + + +#endif /* __PJMEDIA_BIDIRECTIONAL_H__ */ + diff --git a/pjmedia/src/pjmedia/aec_port.c b/pjmedia/src/pjmedia/aec_port.c index f772d859..aa56b175 100644 --- a/pjmedia/src/pjmedia/aec_port.c +++ b/pjmedia/src/pjmedia/aec_port.c @@ -17,7 +17,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <pjmedia/aec_port.h> -#include "../pjmedia-codec/speex/speex_echo.h" +#include <pjmedia/aec.h> #include <pjmedia/errno.h> #include <pj/assert.h> #include <pj/log.h> @@ -26,16 +26,13 @@ #define THIS_FILE "aec_port.c" #define SIGNATURE PJMEDIA_PORT_SIGNATURE('A', 'E', 'C', ' ') +#define BUF_COUNT 32 - -struct aec_port +struct aec { pjmedia_port base; pjmedia_port *dn_port; - SpeexEchoState *state; - pj_int16_t *tmp_frame; - pj_bool_t has_frame; - pj_int16_t *last_frame; + pjmedia_aec *aec; }; @@ -52,54 +49,37 @@ PJ_DEF(pj_status_t) pjmedia_aec_port_create( pj_pool_t *pool, pjmedia_port **p_port ) { const pj_str_t AEC = { "AEC", 3 }; - struct aec_port *aec_port; - int sampling_rate; + struct aec *aec; + pj_status_t status; PJ_ASSERT_RETURN(pool && dn_port && p_port, PJ_EINVAL); PJ_ASSERT_RETURN(dn_port->info.bits_per_sample==16 && tail_length, PJ_EINVAL); - /* Create and initialize the port */ - aec_port = pj_pool_zalloc(pool, sizeof(struct aec_port)); + /* Create the port and the AEC itself */ + aec = pj_pool_zalloc(pool, sizeof(struct aec)); - pjmedia_port_info_init(&aec_port->base.info, &AEC, SIGNATURE, + pjmedia_port_info_init(&aec->base.info, &AEC, SIGNATURE, dn_port->info.clock_rate, dn_port->info.channel_count, dn_port->info.bits_per_sample, dn_port->info.samples_per_frame); - aec_port->state = speex_echo_state_init(dn_port->info.samples_per_frame, - tail_length); - - /* Set sampling rate */ - sampling_rate = 0; - speex_echo_ctl(aec_port->state, SPEEX_ECHO_GET_SAMPLING_RATE, - &sampling_rate); - sampling_rate = dn_port->info.clock_rate; - speex_echo_ctl(aec_port->state, SPEEX_ECHO_SET_SAMPLING_RATE, - &sampling_rate); + status = pjmedia_aec_create(pool, dn_port->info.clock_rate, + dn_port->info.samples_per_frame, + tail_length, 0, &aec->aec); + if (status != PJ_SUCCESS) + return status; /* More init */ - aec_port->dn_port = dn_port; - aec_port->base.get_frame = &aec_get_frame; - aec_port->base.put_frame = &aec_put_frame; - aec_port->base.on_destroy = &aec_on_destroy; - - aec_port->last_frame = pj_pool_zalloc(pool, sizeof(pj_int16_t) * - dn_port->info.samples_per_frame); - aec_port->tmp_frame = pj_pool_zalloc(pool, sizeof(pj_int16_t) * - dn_port->info.samples_per_frame); + aec->dn_port = dn_port; + aec->base.get_frame = &aec_get_frame; + aec->base.put_frame = &aec_put_frame; + aec->base.on_destroy = &aec_on_destroy; /* Done */ - *p_port = &aec_port->base; - - PJ_LOG(4,(THIS_FILE, "AEC created for port %.*s, clock_rate=%d, " - "samples per frame=%d, tail length=%d ms", - (int)dn_port->info.name.slen, - dn_port->info.name.ptr, - dn_port->info.clock_rate, - dn_port->info.samples_per_frame, - tail_length * 1000 / dn_port->info.clock_rate)); + *p_port = &aec->base; + return PJ_SUCCESS; } @@ -107,58 +87,49 @@ PJ_DEF(pj_status_t) pjmedia_aec_port_create( pj_pool_t *pool, static pj_status_t aec_put_frame(pjmedia_port *this_port, const pjmedia_frame *frame) { - struct aec_port *aec_port = (struct aec_port*)this_port; + struct aec *aec = (struct aec*)this_port; PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL); - if (frame->type == PJMEDIA_FRAME_TYPE_NONE || !aec_port->has_frame) { - return pjmedia_port_put_frame(aec_port->dn_port, frame); + if (frame->type == PJMEDIA_FRAME_TYPE_NONE ) { + return pjmedia_port_put_frame(aec->dn_port, frame); } PJ_ASSERT_RETURN(frame->size == this_port->info.samples_per_frame * 2, PJ_EINVAL); - speex_echo_cancel(aec_port->state, - (const spx_int16_t*)frame->buf, - (const spx_int16_t*)aec_port->last_frame, - (spx_int16_t*)aec_port->tmp_frame, - NULL); - - pjmedia_copy_samples(frame->buf, aec_port->tmp_frame, - this_port->info.samples_per_frame); + pjmedia_aec_capture(aec->aec, frame->buf, 0); - return pjmedia_port_put_frame(aec_port->dn_port, frame); + return pjmedia_port_put_frame(aec->dn_port, frame); } static pj_status_t aec_get_frame( pjmedia_port *this_port, pjmedia_frame *frame) { - struct aec_port *aec_port = (struct aec_port*)this_port; + struct aec *aec = (struct aec*)this_port; pj_status_t status; PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL); - status = pjmedia_port_get_frame(aec_port->dn_port, frame); - if (status==PJ_SUCCESS && frame->type==PJMEDIA_FRAME_TYPE_AUDIO) { - aec_port->has_frame = PJ_TRUE; - pjmedia_copy_samples(aec_port->tmp_frame, frame->buf, - this_port->info.samples_per_frame); - } else { - aec_port->has_frame = PJ_FALSE; + status = pjmedia_port_get_frame(aec->dn_port, frame); + if (status!=PJ_SUCCESS || frame->type!=PJMEDIA_FRAME_TYPE_AUDIO) { + pjmedia_zero_samples(frame->buf, this_port->info.samples_per_frame); } + pjmedia_aec_playback(aec->aec, frame->buf); + return status; } static pj_status_t aec_on_destroy(pjmedia_port *this_port) { - struct aec_port *aec_port = (struct aec_port*)this_port; + struct aec *aec = (struct aec*)this_port; PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL); - speex_echo_state_destroy(aec_port->state); + pjmedia_aec_destroy(aec->aec); return PJ_SUCCESS; } diff --git a/pjmedia/src/pjmedia/aec_speex.c b/pjmedia/src/pjmedia/aec_speex.c new file mode 100644 index 00000000..76a8dd44 --- /dev/null +++ b/pjmedia/src/pjmedia/aec_speex.c @@ -0,0 +1,257 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 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/aec.h> +#include <pjmedia/errno.h> +#include <pjmedia/silencedet.h> +#include <pj/assert.h> +#include <pj/lock.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <speex/speex_echo.h> + + +#define THIS_FILE "aec_speex.c" +#define BUF_COUNT 16 + + +struct frame +{ + pj_int16_t *buf; +}; + +struct pjmedia_aec +{ + SpeexEchoState *state; + unsigned samples_per_frame; + unsigned options; + pj_int16_t *tmp_frame; + + pj_lock_t *lock; /* To protect buffers, if required */ + + unsigned rpos; /* Index to get oldest frame. */ + unsigned wpos; /* Index to put newest frame. */ + struct frame frames[BUF_COUNT]; /* Playback frame buffers. */ +}; + + + +/* + * Create the AEC. + */ +PJ_DEF(pj_status_t) pjmedia_aec_create( pj_pool_t *pool, + unsigned clock_rate, + unsigned samples_per_frame, + unsigned tail_size, + unsigned options, + pjmedia_aec **p_aec ) +{ + pjmedia_aec *aec; + int sampling_rate; + unsigned i; + pj_status_t status; + + aec = pj_pool_zalloc(pool, sizeof(pjmedia_aec)); + PJ_ASSERT_RETURN(aec != NULL, PJ_ENOMEM); + + status = pj_lock_create_simple_mutex(pool, "aec%p", &aec->lock); + if (status != PJ_SUCCESS) + return status; + + aec->samples_per_frame = samples_per_frame; + aec->options = options; + + aec->state = speex_echo_state_init(samples_per_frame,tail_size); + if (aec->state == NULL) { + pj_lock_destroy(aec->lock); + return PJ_ENOMEM; + } + + /* Set sampling rate */ + sampling_rate = clock_rate; + speex_echo_ctl(aec->state, SPEEX_ECHO_SET_SAMPLING_RATE, + &sampling_rate); + + /* Create temporary frame for echo cancellation */ + aec->tmp_frame = pj_pool_zalloc(pool, sizeof(pj_int16_t) * + samples_per_frame); + PJ_ASSERT_RETURN(aec->tmp_frame != NULL, PJ_ENOMEM); + + /* Create internal playback buffers */ + for (i=0; i<BUF_COUNT; ++i) { + aec->frames[i].buf = pj_pool_zalloc(pool, samples_per_frame * 2); + PJ_ASSERT_RETURN(aec->frames[i].buf != NULL, PJ_ENOMEM); + } + + + /* Done */ + *p_aec = aec; + + PJ_LOG(4,(THIS_FILE, "Echo canceller/AEC created, clock_rate=%d, " + "samples per frame=%d, tail length=%d ms", + clock_rate, + samples_per_frame, + tail_size * 1000 / clock_rate)); + return PJ_SUCCESS; + +} + + +/* + * Destroy AEC + */ +PJ_DEF(pj_status_t) pjmedia_aec_destroy(pjmedia_aec *aec ) +{ + PJ_ASSERT_RETURN(aec && aec->state, PJ_EINVAL); + + if (aec->state) { + speex_echo_state_destroy(aec->state); + aec->state = NULL; + } + + if (aec->lock) { + pj_lock_destroy(aec->lock); + aec->lock = NULL; + } + + return PJ_SUCCESS; +} + + +/* + * Let the AEC knows that a frame has been played to the speaker. + */ +PJ_DEF(pj_status_t) pjmedia_aec_playback(pjmedia_aec *aec, + pj_int16_t *play_frm ) +{ + /* Sanity checks */ + PJ_ASSERT_RETURN(aec && play_frm, PJ_EINVAL); + + /* The AEC must be configured to support internal playback buffer */ + PJ_ASSERT_RETURN(aec->frames[0].buf != NULL, PJ_EINVALIDOP); + + pj_lock_acquire(aec->lock); + + /* Check for overflows */ + if (aec->wpos == aec->rpos) { + PJ_LOG(5,(THIS_FILE, "AEC overflow (playback runs faster, " + "wpos=%d, rpos=%d)", + aec->wpos, aec->rpos)); + aec->rpos = (aec->wpos - BUF_COUNT/2) % BUF_COUNT; + speex_echo_state_reset(aec->state); + } + + /* Save fhe frame */ + pjmedia_copy_samples(aec->frames[aec->wpos].buf, + play_frm, aec->samples_per_frame); + aec->wpos = (aec->wpos+1) % BUF_COUNT; + + pj_lock_release(aec->lock); + + return PJ_SUCCESS; +} + + +/* + * Let the AEC knows that a frame has been captured from the microphone. + */ +PJ_DEF(pj_status_t) pjmedia_aec_capture( pjmedia_aec *aec, + pj_int16_t *rec_frm, + unsigned options ) +{ + pj_status_t status; + + /* Sanity checks */ + PJ_ASSERT_RETURN(aec && rec_frm, PJ_EINVAL); + + /* The AEC must be configured to support internal playback buffer */ + PJ_ASSERT_RETURN(aec->frames[0].buf != NULL, PJ_EINVALIDOP); + + /* Lock mutex */ + pj_lock_acquire(aec->lock); + + + /* Check for underflow */ + if (aec->rpos == aec->wpos) { + /* Return frame as it is */ + pj_lock_release(aec->lock); + + PJ_LOG(5,(THIS_FILE, "AEC underflow (capture runs faster than " + "playback, wpos=%d, rpos=%d)", + aec->wpos, aec->rpos)); + aec->rpos = (aec->wpos - BUF_COUNT/2) % BUF_COUNT; + speex_echo_state_reset(aec->state); + + return PJ_SUCCESS; + } + + + /* Cancel echo */ + status = pjmedia_aec_cancel_echo(aec, rec_frm, + aec->frames[aec->rpos].buf, options, + NULL); + + aec->rpos = (aec->rpos + 1) % BUF_COUNT; + + pj_lock_release(aec->lock); + return status; +} + + +/* + * Perform echo cancellation. + */ +PJ_DEF(pj_status_t) pjmedia_aec_cancel_echo( pjmedia_aec *aec, + pj_int16_t *rec_frm, + const pj_int16_t *play_frm, + unsigned options, + void *reserved ) +{ + unsigned level0, level1; + + /* Sanity checks */ + PJ_ASSERT_RETURN(aec && rec_frm && play_frm && options==0 && + reserved==NULL, PJ_EINVAL); + + /* Cancel echo, put output in temporary buffer */ + speex_echo_cancel(aec->state, (const spx_int16_t*)rec_frm, + (const spx_int16_t*)play_frm, + (spx_int16_t*)aec->tmp_frame, NULL); + +#if 0 + level0 = pjmedia_calc_avg_signal(rec_frm, aec->samples_per_frame); + level1 = pjmedia_calc_avg_signal(aec->tmp_frame, aec->samples_per_frame); + + if (level1 < level0) { + PJ_LOG(5,(THIS_FILE, "Input signal reduced from %d to %d", + level0, level1)); + } +#else + PJ_UNUSED_ARG(level0); + PJ_UNUSED_ARG(level1); +#endif + + /* Copy temporary buffer back to original rec_frm */ + pjmedia_copy_samples(rec_frm, aec->tmp_frame, aec->samples_per_frame); + + return PJ_SUCCESS; + +} + diff --git a/pjmedia/src/pjmedia/bidirectional.c b/pjmedia/src/pjmedia/bidirectional.c new file mode 100644 index 00000000..7f2cb0ba --- /dev/null +++ b/pjmedia/src/pjmedia/bidirectional.c @@ -0,0 +1,75 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 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/bidirectional.h> +#include <pj/pool.h> + + +#define THIS_FILE "bidirectional.c" +#define SIGNATURE PJMEDIA_PORT_SIGNATURE('B', 'D', 'I', 'R') + +struct bidir_port +{ + pjmedia_port base; + pjmedia_port *get_port; + pjmedia_port *put_port; +}; + + +static pj_status_t put_frame(pjmedia_port *this_port, + const pjmedia_frame *frame) +{ + struct bidir_port *p = (struct bidir_port*)this_port; + return pjmedia_port_put_frame(p->put_port, frame); +} + + +static pj_status_t get_frame(pjmedia_port *this_port, + pjmedia_frame *frame) +{ + struct bidir_port *p = (struct bidir_port*)this_port; + return pjmedia_port_get_frame(p->get_port, frame); +} + + +PJ_DEF(pj_status_t) pjmedia_bidirectional_port_create( pj_pool_t *pool, + pjmedia_port *get_port, + pjmedia_port *put_port, + pjmedia_port **p_port ) +{ + struct bidir_port *port; + + port = pj_pool_zalloc(pool, sizeof(struct bidir_port)); + + pjmedia_port_info_init(&port->base.info, &get_port->info.name, SIGNATURE, + get_port->info.clock_rate, + get_port->info.channel_count, + get_port->info.bits_per_sample, + get_port->info.samples_per_frame); + + port->get_port = get_port; + port->put_port = put_port; + + port->base.get_frame = &get_frame; + port->base.put_frame = &put_frame; + + *p_port = &port->base; + + return PJ_SUCCESS; +} + diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c index 08f2232a..ee3e61c9 100644 --- a/pjmedia/src/pjmedia/sound_port.c +++ b/pjmedia/src/pjmedia/sound_port.c @@ -17,6 +17,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <pjmedia/sound_port.h> +#include <pjmedia/aec.h> #include <pjmedia/errno.h> #include <pjmedia/plc.h> #include <pj/assert.h> @@ -24,9 +25,16 @@ #include <pj/rand.h> #include <pj/string.h> /* pj_memset() */ +#ifndef PJMEDIA_SOUND_HAS_AEC +# define PJMEDIA_SOUND_HAS_AEC 1 +#endif -//#define SIMULATE_LOST_PCT 20 +#if defined(PJMEDIA_SOUND_HAS_AEC) && PJMEDIA_SOUND_HAS_AEC!=0 +# include <speex/speex_echo.h> +#endif +//#define SIMULATE_LOST_PCT 20 +#define AEC_TAIL 500 /* in ms */ #define THIS_FILE "sound_port.c" @@ -48,6 +56,7 @@ struct pjmedia_snd_port pjmedia_port *port; unsigned options; + pjmedia_aec *aec; pjmedia_plc *plc; unsigned clock_rate; @@ -105,6 +114,11 @@ static pj_status_t play_cb(/* in */ void *user_data, if (snd_port->plc) pjmedia_plc_save(snd_port->plc, output); + if (snd_port->aec) { + pjmedia_aec_playback(snd_port->aec, output); + } + + return PJ_SUCCESS; no_frame: @@ -121,6 +135,10 @@ no_frame: } + if (snd_port->aec) { + pjmedia_aec_playback(snd_port->aec, output); + } + return PJ_SUCCESS; } @@ -131,13 +149,18 @@ no_frame: */ static pj_status_t rec_cb(/* in */ void *user_data, /* in */ pj_uint32_t timestamp, - /* in */ const void *input, + /* in */ void *input, /* in*/ unsigned size) { pjmedia_snd_port *snd_port = user_data; pjmedia_port *port; pjmedia_frame frame; + /* Cancel echo */ + if (snd_port->aec) { + pjmedia_aec_capture(snd_port->aec, input, 0); + } + /* We're risking accessing the port without holding any mutex. * It's possible that port is disconnected then destroyed while * we're trying to access it. @@ -227,6 +250,16 @@ static pj_status_t start_sound_device( pj_pool_t *pool, snd_port->plc = NULL; } + /* Create AEC only when direction is full duplex */ + if (snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) { + status = pjmedia_aec_create(pool, snd_port->clock_rate, + snd_port->samples_per_frame, + snd_port->clock_rate * AEC_TAIL / 1000, + 0, &snd_port->aec); + if (status != PJ_SUCCESS) + snd_port->aec = NULL; + } + /* Start sound stream. */ status = pjmedia_snd_stream_start(snd_port->snd_stream); if (status != PJ_SUCCESS) { @@ -252,6 +285,12 @@ static pj_status_t stop_sound_device( pjmedia_snd_port *snd_port ) snd_port->snd_stream = NULL; } + /* Destroy AEC */ + if (snd_port->aec) { + pjmedia_aec_destroy(snd_port->aec); + snd_port->aec = NULL; + } + return PJ_SUCCESS; } diff --git a/pjsip-apps/build/samples.dsp b/pjsip-apps/build/samples.dsp index f479c7bd..1b53137b 100644 --- a/pjsip-apps/build/samples.dsp +++ b/pjsip-apps/build/samples.dsp @@ -86,6 +86,10 @@ CFG=samples - Win32 Debug # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" # Begin Source File +SOURCE=..\src\samples\aectest.c +# End Source File +# Begin Source File + SOURCE=..\src\samples\confbench.c # End Source File # Begin Source File diff --git a/pjsip-apps/src/samples/aectest.c b/pjsip-apps/src/samples/aectest.c new file mode 100644 index 00000000..a2770728 --- /dev/null +++ b/pjsip-apps/src/samples/aectest.c @@ -0,0 +1,221 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 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 + */ + + +/** + * \page page_pjmedia_samples_aectest_c Samples: AEC Test (aectest.c) + * + * Play a file to speaker, run AEC, and record the microphone input + * to see if echo is coming. + * + * This file is pjsip-apps/src/samples/aectest.c + * + * \includelineno aectest.c + */ +#include <pjmedia.h> +#include <pjlib-util.h> /* pj_getopt */ +#include <pjlib.h> + +/* For logging purpose. */ +#define THIS_FILE "playfile.c" +#define PTIME 20 + +static const char *desc = +" FILE \n" +" \n" +" aectest.c \n" +" \n" +" PURPOSE \n" +" \n" +" Test the AEC effectiveness. \n" +" \n" +" USAGE \n" +" \n" +" aectest INPUT.WAV OUTPUT.WAV \n" +" \n" +" INPUT.WAV is the file to be played to the speaker. \n" +" OUTPUT.WAV is the output file containing recorded signal from the\n" +" microphone."; + + +static void app_perror(const char *sender, const char *title, pj_status_t st) +{ + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(st, errmsg, sizeof(errmsg)); + PJ_LOG(3,(sender, "%s: %s", title, errmsg)); +} + + +/* + * main() + */ +int main(int argc, char *argv[]) +{ + pj_caching_pool cp; + pjmedia_endpt *med_endpt; + pj_pool_t *pool; + pjmedia_port *play_port; + pjmedia_port *rec_port; + pjmedia_port *bidir_port; + pjmedia_snd_port *snd; + char tmp[10]; + pj_status_t status; + + + if (argc != 3) { + puts("Error: arguments required"); + puts(desc); + return 1; + } + + + /* Must init PJLIB first: */ + status = pj_init(); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + /* Must create a pool factory before we can allocate any memory. */ + pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); + + /* + * Initialize media endpoint. + * This will implicitly initialize PJMEDIA too. + */ + status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + /* Create memory pool for our file player */ + pool = pj_pool_create( &cp.factory, /* pool factory */ + "wav", /* pool name. */ + 4000, /* init size */ + 4000, /* increment size */ + NULL /* callback on error */ + ); + + /* Create file media port from the WAV file */ + status = pjmedia_wav_player_port_create( pool, /* memory pool */ + argv[1], /* file to play */ + PTIME, /* ptime. */ + 0, /* flags */ + 0, /* default buffer */ + &play_port); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to open input WAV file", status); + return 1; + } + + if (play_port->info.channel_count != 1) { + puts("Error: input WAV must have 1 channel audio"); + return 1; + } + if (play_port->info.bits_per_sample != 16) { + puts("Error: input WAV must be encoded as 16bit PCM"); + return 1; + } + +#ifdef PJ_DARWINOS + /* Need to force clock rate on MacOS */ + if (play_port->info.clock_rate != 44100) { + pjmedia_port *resample_port; + + status = pjmedia_resample_port_create(pool, play_port, 44100, 0, + &resample_port); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to create resampling port", status); + return 1; + } + + data.play_port = resample_port; + } +#endif + + /* Create WAV output file port */ + status = pjmedia_wav_writer_port_create(pool, argv[2], + play_port->info.clock_rate, + play_port->info.channel_count, + play_port->info.samples_per_frame, + play_port->info.bits_per_sample, + 0, 0, &rec_port); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to open output file", status); + return 1; + } + + /* Create bidirectional port from the WAV ports */ + pjmedia_bidirectional_port_create(pool, play_port, rec_port, &bidir_port); + + /* Create AEC port */ + if (0) { + pjmedia_aec_port_create(pool, bidir_port, + bidir_port->info.clock_rate * 200 / 1000, + &bidir_port); + } + + /* Create sound device. */ + status = pjmedia_snd_port_create(pool, -1, -1, + play_port->info.clock_rate, + play_port->info.channel_count, + play_port->info.samples_per_frame, + play_port->info.bits_per_sample, + 0, &snd); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to open sound device", status); + return 1; + } + + + /* Connect sound to the port */ + pjmedia_snd_port_connect(snd, bidir_port); + + + puts(""); + printf("Playing %s and recording to %s\n", argv[1], argv[2]); + puts("Press <ENTER> to quit"); + + fgets(tmp, sizeof(tmp), stdin); + + + /* Start deinitialization: */ + + /* Destroy sound device */ + status = pjmedia_snd_port_destroy( snd ); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + + /* Destroy file port(s) */ + status = pjmedia_port_destroy( play_port ); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + status = pjmedia_port_destroy( rec_port ); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + + /* Release application pool */ + pj_pool_release( pool ); + + /* Destroy media endpoint. */ + pjmedia_endpt_destroy( med_endpt ); + + /* Destroy pool factory */ + pj_caching_pool_destroy( &cp ); + + + /* Done. */ + return 0; +} + diff --git a/pjsip-apps/src/samples/debug.c b/pjsip-apps/src/samples/debug.c index b7cc8213..ea31c9a7 100644 --- a/pjsip-apps/src/samples/debug.c +++ b/pjsip-apps/src/samples/debug.c @@ -27,5 +27,5 @@ * E.g.: * #include "playfile.c" */ -#include "pjsip-perf.c" +#include "aectest.c" |