音频混合:实时录制audio时录制麦克风数据 和 写入背景音乐
测试代码:https://github.com/CL-window/audio_mix
本次案例实现了
*MediaPlayer 播放音频
*AudioTrack 播放音频 mp3 --> pcm data ( libs/jl1.0.1.jar )
*AudioRecord 录制音频 pcm file
*AudioTrack 播放音频 pcm data
*AudioRecord 录制音频 use MediaCodec & MediaMuxer write data
*MediaExtractor 和 MediaCodec 手动解码出 pcm 数据
*混合音频
音频混合核心算法来自前辈,表示感谢
/**
* 采用简单的平均算法 average audio mixing algorithm
* code from : http://www.codexiu.cn/android/blog/3618/
* 测试发现这种算法会降低 录制的音量
*/
private byte[] averageMix(byte[][] bMulRoadAudioes) {
if (bMulRoadAudioes == null || bMulRoadAudioes.length == 0)
return null;
byte[] realMixAudio = bMulRoadAudioes[0];
if (bMulRoadAudioes.length == 1)
return realMixAudio;
for (int rw = 0; rw < bMulRoadAudioes.length; ++rw) {
if (bMulRoadAudioes[rw].length != realMixAudio.length) {
Log.e("app", "column of the road of audio + " + rw + " is diffrent.");
return null;
}
}
int row = bMulRoadAudioes.length;
int coloum = realMixAudio.length / 2;
short[][] sMulRoadAudioes = new short[row][coloum];
for (int r = 0; r < row; ++r) {
for (int c = 0; c < coloum; ++c) {
sMulRoadAudioes[r][c] = (short) ((bMulRoadAudioes[r][c * 2] & 0xff) | (bMulRoadAudioes[r][c * 2 + 1] & 0xff) << 8);
}
}
short[] sMixAudio = new short[coloum];
int mixVal;
int sr = 0;
for (int sc = 0; sc < coloum; ++sc) {
mixVal = 0;
sr = 0;
for (; sr < row; ++sr) {
mixVal += sMulRoadAudioes[sr][sc];
}
sMixAudio[sc] = (short) (mixVal / row);
}
for (sr = 0; sr < coloum; ++sr) {
realMixAudio[sr * 2] = (byte) (sMixAudio[sr] & 0x00FF);
realMixAudio[sr * 2 + 1] = (byte) ((sMixAudio[sr] & 0xFF00) >> 8);
}
return realMixAudio;
}
1.MediaPlayer 播放音乐,这个简单
private void initMediaPlayer(String filePath) {
releaseMediaPlayer();
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
Log.i("slack", "onPrepared...");
mMediaPlayer.start();
}
});
try {
mMediaPlayer.setDataSource(filePath);
mMediaPlayer.prepareAsync();
} catch (IOException e) {
e.printStackTrace();
Log.i("slack", e.getMessage());
}
}
2.AudioTrack 播放音频 mp3 --> pcm data 使用了( libs/jl1.0.1.jar )
class PlayTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... arg0) {
mIsPlaying = true;
Decoder mDecoder = new Decoder();
try {
int bufferSize = AudioTrack.getMinBufferSize(mFrequence,
mPlayChannelConfig, mAudioEncoding);
short[] buffer = new short[bufferSize];
// 定义输入流,将音频写入到AudioTrack类中,实现播放
FileInputStream fin = new FileInputStream(mp3FilePath);
Bitstream bitstream = new Bitstream(fin);
// 实例AudioTrack
AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC,
mFrequence,
mPlayChannelConfig, mAudioEncoding, bufferSize,
AudioTrack.MODE_STREAM);
// 开始播放
track.play();
// 由于AudioTrack播放的是流,所以,我们需要一边播放一边读取
Header header;
while (mIsPlaying && (header = bitstream.readFrame()) != null) {
SampleBuffer sampleBuffer = (SampleBuffer) mDecoder.decodeFrame(header, bitstream);
buffer = sampleBuffer.getBuffer();
track.write(buffer, 0, buffer.length);
bitstream.closeFrame();
}
// 播放结束
track.stop();
track.release();
fin.close();
} catch (Exception e) {
// TODO: handle exception
Log.e("slack", "error:" + e.getMessage());
}
return null;
}
protected void onPostExecute(Void result) {
}
protected void onPreExecute() {
}
}
3.AudioRecord 录制音频 pcm file
class RecordTask extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... arg0) {
mIsRecording = true;
try {
// 开通输出流到指定的文件
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(mAudioFile)));
// 根据定义好的几个配置,来获取合适的缓冲大小
int bufferSize = AudioRecord.getMinBufferSize(mFrequence,
mChannelStereo, mAudioEncoding);
// 实例化AudioRecord
// AudioRecord record = findAudioRecord();
AudioRecord record = new AudioRecord(
MediaRecorder.AudioSource.MIC, mFrequence,
mChannelConfig, mAudioEncoding, bufferSize);
// 定义缓冲
short[] buffer = new short[bufferSize];
// 开始录制
record.startRecording();
int r = 0; // 存储录制进度
// 定义循环,根据isRecording的值来判断是否继续录制
while (mIsRecording) {
// 从bufferSize中读取字节,返回读取的short个数
int bufferReadResult = record
.read(buffer, 0, buffer.length);
// 循环将buffer中的音频数据写入到OutputStream中
for (int i = 0; i < bufferReadResult; i++) {
dos.writeShort(buffer[i]);
}
publishProgress(new Integer(r)); // 向UI线程报告当前进度
r++; // 自增进度值
}
// 录制结束
record.stop();
Log.i("slack", "::" + mAudioFile.length());
dos.close();
} catch (Exception e) {
// TODO: handle exception
Log.e("slack", "::" + e.getMessage());
}
return null;
}
// 当在上面方法中调用publishProgress时,该方法触发,该方法在UI线程中被执行
protected void onProgressUpdate(Integer... progress) {
//
}
protected void onPostExecute(Void result) {
}
}
4.AudioTrack 播放音频 pcm data
class PlayPCMTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... arg0) {
mIsPlaying = true;
int bufferSize = AudioTrack.getMinBufferSize(mFrequence,
mPlayChannelConfig, mAudioEncoding);
short[] buffer = new short[bufferSize];
try {
// 定义输入流,将音频写入到AudioTrack类中,实现播放
DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream(mAudioFile)));
// 实例AudioTrack
// AudioTrack AudioFormat.CHANNEL_IN_STEREO here may some problem