声效 (Sound Effects) 是一种用来增强声音的技术。声效可以通过各种方式实现,包括声音处理器和数字信号处理等。下面是三种常见的声效处理技术的简要介绍:
自适应回声消除 (Acoustic Echo Cancellation, AEC):AEC 技术主要用于语音通信系统中,可以自动地消除回声。当在通话中使用扬声器时,麦克风可能会捕捉到从扬声器返回的声音,导致回声。AEC 技术能够分析回声信号,并将其从录音中消除,确保清晰的语音通信。
自动增益控制 (Automatic Gain Control, AGC):AGC 技术可以自动调整声音信号的增益,以便在不同的录音环境中保持一致的音量水平。对于声音信号的动态范围较大的情况,AGC 可以防止信号过于强大而导致失真,或者过于微弱而无法听清。
噪音抑制 (Noise Suppression, NS):NS 技术用于减少背景噪音对于声音信号的干扰。在录音中,周围环境的噪音可能会混入到所需的声音中,影响清晰度。NS 技术利用信号处理算法,识别和降低噪音成分,使得声音更加清晰和可听。
这些声效技术通常用于音频通信、音频处理和音频增强等应用。它们可以提供更好的音质和体验,减少噪音干扰,并优化音频的各个方面。
package com.realtop.audioecho.other;
import android.annotation.SuppressLint;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.media.audiofx.AcousticEchoCanceler;
import android.media.audiofx.AudioEffect;
import android.media.audiofx.AutomaticGainControl;
import android.media.audiofx.NoiseSuppressor;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatButton;
import androidx.appcompat.widget.AppCompatCheckBox;
import androidx.appcompat.widget.AppCompatTextView;
public class MainActivity extends AppCompatActivity {
private static final String LOG_TAG = "other_test_activity";
private static final String TAG = LOG_TAG;
private AudioRecord mAudioRecorder = null;
private AudioTrack mStreamingPlayer = null;
int bufferSize;
int chunkSize;
private AutomaticGainControl agc;
private NoiseSuppressor suppressor;
private AcousticEchoCanceler aec;
private boolean manuallyAttachEffects = false;
private boolean isRunning = false;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout linearLayout = new LinearLayout(this);
initView(linearLayout);
setContentView(linearLayout);
}
@SuppressLint("SetTextI18n")
private void initView(LinearLayout linearLayout) {
linearLayout.setOrientation(LinearLayout.VERTICAL);
AppCompatButton button = new AppCompatButton(this);
AppCompatButton button2 = new AppCompatButton(this);
AppCompatTextView appCompatTextView = new AppCompatTextView(this);
AppCompatCheckBox checkBox = new AppCompatCheckBox(this);
linearLayout.addView(button);
linearLayout.addView(button2);
linearLayout.addView(checkBox);
linearLayout.addView(appCompatTextView);
AudioEffect.Descriptor[] descriptors = AudioEffect.queryEffects();
StringBuilder outs = new StringBuilder();
for (AudioEffect.Descriptor descriptor : descriptors) {
outs.append("; name:").append(descriptor.name).append("type:")
.append(descriptor.type).append("; mode:")
.append(descriptor.connectMode)
.append("; uuid:").append(descriptor.uuid).append("\n");
}
Log.i(TAG, "onCreate: aec:" + outs + "\n" + AudioEffect.EFFECT_TYPE_AEC.toString());
String msg = "回声降噪:" + AcousticEchoCanceler.isAvailable()
+ "; 增益消除:" + AutomaticGainControl.isAvailable()
+ "; 降噪:" + NoiseSuppressor.isAvailable();
appCompatTextView.setText(msg + "\n" + outs);
button.setText("点击录音");
button2.setText("关闭录音");
checkBox.setText("是否手动启动echo api");
button.setOnClickListener(v -> startRecordingAudio());
button2.setOnClickListener(v -> isRunning = false);
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> manuallyAttachEffects = isChecked);
}
@SuppressWarnings({"ConstantConditions", "deprecation"})
@SuppressLint("MissingPermission")
private void startRecordingAudio() {
int sampleRate = 44100;
int channelConfig = AudioFormat.CHANNEL_IN_MONO;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
chunkSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
bufferSize = chunkSize * 10;
int audioSource = MediaRecorder.AudioSource.MIC;
boolean useVoip = true;
if (useVoip && !manuallyAttachEffects) {
audioSource = MediaRecorder.AudioSource.VOICE_COMMUNICATION;
}
mAudioRecorder = new AudioRecord(audioSource,
sampleRate,
channelConfig,
audioFormat,
bufferSize);
if (useVoip && manuallyAttachEffects) {
msg("手动开启回声消除效果");
setupVoipEffects(mAudioRecorder.getAudioSessionId());
}
mAudioRecorder.startRecording();
mStreamingPlayer = new AudioTrack(AudioManager.STREAM_MUSIC,
sampleRate,
AudioFormat.CHANNEL_OUT_MONO,
audioFormat,
bufferSize,
AudioTrack.MODE_STREAM,
mAudioRecorder.getAudioSessionId());
mStreamingPlayer.play();
Thread playbackThread = new Thread(this::readAudioData);
isRunning = true;
playbackThread.start();
Log.i(TAG, "startRecordingAudio: AudioEffect-JAVA: create aec start");
AcousticEchoCanceler.create(mAudioRecorder.getAudioSessionId());
}
private void readAudioData() {
byte[] buffer = new byte[chunkSize];
while (isRunning) {
if (mAudioRecorder == null)
break;
int bytesRead = mAudioRecorder.read(buffer, 0, chunkSize);
if (bytesRead > 0) {
if (mStreamingPlayer == null)
break;
mStreamingPlayer.write(buffer, 0, chunkSize);
}
}
// 录制/播放结束--------------------------------------
try {
Thread.sleep(888);
mStreamingPlayer.flush();
mStreamingPlayer.stop();
mStreamingPlayer.release();
mAudioRecorder.stop();
mAudioRecorder.release();
if (agc != null) {
agc.setEnabled(false);
agc.release();
Log.i(TAG, "readAudioData: close agc");
}
if (suppressor != null) {
suppressor.setEnabled(false);
suppressor.release();
Log.i(TAG, "readAudioData: close ns");
}
if (aec != null) {
aec.setEnabled(false);
aec.release();
Log.i(TAG, "readAudioData: close aec");
}
Log.i(TAG, "readAudioData: release ok");
} catch (Exception e) {
e.printStackTrace();
Log.i(TAG, "readAudioData: error:" + e.getMessage());
}
msg("录音结束");
}
private void msg(String s) {
new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(MainActivity.this, s, Toast.LENGTH_LONG).show());
}
private void setupVoipEffects(int sessionId) {
if (AutomaticGainControl.isAvailable()) {
agc = AutomaticGainControl.create(sessionId);
if (agc != null) {
if (!agc.getEnabled()) {
agc.setEnabled(true);
Log.d(LOG_TAG, "AGC is " + (agc.getEnabled() ? "enabled" : "disabled" + " after trying to enable"));
}
} else {
Log.i(TAG, "setupVoipEffects: agc is null");
}
}
if (NoiseSuppressor.isAvailable()) {
suppressor = NoiseSuppressor.create(sessionId);
if (suppressor != null) {
if (!suppressor.getEnabled()) {
suppressor.setEnabled(true);
Log.d(LOG_TAG, "NS is " + (suppressor.getEnabled() ? "enabled" : "disabled" + " after trying to disable"));
}
} else {
Log.i(TAG, "setupVoipEffects: ns is null");
}
}
if (AcousticEchoCanceler.isAvailable()) {
aec = AcousticEchoCanceler.create(sessionId);
if (aec != null) {
if (!aec.getEnabled()) {
aec.setEnabled(true);
Log.d(LOG_TAG, "AEC is " + (aec.getEnabled() ? "enabled" : "disabled" + " after trying to disable"));
}
} else {
Log.i(TAG, "setupVoipEffects: aec is null");
}
}
}
}