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 也在这个目录下。

标签: 录音添加背景音乐 添加伴奏

网站备案号:京ICP备11043289号-1 北京市公安局网络备案 海1101084571
版权所有 北京育灵童科技发展有限公司 Copyright © 2002-2024 www.elight.cn, All Rights Reserved