Android 录音详解(三)—— 音频解码

2018-12-5 liuyingcong 安卓开发

    想要转换音频格式(如 mp3格式转 wav格式)或者添加背景音乐,都需要解码声音文件,今天就来讲一讲解码的实现:

    幸运的是 Android SDK 中提供了解码的 API,它就是 MediaCodec,也就是音频解码器,我们用它实现 mp3格式音频的解码:

    

    public void startDecoding() {
        // 待解码声音文件路径
        String filePath = getExternalCacheDir() + "/bg.mp3";
        // 音/视频 提取器
        MediaExtractor mediaExtractor = new MediaExtractor(); try {
            mediaExtractor.setDataSource(filePath);
            // 分离音频用的,获取音频数据
            MediaFormat mediaFormat = mediaExtractor.getTrackFormat(0);
          /*// 获取采样率
            int sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
            // 获取声道数
            int channelCount = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
            // 获取时长
            long duration = mediaFormat.getLong(MediaFormat.KEY_DURATION);*/
            // 获取类型
            String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
          /*System.out.println("sampleRate:" + sampleRate + ",channelCount:" +
            channelCount + ",duration:" + duration + ",mime:" + 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();
            
            // 创建随机读写流
            RandomAccessFile raf = new RandomAccessFile(getExternalCacheDir() + "/demo.wav", "rw");
            // 留出文件头的位置
            raf.seek(44);
            // 解码状态
            boolean isDecoding = true;
            main:
            while (isDecoding) {
                for (ByteBuffer ib : inputBuffers) {
                    // 获取输入缓存器
                    int inputIndex = mediaCodec.dequeueInputBuffer(-1);
                    if (inputIndex < 0) {
                        break main;
                    }
                    ByteBuffer inputBuffer = inputBuffers[inputIndex];
                    // 读取数据到输入缓存器
                    int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);
                    if (sampleSize < 0) {
                        isDecoding = false;
                    } else {
                        // 通知解码器输入了数据
                        mediaCodec.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0);
                        // 移动到下一取样处
                        mediaExtractor.advance();
                    }
                }
                
                // 获取输出缓存器
                int outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 10000);
                if (outputIndex == -2) {
                    // 格式变了,重新获取一次
                    outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 10000);
                }
                // 拿到用于存放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数据到文件夹
                    raf.write(chunckPCM, 0, bufferInfo.size);
                    // 释放输出buffer,不然mediaCodec用完所有buffer后,就不再向外输出数据
                    mediaCodec.releaseOutputBuffer(outputIndex, false);
                    // 再次获取数据,如果没有数据则outputIndex=-1,结束循环
                    outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 10000);
                }
                
            }
            WriteWaveFileHeader(raf, raf.length(), 44100, 1, 44100 * 16 / 8);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 为 wav 文件添加文件头,前提是在头部预留了 44字节空间
     *
     * @param raf        随机读写流
     * @param fileLength 文件总长
     * @param sampleRate 采样率
     * @param channels   声道数量
     * @param byteRate   码率 = 采样率 * 采样位数 * 声道数 / 8
     * @throws IOException
     */
    private void WriteWaveFileHeader(RandomAccessFile raf, long fileLength, long sampleRate, int channels, long byteRate) throws IOException {
        long totalDataLen = fileLength + 36;
        byte[] header = new byte[44];
        header[0] = 'R'; // RIFF/WAVE header
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        header[12] = 'f'; // 'fmt ' chunk
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        header[16] = 16; // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        header[20] = 1; // format = 1
        header[21] = 0;
        header[22] = (byte) channels;
        header[23] = 0;
        header[24] = (byte) (sampleRate & 0xff);
        header[25] = (byte) ((sampleRate >> 8) & 0xff);
        header[26] = (byte) ((sampleRate >> 16) & 0xff);
        header[27] = (byte) ((sampleRate >> 24) & 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        header[32] = (byte) (2 * 16 / 8); // block align
        header[33] = 0;
        header[34] = 16; // bits per sample
        header[35] = 0;
        header[36] = 'd';
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (fileLength & 0xff);
        header[41] = (byte) ((fileLength >> 8) & 0xff);
        header[42] = (byte) ((fileLength >> 16) & 0xff);
        header[43] = (byte) ((fileLength >> 24) & 0xff);
        raf.seek(0);
        raf.write(header, 0, 44);
    }
        你可以把待解码的 mp3 文件放在任意文件目录中,修改上面的 filePath 即可,如果你懒得修改,你可以先运行一遍程序,然后再把 mp3 文件放在手机 文件管理器-Android-data-包名-cache 目录下,并改名为 bg.mp3,再运行程序解码,解码完成的文件 demo.wav 也存放在这个目录下。



    

标签: Android 音频解码 MediaCodec

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