package com.aipeople.by.test;
import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.util.Log;
import org.webrtc.voiceengine.WebRtcAudioEffects;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 音频管理工具类(集成WebRTC回声消除+噪声抑制,适配所有Android版本,无编译错误)
*/
public class AudioManager {
private static final String TAG = "AudioManager";
private Context mContext;
// 录音参数(适配WebRTC)
private static final int SAMPLE_RATE = 16000;
private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
private AudioRecord mAudioRecord;
private WebRtcAudioEffects webRtcAudioEffects; // 核心音频效果器
private boolean isRecording = false;
private File mRecordFile;
// 兼容低版本Android:SESSION_ID_GENERATE的实际值是-1
private static final int AUDIO_SESSION_ID_GENERATE = -1;
// 播放相关
private MediaPlayer mMediaPlayer;
public AudioManager(Context context) {
this.mContext = context;
initRecordFile();
}
/**
* 初始化录音文件(应用私有目录,无需外部权限)
*/
private void initRecordFile() {
File recordDir = new File(mContext.getFilesDir(), "AudioDemo");
if (!recordDir.exists()) recordDir.mkdirs();
mRecordFile = new File(recordDir, "record_" + System.currentTimeMillis() + ".pcm");
}
/**
* 开始录音(WebRTC回声消除+噪声抑制)
* @return 录音是否启动成功
*/
public boolean startRecordWithAEC() {
try {
// 1. 计算最小缓存区
int bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT);
if (bufferSize == AudioRecord.ERROR_BAD_VALUE) {
Log.e(TAG, "缓存区计算失败");
return false;
}
// 2. 创建AudioRecord(通话场景优化)
mAudioRecord = new AudioRecord(
MediaRecorder.AudioSource.VOICE_COMMUNICATION,
SAMPLE_RATE,
CHANNEL_CONFIG,
AUDIO_FORMAT,
bufferSize * 2
);
// 3. 初始化WebRTC音频效果(核心:AEC+NS,补充生效校验)
initWebRtcAudioEffects();
// 4. 开始录音
mAudioRecord.startRecording();
isRecording = true;
// 5. 写入录音数据(WebRTC已在底层处理音频)
new Thread(() -> {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(mRecordFile);
byte[] buffer = new byte[bufferSize];
while (isRecording) {
int readSize = mAudioRecord.read(buffer, 0, buffer.length);
if (readSize > 0) {
fos.write(buffer, 0, readSize); // 直接写入处理后的音频
}
}
} catch (IOException e) {
Log.e(TAG, "录音写入失败:" + e.getMessage());
} finally {
try {
if (fos != null) fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
Log.d(TAG, "录音启动成功,WebRTC音频效果已启用");
return true;
} catch (Exception e) {
Log.e(TAG, "录音启动失败:" + e.getMessage());
releaseWebRtcResources();
return false;
}
}
/**
* 初始化WebRTC音频效果器(补充AEC生效校验)
*/
private void initWebRtcAudioEffects() {
try {
if (mAudioRecord == null) return;
// 1. 先检查设备是否支持硬件AEC
boolean aecSupported = WebRtcAudioEffects.canUseAcousticEchoCanceler();
boolean nsSupported = WebRtcAudioEffects.canUseNoiseSuppressor();
Log.d(TAG, "设备支持AEC: " + aecSupported + ", 支持NS: " + nsSupported);
if (!aecSupported) {
Log.w(TAG, "⚠️ 设备不支持硬件AEC,回声消除可能无效!");
}
// 2. 正确创建实例:无参构造
webRtcAudioEffects = WebRtcAudioEffects.create();
if (webRtcAudioEffects != null) {
// 3. 正确设置效果:只有setAEC和setNS,没有setAgc
boolean aecEnabled = webRtcAudioEffects.setAEC(true); // 开启回声消除
boolean nsEnabled = webRtcAudioEffects.setNS(true); // 开启噪声抑制
Log.d(TAG, "WebRTC AEC开启结果: " + aecEnabled + ", NS开启结果: " + nsEnabled);
// 4. 正确启用:需要传入audioSessionId
webRtcAudioEffects.enable(mAudioRecord.getAudioSessionId());
Log.d(TAG, "WebRTC音频效果已启用,音频会话ID: " + mAudioRecord.getAudioSessionId());
} else {
Log.w(TAG, "设备不支持WebRTC音频效果,使用系统默认");
}
} catch (Exception e) {
Log.e(TAG, "WebRTC音频效果初始化失败:" + e.getMessage());
}
}
/**
* 停止录音
*/
public void stopRecord() {
isRecording = false;
// 释放AudioRecord
if (mAudioRecord != null) {
try {
mAudioRecord.stop();
mAudioRecord.release();
} catch (Exception e) {
e.printStackTrace();
} finally {
mAudioRecord = null;
}
}
// 释放WebRTC资源
releaseWebRtcResources();
Log.d(TAG, "录音停止,文件保存至:" + mRecordFile.getAbsolutePath());
}
/**
* 释放WebRTC音频效果资源
*/
private void releaseWebRtcResources() {
if (webRtcAudioEffects != null) {
webRtcAudioEffects.release();
webRtcAudioEffects = null;
}
}
/**
* 播放在线音频(已绑定录音的音频会话ID,解决回声问题)
*/
public void playOnlineAudio(String audioUrl, OnAudioPlayCallback callback) {
stopAllPlayback();
try {
mMediaPlayer = new MediaPlayer();
// 关键修改1:改用通话声道(STREAM_VOICE_CALL)
mMediaPlayer.setAudioStreamType(android.media.AudioManager.STREAM_VOICE_CALL);
// 关键修改2:绑定到录音的音频会话ID,让AEC获取参考音频
if (mAudioRecord != null) {
mMediaPlayer.setAudioSessionId(mAudioRecord.getAudioSessionId());
Log.d(TAG, "在线音频绑定到录音会话ID: " + mAudioRecord.getAudioSessionId());
}
mMediaPlayer.setDataSource(audioUrl);
mMediaPlayer.prepareAsync();
mMediaPlayer.setOnPreparedListener(mp -> {
mp.start();
if (callback != null) callback.onStart();
Log.d(TAG, "在线音频开始播放");
});
mMediaPlayer.setOnCompletionListener(mp -> {
if (callback != null) callback.onComplete();
stopAllPlayback();
});
mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
String errorMsg = "播放失败,错误码:" + what;
if (callback != null) callback.onError(errorMsg);
stopAllPlayback();
Log.e(TAG, errorMsg);
return true;
});
} catch (Exception e) {
String errorMsg = "在线音频初始化失败:" + e.getMessage();
if (callback != null) callback.onError(errorMsg);
Log.e(TAG, errorMsg);
}
}
/**
* 播放本地录音(适配所有Android版本,无编译错误)
*/
public void playLocalRecord(OnAudioPlayCallback callback) {
if (!mRecordFile.exists()) {
String errorMsg = "录音文件不存在";
if (callback != null) callback.onError(errorMsg);
Log.w(TAG, errorMsg);
return;
}
stopAllPlayback();
new Thread(() -> {
AudioTrack audioTrack = null;
FileInputStream fis = null;
try {
int bufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AUDIO_FORMAT);
// 关键:统一使用传统构造方法(适配所有Android版本,无编译错误)
int audioSessionId = AUDIO_SESSION_ID_GENERATE;
if (mAudioRecord != null) {
audioSessionId = mAudioRecord.getAudioSessionId();
}
if (audioSessionId<0){
audioSessionId=0;
}
// 所有版本都用这个构造方法,避免Builder的兼容性问题
audioTrack = new AudioTrack(
android.media.AudioManager.STREAM_VOICE_CALL, // 通话声道,适配AEC
SAMPLE_RATE,
AudioFormat.CHANNEL_OUT_MONO,
AUDIO_FORMAT,
bufferSize,
AudioTrack.MODE_STREAM,
audioSessionId // 绑定录音会话ID(所有版本都支持)
);
Log.d(TAG, "本地录音绑定到录音会话ID: " + audioSessionId);
audioTrack.play();
if (callback != null) callback.onStart();
fis = new FileInputStream(mRecordFile);
byte[] buffer = new byte[bufferSize];
int readSize;
while ((readSize = fis.read(buffer)) > 0) {
audioTrack.write(buffer, 0, readSize);
}
audioTrack.stop();
if (callback != null) callback.onComplete();
} catch (Exception e) {
String errorMsg = "本地录音播放失败:" + e.getMessage();
if (callback != null) callback.onError(errorMsg);
Log.e(TAG, errorMsg);
} finally {
if (audioTrack != null) audioTrack.release();
try {
if (fis != null) fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
/**
* 停止所有音频播放
*/
public void stopAllPlayback() {
if (mMediaPlayer != null) {
try {
if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();
mMediaPlayer.release();
} catch (Exception e) {
e.printStackTrace();
} finally {
mMediaPlayer = null;
}
}
}
/**
* 检查是否有有效录音文件
*/
public boolean hasValidRecordFile() {
return mRecordFile != null && mRecordFile.exists() && mRecordFile.length() > 0;
}
/**
* 音频播放回调接口
*/
public interface OnAudioPlayCallback {
void onStart(); // 开始播放
void onComplete(); // 播放完成
void onError(String errorMsg); // 播放错误
}
/**
* 释放资源
*/
public void release() {
stopRecord();
stopAllPlayback();
}
}