echo_speex.c

来自「基于sip协议的网络电话源码」· C语言 代码 · 共 576 行

C
576
字号
/* $Id: echo_speex.c 974 2007-02-19 01:13:53Z bennylp $ *//*  * Copyright (C) 2003-2007 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/echo.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>#include <speex/speex_preprocess.h>#define THIS_FILE	"echo_speex.c"#define BUF_COUNT	16#define MIN_PREFETCH	2#define MAX_PREFETCH	(BUF_COUNT*2/3)#if 0# define TRACE_(expr)  PJ_LOG(5,expr)#else# define TRACE_(expr)#endiftypedef struct pjmedia_frame_queue pjmedia_frame_queue;struct fq_frame{    PJ_DECL_LIST_MEMBER(struct fq_frame);    void	*buf;    unsigned	 size;    pj_uint32_t	 seq;};struct pjmedia_frame_queue{    char	     obj_name[PJ_MAX_OBJ_NAME];    unsigned	     frame_size;    int		     samples_per_frame;    unsigned	     count;    unsigned	     max_count;    struct fq_frame  frame_list;    struct fq_frame  free_list;    int		     seq_delay;    int		     prefetch_count;};PJ_DEF(pj_status_t) pjmedia_frame_queue_create( pj_pool_t *pool,					        const char *name,					        unsigned frame_size,					        unsigned samples_per_frame,					        unsigned max_count,					        pjmedia_frame_queue **p_fq){    pjmedia_frame_queue *fq;    unsigned i;    fq = pj_pool_zalloc(pool, sizeof(pjmedia_frame_queue));    pj_ansi_snprintf(fq->obj_name, sizeof(fq->obj_name), name, fq);    fq->obj_name[sizeof(fq->obj_name)-1] = '\0';    fq->max_count = max_count;    fq->frame_size = frame_size;    fq->samples_per_frame = samples_per_frame;    fq->count = 0;    pj_list_init(&fq->frame_list);    pj_list_init(&fq->free_list);    for (i=0; i<max_count; ++i) {	struct fq_frame *f;	f = pj_pool_zalloc(pool, sizeof(struct fq_frame));	f->buf = pj_pool_alloc(pool, frame_size);	pj_list_push_back(&fq->free_list, f);	    }    *p_fq = fq;    return PJ_SUCCESS;}PJ_DEF(pj_status_t) pjmedia_frame_queue_init( pjmedia_frame_queue *fq,					      int seq_delay,					      int prefetch_count){    if (prefetch_count > MAX_PREFETCH)	prefetch_count = MAX_PREFETCH;    fq->seq_delay = seq_delay;    fq->prefetch_count = prefetch_count;    fq->count = 0;    pj_list_merge_first(&fq->free_list, &fq->frame_list);    PJ_LOG(5,(fq->obj_name, "AEC reset, delay=%d, prefetch=%d", 	      fq->seq_delay, fq->prefetch_count));    return PJ_SUCCESS;}PJ_DEF(pj_bool_t) pjmedia_frame_queue_empty( pjmedia_frame_queue *fq ){    return pj_list_empty(&fq->frame_list);}PJ_DEF(int) pjmedia_frame_queue_get_prefetch( pjmedia_frame_queue *fq ){    return fq->prefetch_count;}PJ_DEF(pj_status_t) pjmedia_frame_queue_put( pjmedia_frame_queue *fq,					     const void *framebuf,					     unsigned size,					     pj_uint32_t timestamp ){    struct fq_frame *f;    TRACE_((fq->obj_name, "PUT seq=%d, count=%d", 	    timestamp / fq->samples_per_frame, fq->count));    if (pj_list_empty(&fq->free_list)) {	PJ_LOG(5,(fq->obj_name, 		  " AEC info: queue is full, frame discarded "		  "[count=%d, seq=%d]",		  fq->max_count, timestamp / fq->samples_per_frame));	//pjmedia_frame_queue_init(fq, fq->seq_delay, fq->prefetch_count);	return PJ_ETOOMANY;    }    PJ_ASSERT_RETURN(size <= fq->frame_size, PJ_ETOOBIG);    f = fq->free_list.next;    pj_list_erase(f);    pj_memcpy(f->buf, framebuf, size);    f->size = size;    f->seq = timestamp / fq->samples_per_frame;    pj_list_push_back(&fq->frame_list, f);    ++fq->count;    return PJ_SUCCESS;}PJ_DEF(pj_status_t) pjmedia_frame_queue_get( pjmedia_frame_queue *fq,					     pj_uint32_t get_timestamp,					     void **framebuf,					     unsigned *size ){    pj_uint32_t frame_seq;    struct fq_frame *f;    frame_seq = get_timestamp/fq->samples_per_frame + fq->seq_delay -		fq->prefetch_count;    TRACE_((fq->obj_name, "GET seq=%d for seq=%d delay=%d, prefetch=%d", 	    get_timestamp/fq->samples_per_frame, frame_seq, fq->seq_delay, 	    fq->prefetch_count));    *size = 0;    /* Remove old frames */    for (;!pj_list_empty(&fq->frame_list);) {	f = fq->frame_list.next;	if (f->seq >= frame_seq)	    break;	PJ_LOG(5,(fq->obj_name, 		  " AEC Info: old frame removed (seq=%d, want=%d, count=%d)",		  f->seq, frame_seq, fq->count));	pj_list_erase(f);	--fq->count;	pj_list_push_back(&fq->free_list, f);    }    if (pj_list_empty(&fq->frame_list)) {	PJ_LOG(5,(fq->obj_name, 		  " AEC Info: empty queue for seq=%d!",		  frame_seq));	return PJ_ENOTFOUND;    }    f = fq->frame_list.next;    if (f->seq > frame_seq) {	PJ_LOG(5,(fq->obj_name, 		  " AEC Info: prefetching (first seq=%d)",		  f->seq));	return -1;    }    pj_list_erase(f);    --fq->count;    *framebuf = (void*)f->buf;    *size = f->size;    TRACE_((fq->obj_name, " returning frame with seq=%d, count=%d", 	    f->seq, fq->count));    pj_list_push_front(&fq->free_list, f);    return PJ_SUCCESS;}/* * Prototypes */PJ_DECL(pj_status_t) speex_aec_create(pj_pool_t *pool,				      unsigned clock_rate,				      unsigned samples_per_frame,				      unsigned tail_ms,				      unsigned latency_ms,				      unsigned options,				      void **p_state );PJ_DECL(pj_status_t) speex_aec_destroy(void *state );PJ_DECL(pj_status_t) speex_aec_playback(void *state,				        pj_int16_t *play_frm );PJ_DECL(pj_status_t) speex_aec_capture(void *state,				       pj_int16_t *rec_frm,				       unsigned options );PJ_DECL(pj_status_t) speex_aec_cancel_echo(void *state,					   pj_int16_t *rec_frm,					   const pj_int16_t *play_frm,					   unsigned options,					   void *reserved );enum{    TS_FLAG_PLAY = 1,    TS_FLAG_REC	 = 2,    TS_FLAG_OK	 = 3,};typedef struct speex_ec{    SpeexEchoState	 *state;    SpeexPreprocessState *preprocess;    unsigned		  samples_per_frame;    unsigned		  prefetch;    unsigned		  options;    pj_int16_t		 *tmp_frame;    spx_int32_t		 *residue;    pj_uint32_t		  play_ts,			  rec_ts,			  ts_flag;    pjmedia_frame_queue	 *frame_queue;    pj_lock_t		 *lock;		/* To protect buffers, if required  */} speex_ec;/* * Create the AEC.  */PJ_DEF(pj_status_t) speex_aec_create(pj_pool_t *pool,				     unsigned clock_rate,				     unsigned samples_per_frame,				     unsigned tail_ms,				     unsigned latency_ms,				     unsigned options,				     void **p_echo ){    speex_ec *echo;    int sampling_rate;    pj_status_t status;    *p_echo = NULL;    echo = pj_pool_zalloc(pool, sizeof(speex_ec));    PJ_ASSERT_RETURN(echo != NULL, PJ_ENOMEM);    if (options & PJMEDIA_ECHO_NO_LOCK) {	status = pj_lock_create_null_mutex(pool, "aec%p", &echo->lock);	if (status != PJ_SUCCESS)	    return status;    } else {	status = pj_lock_create_simple_mutex(pool, "aec%p", &echo->lock);	if (status != PJ_SUCCESS)	    return status;    }    echo->samples_per_frame = samples_per_frame;    echo->prefetch = (latency_ms * clock_rate / 1000) / samples_per_frame;    if (echo->prefetch < MIN_PREFETCH)	echo->prefetch = MIN_PREFETCH;    if (echo->prefetch > MAX_PREFETCH)	echo->prefetch = MAX_PREFETCH;    echo->options = options;    echo->state = speex_echo_state_init(samples_per_frame,					clock_rate * tail_ms / 1000);    if (echo->state == NULL) {	pj_lock_destroy(echo->lock);	return PJ_ENOMEM;    }    /* Set sampling rate */    sampling_rate = clock_rate;    speex_echo_ctl(echo->state, SPEEX_ECHO_SET_SAMPLING_RATE, 		   &sampling_rate);    echo->preprocess = speex_preprocess_state_init(samples_per_frame, 						   clock_rate);    if (echo->preprocess == NULL) {	speex_echo_state_destroy(echo->state);	pj_lock_destroy(echo->lock);	return PJ_ENOMEM;    }    /* Disable all preprocessing, we only want echo cancellation */#if 0    disabled = 0;    enabled = 1;    speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_DENOISE, 			 &enabled);    speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_AGC, 			 &disabled);    speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_VAD, 			 &disabled);    speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_DEREVERB, 			 &disabled);#endif    /* Control echo cancellation in the preprocessor */   speex_preprocess_ctl(echo->preprocess, SPEEX_PREPROCESS_SET_ECHO_STATE, 			echo->state);    /* Create temporary frame for echo cancellation */    echo->tmp_frame = pj_pool_zalloc(pool, 2 * samples_per_frame);    PJ_ASSERT_RETURN(echo->tmp_frame != NULL, PJ_ENOMEM);    /* Create temporary frame to receive residue */    echo->residue = pj_pool_zalloc(pool, sizeof(spx_int32_t) * 					    (samples_per_frame+1));    PJ_ASSERT_RETURN(echo->residue != NULL, PJ_ENOMEM);    /* Create frame queue */    status = pjmedia_frame_queue_create(pool, "aec%p", samples_per_frame*2,					samples_per_frame, BUF_COUNT, 					&echo->frame_queue);    if (status != PJ_SUCCESS) {	speex_preprocess_state_destroy(echo->preprocess);	speex_echo_state_destroy(echo->state);	pj_lock_destroy(echo->lock);	return status;    }    /* Done */    *p_echo = echo;    PJ_LOG(4,(THIS_FILE, "Speex Echo canceller/AEC created, clock_rate=%d, "			 "samples per frame=%d, tail length=%d ms, "			 "latency=%d ms", 			 clock_rate, samples_per_frame, tail_ms, latency_ms));    return PJ_SUCCESS;}/* * Destroy AEC */PJ_DEF(pj_status_t) speex_aec_destroy(void *state ){    speex_ec *echo = state;    PJ_ASSERT_RETURN(echo && echo->state, PJ_EINVAL);    if (echo->lock)	pj_lock_acquire(echo->lock);    if (echo->state) {	speex_echo_state_destroy(echo->state);	echo->state = NULL;    }    if (echo->preprocess) {	speex_preprocess_state_destroy(echo->preprocess);	echo->preprocess = NULL;    }    if (echo->lock) {	pj_lock_destroy(echo->lock);	echo->lock = NULL;    }    return PJ_SUCCESS;}/* * Let the AEC knows that a frame has been played to the speaker. */PJ_DEF(pj_status_t) speex_aec_playback(void *state,				       pj_int16_t *play_frm ){    speex_ec *echo = state;    /* Sanity checks */    PJ_ASSERT_RETURN(echo && play_frm, PJ_EINVAL);    /* The AEC must be configured to support internal playback buffer */    PJ_ASSERT_RETURN(echo->frame_queue!= NULL, PJ_EINVALIDOP);    pj_lock_acquire(echo->lock);    /* Inc timestamp */    echo->play_ts += echo->samples_per_frame;    /* Initialize frame delay. */    if ((echo->ts_flag & TS_FLAG_PLAY) == 0) {	echo->ts_flag |= TS_FLAG_PLAY;	if (echo->ts_flag == TS_FLAG_OK) {	    int seq_delay;	    seq_delay = ((int)echo->play_ts - (int)echo->rec_ts) / 			    (int)echo->samples_per_frame;	    pjmedia_frame_queue_init(echo->frame_queue, seq_delay, 				     echo->prefetch);	}    }    if (pjmedia_frame_queue_put(echo->frame_queue, play_frm, 				echo->samples_per_frame*2, 				echo->play_ts) != PJ_SUCCESS)    {	int seq_delay;	/* On full reset frame queue */	seq_delay = ((int)echo->play_ts - (int)echo->rec_ts) / 			(int)echo->samples_per_frame;	pjmedia_frame_queue_init(echo->frame_queue, seq_delay,				 echo->prefetch);	/* And re-put */	pjmedia_frame_queue_put(echo->frame_queue, play_frm, 				echo->samples_per_frame*2, 				echo->play_ts);    }    pj_lock_release(echo->lock);    return PJ_SUCCESS;}/* * Let the AEC knows that a frame has been captured from the microphone. */PJ_DEF(pj_status_t) speex_aec_capture( void *state,				       pj_int16_t *rec_frm,				       unsigned options ){    speex_ec *echo = state;    pj_status_t status = PJ_SUCCESS;    /* Sanity checks */    PJ_ASSERT_RETURN(echo && rec_frm, PJ_EINVAL);    /* The AEC must be configured to support internal playback buffer */    PJ_ASSERT_RETURN(echo->frame_queue!= NULL, PJ_EINVALIDOP);    /* Lock mutex */    pj_lock_acquire(echo->lock);    /* Inc timestamp */    echo->rec_ts += echo->samples_per_frame;    /* Init frame delay. */    if ((echo->ts_flag & TS_FLAG_REC) == 0) {	echo->ts_flag |= TS_FLAG_REC;	if (echo->ts_flag == TS_FLAG_OK) {	    int seq_delay;	    seq_delay = ((int)echo->play_ts - (int)echo->rec_ts) / 			    (int)echo->samples_per_frame;	    pjmedia_frame_queue_init(echo->frame_queue, seq_delay, 				     echo->prefetch);	}    }    /* Cancel echo */    if (echo->ts_flag == TS_FLAG_OK) {	void *play_buf;	unsigned size = 0;		if (pjmedia_frame_queue_empty(echo->frame_queue)) {	    int seq_delay;	    seq_delay = ((int)echo->play_ts - (int)echo->rec_ts) / 			    (int)echo->samples_per_frame;	    pjmedia_frame_queue_init(echo->frame_queue, seq_delay, 				     echo->prefetch);	    status = -1;	} else {	    status = pjmedia_frame_queue_get(echo->frame_queue, echo->rec_ts,					     &play_buf, &size);	    if (size != 0) {		speex_aec_cancel_echo(echo, rec_frm, (pj_int16_t*)play_buf,				      options, NULL);	    }		}	if (status != PJ_SUCCESS)	    speex_echo_state_reset(echo->state);    }    pj_lock_release(echo->lock);    return PJ_SUCCESS;}/* * Perform echo cancellation. */PJ_DEF(pj_status_t) speex_aec_cancel_echo( void *state,					   pj_int16_t *rec_frm,					   const pj_int16_t *play_frm,					   unsigned options,					   void *reserved ){    speex_ec *echo = state;    /* Sanity checks */    PJ_ASSERT_RETURN(echo && rec_frm && play_frm && options==0 &&		     reserved==NULL, PJ_EINVAL);    /* Cancel echo, put output in temporary buffer */    speex_echo_cancellation(echo->state, (const spx_int16_t*)rec_frm,			    (const spx_int16_t*)play_frm,			    (spx_int16_t*)echo->tmp_frame);    /* Preprocess output */    speex_preprocess_run(echo->preprocess, (spx_int16_t*)echo->tmp_frame);    /* Copy temporary buffer back to original rec_frm */    pjmedia_copy_samples(rec_frm, echo->tmp_frame, echo->samples_per_frame);    return PJ_SUCCESS;}

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?