에코 제거라는 지연 시간을 추정하는 것은 합리적이지 않습니다. 여기서 핵심은 webrtc를 호출하는 조건 경계를 추정하는 것입니다. webrtc 에코 제거가 적용되기 위한 전제 조건은 맨 끝 소리의 정보를 얻는 것입니다. , 그런 다음 근단 소리와 원단 소리를 얻습니다.근단 소리에서 원단 소리와 결합하여 근단 소리에서 원단 소리의 에코 정보를 시뮬레이션하고 그런 다음 에코 정보를 죽입니다.따라서 정렬에 저장된 맨 끝 사운드에 해당하는 사운드가 없으면 webrtc 에코 제거 효과가 좋지 않거나 효과가 없으므로 이 시간 차이가 매우 중요하며 종합적으로 필요합니다. 하드웨어 상황과 함께 delay_time을 측정합니다.
webrtc의 반향 제거(aec, aecm) 알고리즘은 주로 다음과 같은 중요한 모듈을 포함합니다.
- 에코 지연 추정
- NLMS(정규화된 최소 평균 제곱 적응 알고리즘)
- NLP(비선형 필터링)
- CNG(컴포트 노이즈 생성)
에코 지연 추정
이 그림에는 무시할 수 있는 것들이 많이 있습니다. T0, T1, T2에 집중합시다.
- T0는 소리가 스피커에서 마이크까지 전달되는 시간을 의미하며, 일반적으로 마이크와 스피커 사이의 거리가 그리 멀지 않기 때문에 이 시간은 무시할 수 있습니다. 1밀리초를 초과하지 않습니다.
- T1은 멀리서 들리는 소리를 나타내며 이 소리가 반향 제거 원격 인터페이스(WebRtcAec_BufferFarend)로 전달되는 시간부터 재생될 때까지의 시간입니다. 일반적으로 수신된 오디오 데이터가 이 인터페이스로 전송되면 상위 계층에서 스피커로 전송되는 순간이므로 사운드가 재생 대기열에서 타이밍을 시작하여 재생되는 시간으로 이해될 수 있습니다.
- T2는 화자에서 한 조각의 소리를 모아 근단 처리 기능(WebRtcAec_Process)으로 보내는 순간을 의미하는데, 소리가 모아졌기 때문에 바로 반향 제거 과정이 진행되기 때문에 이 시간을 이해할 수 있다. 마이크에 의해 사운드가 수집되는 타이밍의 시작으로 코드가 오디오 PCM 데이터를 가져오는 데 걸리는 시간입니다.
- 지연=T0+T1+T2, 실제로는 T1+T2입니다.
일반적으로 장치가 적절한 지연을 찾을 수 있는 경우 이 장치의 반향 제거 처리는 노이즈 감소 이득만큼 어렵습니다. 예를 들어 iPhone의 고정 지연은 60ms입니다. 그러나 이것은 코드의 위치에 따라 다르며, 칩 내부에 있는 경우 상대적으로 시간이 작고 수정이 용이하며, 시스템 응용 계층 소프트웨어에 있는 경우 전체 시간이 불확실하다. 상대적으로 큽니다. apm_->set_stream_delay_ms(delay_ms_);
예를 들어, android 시스템에서 webrtc의 패키지 호출은 다음과 같습니다.
#include "webrtc_apm.h"
//#include "webrtc/common_types.h"
#include "webrtc/modules/audio_processing/include/audio_processing.h"
#include "webrtc/modules/include/module_common_types.h"
#include "webrtc/api/audio/audio_frame.h"
#include "YuvConvert.h"
using namespace webrtc;
//using namespace cbase;
WebrtcAPM::WebrtcAPM(int process_smp, int reverse_smp)
: apm_(nullptr)
, far_frame_(new AudioFrame)
, near_frame_(new AudioFrame)
, ref_cnt_(0)
, sample_rate_(8000)
, samples_per_channel_(8000/100)
, channels_(1)
, frame_size_10ms_(8000/100*sizeof(int16_t))
, delay_ms_(60)
, process_sample_rate_(process_smp)//44100
, reverse_sample_rate_(reverse_smp)//48000
{
audio_send_ = new unsigned char[kMaxDataSizeSamples_];
audio_reverse_ = new unsigned char[kMaxDataSizeSamples_];
#if defined(__APPLE__)
delay_ms_ = 60;
#endif
#if defined(VAD_TEST)
//create webrtc vad
ty_vad_create(8000, 1);
ty_set_vad_level(vad_level::LOW);
ty_vad_set_recordfile("/sdcard/vadfile.pcm");
#endif
}
WebrtcAPM::~WebrtcAPM()
{
if(far_frame_)
{
delete far_frame_;
far_frame_ = NULL ;
}
if (near_frame_) {
delete near_frame_ ;
near_frame_ = NULL ;
}
if (audio_send_) {
delete[] audio_send_;
}
if (audio_reverse_) {
delete[] audio_reverse_;
}
#if defined(VAD_TEST)
//destory webrtc vad
ty_vad_destory();
#endif
}
void WebrtcAPM::set_sample_rate(int sample_rate)
{
sample_rate_ = sample_rate;
samples_per_channel_ = sample_rate_ / 100;
}
int WebrtcAPM::frame_size()
{
return frame_size_10ms_;
}
void WebrtcAPM::set_ace_delay(int delay)
{
LOGI("set aec delay to %d ms \n", delay);
delay_ms_ = delay;
}
void WebrtcAPM::set_reverse_stream(int reverse_sample_rate)
{
std::lock_guard<std::mutex> guard(mutex_);
reverse_sample_rate_ = reverse_sample_rate;
if (resampleReverse) {
delete resampleReverse;
resampleReverse = NULL ;
}
resampleReverse = new webrtc::Resampler(reverse_sample_rate_,sample_rate_,channels_);
int result = resampleReverse->Reset(reverse_sample_rate_,sample_rate_,channels_);
if (result != 0) {
LOGE("reset resampleReverse fail,%d!\n", result);
}
}
int WebrtcAPM::init()
{
std::lock_guard<std::mutex> guard(mutex_);
resampleReverse = new webrtc::Resampler(reverse_sample_rate_,sample_rate_,channels_);
int result = resampleReverse->Reset(reverse_sample_rate_,sample_rate_,channels_);
if (result != 0) {
LOGE("reset resampleReverse fail,%d!\n", result);
}
resampleIn = new webrtc::Resampler(sample_rate_,process_sample_rate_,channels_);
result = resampleIn->Reset(sample_rate_,process_sample_rate_,channels_);
if (result != 0) {
LOGE("reset resampleIn fail,%d!\n", result);
}
initAPM();
LOGI("initAPM success!");
return 0;//crash without this
}
void WebrtcAPM::uninit()
{
std::lock_guard<std::mutex> guard(mutex_);
deInitAPM();
safe_delete(resampleIn);
safe_delete(resampleReverse);
}
void WebrtcAPM::reset_apm(){
std::lock_guard<std::mutex> guard(mutex_);
deInitAPM();
initAPM();
}
int WebrtcAPM::initAPM(){
apm_ = AudioProcessingBuilder().Create();
if (apm_ == nullptr) {
LOGE("AudioProcessing create failed");
return -1;
}
AudioProcessing::Config config;
config.echo_canceller.enabled = true;
config.echo_canceller.mobile_mode = true;
config.gain_controller1.enabled = true;
config.gain_controller1.mode = AudioProcessing::Config::GainController1::kAdaptiveDigital;
config.gain_controller1.analog_level_minimum = 0;
config.gain_controller1.analog_level_maximum = 255;
config.noise_suppression.enabled = true;
config.noise_suppression.level = AudioProcessing::Config::NoiseSuppression::Level::kModerate;
config.gain_controller2.enabled = true;
config.high_pass_filter.enabled = true;
config.voice_detection.enabled = true;
apm_->ApplyConfig(config);
LOGI("AudioProcessing initialize success \n");
far_frame_->sample_rate_hz_ = sample_rate_;
far_frame_->samples_per_channel_ = samples_per_channel_;
far_frame_->num_channels_ = channels_;
near_frame_->sample_rate_hz_ = sample_rate_;
near_frame_->samples_per_channel_ = samples_per_channel_;
near_frame_->num_channels_ = channels_;
frame_size_10ms_ = samples_per_channel_ * channels_ * sizeof(int16_t);
//LOGI("AudioProcessing initialize success end\n");
return 0;
}
void WebrtcAPM::deInitAPM(){
//std::lock_guard<std::mutex> guard(mutex_);
//if (--ref_cnt_ == 0) {
LOGI("destroy WebrtcAPM \n");
safe_delete(apm_);
//}
}
//8000->44100
void WebrtcAPM::process_stream(uint8_t *buffer,int bufferLength, uint8_t *bufferOut, int* pOutLen, bool bUseAEC)
{
if (bUseAEC) {
std::lock_guard<std::mutex> guard(mutex_);
if (apm_) {
int frame_count = bufferLength / frame_size_10ms_;
for (int i = 0; i < frame_count; i++) {
apm_->set_stream_delay_ms(delay_ms_);
// webrtc apm process 10ms datas every time
memcpy((void*)near_frame_->data(), buffer + i*frame_size_10ms_, frame_size_10ms_);
int res = apm_->ProcessStream(near_frame_->data(),
StreamConfig(near_frame_->sample_rate_hz_, near_frame_->num_channels_),
StreamConfig(near_frame_->sample_rate_hz_, near_frame_->num_channels_),
(int16_t * const)near_frame_->data());
if (res != 0) {
LOGE("ProcessStream failed, ret %d \n",res);
}
#if defined(VAD_TEST)
bool ret = ty_vad_process(buffer + i*frame_size_10ms_, frame_size_10ms_);
#endif
memcpy(buffer + i*frame_size_10ms_, near_frame_->data(), frame_size_10ms_);
}
}
}
if (resampleIn && apm_) {
//resample
size_t outlen = 0 ;
int result = resampleIn->Push((int16_t*)buffer, bufferLength/sizeof(int16_t), (int16_t*)bufferOut, kMaxDataSizeSamples_/sizeof(int16_t), outlen);
if (result != 0) {
LOGE("resampleIn error, result = %d, outlen = %d\n", result, outlen);
}
*pOutLen = outlen;
}
}
//48000->8000
void WebrtcAPM::process_reverse_10ms_stream(uint8_t *bufferIn, int bufferLength, uint8_t *bufferOut, int* pOutLen, bool bUseAEC)
{
size_t outlen = 0 ;
if (resampleReverse && apm_) {
//resample
int result = resampleReverse->Push((int16_t*)bufferIn, bufferLength/sizeof(int16_t), (int16_t*)audio_reverse_, kMaxDataSizeSamples_/sizeof(int16_t), outlen);
if (result != 0) {
LOGE("resampleReverse error, result = %d, outlen = %d\n", result, outlen);
}
}
else {
memcpy(audio_reverse_, bufferIn, bufferLength);
outlen = bufferLength;
}
*pOutLen = outlen;
if (!bUseAEC){
//copy data and return
memcpy(bufferOut, audio_reverse_, frame_size_10ms_);
return;
}
std::lock_guard<std::mutex> guard(mutex_);
if (apm_) {
memcpy((void*)far_frame_->data(), audio_reverse_, frame_size_10ms_);
int res = apm_->ProcessReverseStream(far_frame_->data(),
StreamConfig(far_frame_->sample_rate_hz_, far_frame_->num_channels_),
StreamConfig(far_frame_->sample_rate_hz_, far_frame_->num_channels_),
(int16_t * const)far_frame_->data());
if (res != 0) {
LOGE("ProcessReverseStream failed, ret %d \n",res);
}
memcpy(bufferOut, audio_reverse_, frame_size_10ms_);//far_frame_->data()
}
}