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