linphone-ios/linphone/mediastreamer2/src/msvolume.c
smorlat 9b99da8840 fix bug in MSVolume
git-svn-id: svn+ssh://svn.savannah.nongnu.org/linphone/trunk@513 3f6dc0c8-ddfe-455d-9043-3cd528dc4637
2009-07-02 13:14:14 +00:00

346 lines
8.1 KiB
C

/*
mediastreamer2 library - modular sound and video processing and streaming
Copyright (C) 2006 Simon MORLAT (simon.morlat@linphone.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.
*/
#ifdef HAVE_CONFIG_H
#include "mediastreamer-config.h"
#endif
#include "mediastreamer2/msvolume.h"
#include <math.h>
#ifdef HAVE_SPEEXDSP
#include <speex/speex_preprocess.h>
#endif
static const float max_e=32767*32767;
static const float coef=0.1;
static const float gain_k=0.02;
static const float en_weight=4.0;
static const float noise_thres=0.1;
typedef struct Volume{
float energy;
float norm_en;
float gain;
float static_gain;
float gain_k;
float thres;
float force;
MSFilter *peer;
#ifdef HAVE_SPEEXDSP
SpeexPreprocessState *speex_pp;
#endif
int sample_rate;
int nsamples;
MSBufferizer *buffer;
bool_t ea_active;
bool_t agc_enabled;
}Volume;
static void volume_init(MSFilter *f){
Volume *v=(Volume*)ms_new(Volume,1);
v->energy=0;
v->norm_en=0;
v->static_gain=v->gain=1;
v->ea_active=FALSE;
v->gain_k=gain_k;
v->thres=noise_thres;
v->force=en_weight;
v->peer=NULL;
v->agc_enabled=FALSE;
v->buffer=ms_bufferizer_new();
v->sample_rate=8000;
v->nsamples=80;
#ifdef HAVE_SPEEXDSP
v->speex_pp=NULL;
#endif
f->data=v;
}
static void volume_uninit(MSFilter *f){
Volume *v=(Volume*)f->data;
ms_bufferizer_destroy(v->buffer);
ms_free(f->data);
}
static int volume_get(MSFilter *f, void *arg){
float *farg=(float*)arg;
Volume *v=(Volume*)f->data;
*farg=10*log10f((v->energy+1)/max_e);
return 0;
}
static int volume_set_sample_rate(MSFilter *f, void *arg){
Volume *v=(Volume*)f->data;
v->sample_rate=*(int*)arg;
return 0;
}
static int volume_get_linear(MSFilter *f, void *arg){
float *farg=(float*)arg;
Volume *v=(Volume*)f->data;
*farg=(v->energy+1)/max_e;
return 0;
}
#ifdef HAVE_SPEEXDSP
static void volume_agc_process(Volume *v, mblk_t *om){
speex_preprocess_run(v->speex_pp,(int16_t*)om->b_rptr);
}
#else
static void volume_agc_process(Volume *v, mblk_t *om){
}
#endif
static inline float compute_gain(float static_gain, float energy, float weight){
float ret=static_gain*(1 - (energy*weight));
if (ret<0) ret=0;
return ret;
}
static void volume_echo_avoider_process(Volume *v){
float peer_e;
float gain;
ms_filter_call_method(v->peer,MS_VOLUME_GET_LINEAR,&peer_e);
peer_e=sqrt(peer_e);
if (v->ea_active){
if (peer_e>v->thres){
/*lower our output*/
gain=compute_gain(v->static_gain,peer_e,v->force);
}else {
gain=v->static_gain;
v->ea_active=FALSE;
}
v->gain=(v->gain*(1-v->gain_k)) + (v->gain_k*gain);
}else{
int peer_active=FALSE;
ms_filter_call_method(v->peer,MS_VOLUME_GET_EA_STATE,&peer_active);
if (peer_e>v->thres && ! peer_active){
/*lower our output*/
gain=compute_gain(v->static_gain,peer_e,v->force);
v->ea_active=TRUE;
v->gain=gain;
}else {
gain=v->static_gain;
v->gain=(v->gain*(1-v->gain_k)) + (v->gain_k*gain);
}
}
ms_message("ea_active=%i, peer_e=%f gain=%f gain_k=%f force=%f",v->ea_active,peer_e,v->gain, v->gain_k,v->force);
}
static int volume_set_gain(MSFilter *f, void *arg){
float *farg=(float*)arg;
Volume *v=(Volume*)f->data;
v->gain=v->static_gain=*farg;
return 0;
}
static int volume_get_ea_state(MSFilter *f, void *arg){
int *barg=(int*)arg;
Volume *v=(Volume*)f->data;
*barg=v->ea_active;
return 0;
}
static int volume_set_peer(MSFilter *f, void *arg){
MSFilter *p=(MSFilter*)arg;
Volume *v=(Volume*)f->data;
v->peer=p;
return 0;
}
static int volume_set_agc(MSFilter *f, void *arg){
Volume *v=(Volume*)f->data;
v->agc_enabled=*(int*)arg;
return 0;
}
static int volume_set_ea_threshold(MSFilter *f, void*arg){
Volume *v=(Volume*)f->data;
float val=*(float*)arg;
if (val<0 || val>1) {
ms_error("Error: threshold must be in range [0..1]");
return -1;
}
v->thres=val;
return 0;
}
static int volume_set_ea_speed(MSFilter *f, void*arg){
Volume *v=(Volume*)f->data;
float val=*(float*)arg;
if (val<0 || val>1) {
ms_error("Error: speed must be in range [0..1]");
return -1;
}
v->gain_k=val;
return 0;
}
static int volume_set_ea_force(MSFilter *f, void*arg){
Volume *v=(Volume*)f->data;
float val=*(float*)arg;
v->force=val;
return 0;
}
static inline int16_t saturate(float val){
return (val>32767) ? 32767 : ( (val<-32767) ? -32767 : val);
}
static float update_energy(int16_t *signal, int numsamples, float last_energy_value){
int i;
float en=last_energy_value;
for (i=0;i<numsamples;++i){
float s=(float)signal[i];
en=(s*s*coef) + (1.0-coef)*en;
}
return en;
}
static void apply_gain(mblk_t *m, float gain){
int16_t *sample;
for ( sample=(int16_t*)m->b_rptr;
sample<(int16_t*)m->b_wptr;
++sample){
float s=*sample;
*sample=saturate(s*gain);
}
}
static void volume_preprocess(MSFilter *f){
Volume *v=(Volume*)f->data;
/*process agc by chunks of 10 ms*/
v->nsamples=(int)(0.01*(float)v->sample_rate);
if (v->agc_enabled){
ms_message("AGC is enabled.");
#ifdef HAVE_SPEEXDSP
if (v->speex_pp==NULL){
int tmp=1;
v->speex_pp=speex_preprocess_state_init(v->nsamples,v->sample_rate);
if (speex_preprocess_ctl(v->speex_pp,SPEEX_PREPROCESS_SET_AGC,&tmp)==-1){
ms_warning("Speex AGC is not available.");
}
tmp=0;
speex_preprocess_ctl(v->speex_pp,SPEEX_PREPROCESS_SET_VAD,&tmp);
speex_preprocess_ctl(v->speex_pp,SPEEX_PREPROCESS_SET_DENOISE,&tmp);
speex_preprocess_ctl(v->speex_pp,SPEEX_PREPROCESS_SET_DEREVERB,&tmp);
}
#else
ms_error("No AGC possible, mediastreamer2 was compiled without libspeexdsp.");
#endif
}
}
static void volume_process(MSFilter *f){
mblk_t *m;
Volume *v=(Volume*)f->data;
float en=v->energy;
if (v->agc_enabled){
mblk_t *om;
int nbytes=v->nsamples*2;
ms_bufferizer_put_from_queue(v->buffer,f->inputs[0]);
while(ms_bufferizer_get_avail(v->buffer)>=nbytes){
om=allocb(nbytes,0);
ms_bufferizer_read(v->buffer,om->b_wptr,nbytes);
om->b_wptr+=nbytes;
en=update_energy((int16_t*)om->b_rptr,v->nsamples,en);
volume_agc_process(v,om);
if (v->peer){
volume_echo_avoider_process(v);
}
if (v->gain!=1){
apply_gain(om,v->gain);
}
ms_queue_put(f->outputs[0],om);
}
}else{
/*light processing: no agc. Work in place in the input buffer*/
while((m=ms_queue_get(f->inputs[0]))!=NULL){
en=update_energy((int16_t*)m->b_rptr,(m->b_wptr-m->b_rptr)/2,en);
if (v->peer){
volume_echo_avoider_process(v);
}
if (v->gain!=1){
apply_gain(m,v->gain);
}
ms_queue_put(f->outputs[0],m);
}
}
v->energy=en;
}
static MSFilterMethod methods[]={
{ MS_VOLUME_GET , volume_get },
{ MS_VOLUME_GET_LINEAR , volume_get_linear },
{ MS_VOLUME_SET_GAIN , volume_set_gain },
{ MS_VOLUME_GET_EA_STATE , volume_get_ea_state },
{ MS_VOLUME_SET_PEER , volume_set_peer },
{ MS_VOLUME_SET_EA_THRESHOLD , volume_set_ea_threshold },
{ MS_VOLUME_SET_EA_SPEED , volume_set_ea_speed },
{ MS_VOLUME_SET_EA_FORCE , volume_set_ea_force },
{ MS_FILTER_SET_SAMPLE_RATE, volume_set_sample_rate },
{ MS_VOLUME_ENABLE_AGC , volume_set_agc },
{ 0 , NULL }
};
#ifndef _MSC_VER
MSFilterDesc ms_volume_desc={
.name="MSVolume",
.text=N_("A filter that controls and measure sound volume"),
.id=MS_VOLUME_ID,
.category=MS_FILTER_OTHER,
.ninputs=1,
.noutputs=1,
.init=volume_init,
.uninit=volume_uninit,
.preprocess=volume_preprocess,
.process=volume_process,
.methods=methods
};
#else
MSFilterDesc ms_volume_desc={
MS_VOLUME_ID,
"MSVolume",
N_("A filter that controls and measure sound volume"),
MS_FILTER_OTHER,
NULL,
1,
1,
volume_init,
NULL,
volume_process,
NULL,
volume_uninit,
methods
};
#endif
MS_FILTER_DESC_EXPORT(ms_volume_desc)