Android 录音详解(四)—— 录音添加背景音乐
2018-12-10 liuyingcong 安卓开发
K歌类 APP 都是在录音的基础上与伴奏音乐合并,组成一首曲目:
今天就讲一讲把 mp3 格式的背景音乐解码,与录音合并,并最终输出 mp3 格式的文件。注意是实时的哦,即边录音边解码边合成,录音结束即合并结束:
// 录音状态 private boolean isRecording; //开始录音 private void record() { new Thread() { @Override public void run() { // 音源 int audioSource = MediaRecorder.AudioSource.MIC; // 采样率 int sampleRate = 44100; // 声道 int channelConfig = AudioFormat.CHANNEL_IN_MONO;//单声道 // 采样位数 int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 录音最小缓存区大小 int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat); // 录音对象 AudioRecord audioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, audioFormat, bufferSizeInBytes); try { // 音轨提取器 MediaExtractor mediaExtractor = new MediaExtractor(); // 给音轨提取器设置文件路径 mediaExtractor.setDataSource(getExternalCacheDir() + "/bg.mp3"); // 获取音频格式信息 MediaFormat mediaFormat = mediaExtractor.getTrackFormat(0); // 获取音频类型 String mime = mediaFormat.getString(MediaFormat.KEY_MIME); // 选中音轨 mediaExtractor.selectTrack(0); // 构造解码器 MediaCodec mediaCodec = MediaCodec.createDecoderByType(mime); // 配置解码器 mediaCodec.configure(mediaFormat, null, null, 0); // 开始解码 mediaCodec.start(); // 解码器在此缓存中获取输入数据 ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); // 编码器将解码后的数据放入此缓存中,存放的是pcm数据 ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers(); // 用于描述解码得到的byte[]数据的相关信息 MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); // 背景音乐解码后的数据输出流,先存储,然后读取再和录音合并 FileOutputStream fos_wav = new FileOutputStream(getExternalCacheDir() + "/demo.wav"); // 读取处理好的背景音乐,和录音合并 RandomAccessFile raf = new RandomAccessFile(getExternalCacheDir() + "/demo.wav", "rw"); // 背景音字节数 long length = 0; // 存储大小端 boolean isBigEnding = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; // 录音缓存 short[] buffer = new short[bufferSizeInBytes]; // 最终生成的mp3数据的缓存器 byte[] mp3buffer = new byte[(int) (7200 + buffer.length * 1.25)]; // 开始录音 audioRecord.startRecording(); // 录音状态 isRecording = true; // 最终文件输出流 FileOutputStream fos = new FileOutputStream(getExternalCacheDir() + "/demo.mp3"); // mp3编码器初始化 MP3Recorder.init(sampleRate, 1, sampleRate, 128, 5); while (isRecording) { // 读取录音 int readSize = audioRecord.read(buffer, 0, bufferSizeInBytes); // 解码背景音乐 for (ByteBuffer ib : inputBuffers) { // 获取输入缓存器 int inputIndex = mediaCodec.dequeueInputBuffer(0); if (inputIndex < 0) { break; } ByteBuffer inputBuffer = inputBuffers[inputIndex]; // 读取数据到输入缓存器 int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0); if (sampleSize >= 0) { // 通知解码器输入了数据 mediaCodec.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0); // 移动到下一取样处 mediaExtractor.advance(); } } // 输出缓存器 int outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0); if (outputIndex == -2) { // 格式变了,重新获取一次 outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0); } // 拿到用于存放PCM数据的buffer ByteBuffer outputBuffer; // PCM数据 byte[] chunckPCM; // 循环读取解码数据 while (outputIndex >= 0) { // 拿到用于存放PCM数据的buffer outputBuffer = outputBuffers[outputIndex]; // bufferInfo 定义了数据块的大小 chunckPCM = new byte[bufferInfo.size]; // 将buffer内的数据取出到字节数组中 outputBuffer.get(chunckPCM); // 数据取出后,一定记得清空,mediaCodec是反复使用这些buffer的 outputBuffer.clear(); // 输出PCM数据到文件夹 fos_wav.write(chunckPCM, 0, bufferInfo.size); // 背景音字节数(解码后的) length += bufferInfo.size; // 释放输出buffer,不然mediaCodec用完所有buffer后,就不再向外输出数据 mediaCodec.releaseOutputBuffer(outputIndex, false); // 再次获取数据,如果没有数据则outputIndex=-1,结束循环 outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0); } // 混音 for (int i = 0; i < buffer.length; i++) { if (raf.getFilePointer() >= length - 1) { raf.seek(0); } if (isBigEnding) { buffer[i] += (short) ((raf.read() << 8) + raf.read()); } else { buffer[i] += (short) (raf.read() + (raf.read() << 8)); } } if (readSize > 0) { int encodeSize = MP3Recorder.encode(buffer, buffer, readSize, mp3buffer); if (encodeSize > 0) { try { fos.write(mp3buffer, 0, encodeSize); } catch (IOException e) { e.printStackTrace(); } } } } int flushSize = MP3Recorder.flush(mp3buffer); if (flushSize > 0) { try { fos.write(mp3buffer, 0, flushSize); } catch (IOException e) { e.printStackTrace(); } } try { fos.close(); raf.close(); fos_wav.close(); } catch (IOException e) { e.printStackTrace(); } audioRecord.stop(); audioRecord.release(); MP3Recorder.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }.start(); } public void stop() { isRecording = false; }
你可以把待解码的 mp3 文件放在任意文件目录中,修改上面的 filePath 即可,如果你懒得修改,你可以先运行一遍程序,然后再把 mp3 文件放在手机 文件管理器-Android-data-包名-cache 目录下,并改名为 bg.mp3,再运行程序解码,解码完成的文件 demo.wav 也存放在这个目录下,最终合成的录音文件 demo.mp3 也在这个目录下。