实现KTV效果:播放歌曲,录音(存储录音文件),并同步播放录音 & 解释4.3以上audio与media资源冲突问题

时间:2022-08-03 10:13:54

很多唱歌类型的APP(比如唱吧),都在寻求实现好的用户体验,其中一个就是能够一边边播放歌曲一边唱(声音会被录下来),有人便想进一步实现播放录的声音(就像拿着麦克风唱歌的感觉,自己可以听见自己的声音),这个想法是好的,也可行。

具体方案是:MediaPlayer播放音乐,MediaRecorder录音并保存; AudioTrack和AudioRecord两个配合实现实时语音流的记录和同步播放(可认为是轻量级的MediaPlayer和MediaRecorder,细节还有有差别请自行检索

但是要是你去下载唱吧,却发现它并没有实现“可以听见自己唱歌的声音”的效果,经过我在做一个项目中的尝试、研究发现,原来android4.3(API18)以上不支持MediaRecorder与AudioRecord的共用,即:只要同时使用两者,则只能实现存储录音文件(MediaRecorder),不能实时获取语音流(AudioRecord)。


具体代码可见下方:

public class MainActivity extends Activity{
private MediaPlayer mediaPlayer;
private MediaRecorder mediaRecorder;
private AudioTrack audioTrack;
private AudioRecord audioRecord;
private int recBufSize, playBufSize;

private static final int sampleRateInHz = 44100;
private static final int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
private static final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

initMediaPlayer();
initMediaRecord();

initAudioTrack();
initAudioRecord();

new RecordPlayThread().start();

}

/**
* 初始化记录音频流资源
*/
private void initAudioRecord() {
recBufSize = audioRecord.getMinBufferSize(sampleRateInHz,channelConfig, audioFormat);
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
sampleRateInHz, channelConfig, audioFormat, recBufSize);
}

/**
* 初始化播放音频流资源
*/
private void initAudioTrack() {
playBufSize = audioTrack.getMinBufferSize(sampleRateInHz,channelConfig, audioFormat);
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz,
channelConfig, audioFormat, playBufSize,
AudioTrack.MODE_STREAM);
}

//音乐播放路径:需要在手机里的"1yzz"文件夹中放一个名为"test1.mp3"的文件(可自行修改)
private String playpath1 = Environment.getExternalStorageDirectory()+ "/1yzz/test1.mp3";
/**
* 初始化音乐播放
*/
private void initMediaPlayer(){
if (mediaPlayer!=null){
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
mediaPlayer = new MediaPlayer();
try {
mediaPlayer.setDataSource(playpath1);
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setLooping(true);
mediaPlayer.prepare();
mediaPlayer.start();

} catch (Exception e) {
e.printStackTrace();
}
}


private String recordpath;
private File audioFile;
private boolean isrecording;
/**
* 初始化录音
*/
private void initMediaRecord() {
if (mediaRecorder != null) {
mediaRecorder.stop();
mediaRecorder.release();
mediaRecorder = null;
}
SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyyMMddhhmmss");
String date = sDateFormat.format(new java.util.Date());
//录音文件保存路径:手机中的"1yzz"文件夹(可自行修改)
String Fpath = Environment.getExternalStorageDirectory() + "/1yzz";
File file = new File(Fpath);
if (!file.exists()) {
file.mkdir();
}
//录音文件命名方式:"时间"+"record"+".mp3"(可自行修改)
recordpath = Fpath + "/" + date + "record.mp3";
audioFile = new File(recordpath);
mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//从麦克风采集
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
mediaRecorder.setAudioSamplingRate(44100); //采样率
mediaRecorder.setAudioChannels(1); //单声道
mediaRecorder.setAudioEncodingBitRate(128000);//比特率
mediaRecorder.setOutputFile(audioFile.getAbsolutePath());
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);//声音编码格式
try {
isrecording = true;
mediaRecorder.prepare();
mediaRecorder.start();
} catch (Exception e) {
e.printStackTrace();
}

}

/**
* 边录音边播放线程
*/
class RecordPlayThread extends Thread {
public void run() {
byte[] data = new byte[recBufSize];
int num = 0;
audioRecord.startRecording();
audioTrack.play();
while (isrecording) {
num = audioRecord.read(data, 0, recBufSize);
byte[] tmpBuf = new byte[num];
System.arraycopy(data, 0, tmpBuf, 0, num);
audioTrack.write(tmpBuf, 0, tmpBuf.length);
}

}
}

@Override
public void onDestroy() {
isrecording = false;
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
if (mediaRecorder != null) {
mediaRecorder.stop();
mediaRecorder.release();
mediaRecorder = null;
}
if (audioTrack!=null) {
audioTrack.stop();
audioTrack.release();
audioTrack = null;
}
if (audioRecord!=null) {
audioRecord.stop();
audioRecord.release();
audioRecord = null;
}
super.onDestroy();
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK ) {
finish();
return true;
} else {
return super.onKeyDown(keyCode, event);
}
}

}


注意需要添加以下权限:

	<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>


在4.3以下运行以上代码,发现可以播放歌曲、录音、同步播放自己声音,体验很好(具体参数也可以调整)

可是在4.3(API18)以及以上的手机就会发现:不能播放自己的声音!(不会抛错,功放只播歌曲而没有自己从麦克风唱歌的声音);经调试可以发现,若在MediaRecorder使用时,要AudioRecord获取语音流是获取不到的(读出来是0),也就是说,受API的限制,两者资源不能同时使用(可能是考虑到资源调用安全性问题,深层原因待进一步研究,水平有限望见谅)


简单说明:AudioRecord可以从麦克风记录短时长的语音流,之后AudioTrack可以从中读取出来并通过功放播放出来;若只用以上两个资源而不使用MediaPlayer和MediaRecorder,则能够实现一个麦克风加功放的简单效果(这时因为未使用MediaRecorder,在4.3以上也是可以正常实现的,因为4.3只限制MediaRecorder与AudioRecord的同时使用,只使用其中一个是没问题的


欢迎建议、补充、指正!

demo下载地址 :http://download.csdn.net/detail/duguju/9082813