要望:
お客様から、製品の録音時にノイズが多いとの報告がありました(コーデックの録音ゲインを最大に調整しており、回路上に専用の音声処理チップがなく、CPUが直接接続されているため) MIC (グランドカバー付き))。シェルとハードウェアを変更できない場合、ソフトウェアは問題を解決する方法を考えなければなりません。
まず思い浮かぶのはデュアルマイクのノイズリダクションですが、原理は大まかに言うと、メインマイクの1つは通話用、もう1つは環境ノイズを集音して音声波形を解析して位相を操作し、音声に重畳するというものです。メインマイクの波形をサンプリングして位相キャンセルを形成し、ノイズを低減します。欠点は、2 つのマイクを近づけすぎてはいけないこと、および 2 つのマイクとスピーカーの距離が遠すぎてはいけないことですが、遠すぎると角度が非常に小さくなり、まったく区別できなくなることです。製品の使用上、上部と下部の別々のマイクがメインマイクと呼ばれます。そのため、実験結果はあまり良くありませんでした。
「人の声」がある場合、録音ノイズは区別できないか、影響が非常に小さいことを考慮すると、無音の場合は明らかな環境ノイズがあるため、無音のノイズリダクション方法を使用して問題を回避したいと考えています。
この記事は単純なミュートノイズリダクションです. 原理は次のとおりです: 録音を開始してから音声が聞こえるまでにしばらく時間がかかる (たとえば 0.5 秒) ことを考慮して、ノイズの大きさ (しきい値) を設定することができます。この0.5秒の時間をもとに音声を予測し、「人の声」の始点を検出するための基礎として利用します。人の声が到着する前に、すべての音声データが 0 に設定され、ミュート処理が行われるため、これをミュート ノイズ リダクションと呼びます。人間の声が到着すると、実際の音声データ(内部のノイズデータを含む)が返されます。しきい値の計算方法は単純に合計と平均です。
次のコードは、RK プラットフォームの hardware/alsa_sound/AudioStreamInALSA.cpp に実装されています。
#define MUTE_NOISE_REDUCTION
#ifdef MUTE_NOISE_REDUCTION
bool enable_reduction_noise = false; //由属性sys.is.audiorecord.only控制
int threshold_def = 0x400; //默认阈值
int threshold = 0; //自适应噪声阈值
int threshold_count = 0; //计数,超过THRESHOLD_COUNT则使用threshold来检测“人声”
#define THRESHOLD_COUNT 10
#define MUTE_DELAY_COUNT 15 //播放人声后保留的音频帧数、不静音
#define AUDIO_BUFFER_NUM 4 //缓存音频数据的帧数
#define AUDIO_BUFFER_SIZE 1024 //一帧的音频数据大小
char *audio_buffer[AUDIO_BUFFER_NUM]; //audio_buffer用于缓存音频数据
char *audio_buffer_temp; //用于交互音频数据
int audio_buffer_pos=0;
#endif
#ifdef MUTE_NOISE_REDUCTION
{
unsigned int value = 0;
int is_voice = 0;
static int is_mute_delay_count;
//ALOGE("in_begin_swip_num:%d in_begin_narrow_num=%d",in_begin_swip_num,in_begin_narrow_num);
if(enable_reduction_noise && bytes > AUDIO_BUFFER_SIZE){
bytes = AUDIO_BUFFER_SIZE;
}
if(enable_reduction_noise){
unsigned char * buffer_temp=(unsigned char *)buffer;
unsigned int total = 0;
unsigned int total_count=0;
unsigned int total_temp = 0;
short data16;
int j = 0;
for(j=0; j<bytes; j=j+2){
value = buffer_temp[j+1]; //第二个字节为高位数据
value = (value<<8)+buffer_temp[j]; //获得一个16bit的音频数据
data16 = value&0xFFFF;
if( (data16 & 0x8000) == 0){
//正数
total +=data16; //思考:会不会溢出
total_count++; //计数
}
}
total_temp = total/total_count;
if(total_temp > threshold_def){
is_voice++; //检测到人声
}else {
//is noise
if(threshold_count == 0){
threshold = total_temp;
}else{
threshold = (threshold+total_temp)/2;
}
threshold_count++;
if(threshold_count >= THRESHOLD_COUNT){
threshold_def = threshold*2; //更新阈值,这里的2要对产品实验来确定。
threshold_count = THRESHOLD_COUNT; //此后一直用新阈值,直到停止录音
}
}
//is_mute_delay_count的意义是,如果前面播放了人声,那再停止说话之后继续保留MUTE_DELAY_COUNT的音频数据,这样不会“戛然而止”。
if( is_voice != 0 ){
is_mute_delay_count=MUTE_DELAY_COUNT;
}else{
if(is_mute_delay_count != 0)
is_mute_delay_count--;
}
//audio_buffer的意义:检测到人声,要返回说话前的一小段音频数据,否则声音从静音到人声有个POP声的跳跃。
//这里用audio_buffer来缓存AUDIO_BUFFER_NUM帧数据。
if(is_mute_delay_count == 0){
//Mute in order to remove noise
memcpy(audio_buffer[audio_buffer_pos], (char *)buffer, bytes); //缓存音频
memset(buffer, 0, bytes); //返回静音数据
}else {
memcpy(audio_buffer_temp, (char *)buffer, bytes);
memcpy((char *)buffer, audio_buffer[audio_buffer_pos], bytes); //返回旧的音频数据
memcpy(audio_buffer[audio_buffer_pos], (char *)audio_buffer_temp, bytes); //保存新的音频数据
}
audio_buffer_pos++;
if(audio_buffer_pos>=AUDIO_BUFFER_NUM)
audio_buffer_pos=0;
}
}
#endif