上下观古今,起伏千万途。这篇文章主要讲述android MediaCodec 音频编解码的实现——转码相关的知识,希望能为你提供帮助。
原文地址:http://blog.csdn.net/tinsanmr/article/details/51049179
从今天开始 每周不定期更新博客,把这一周在工作与学习中遇到的问题做个总结。俗话说:好记性不如写博客,善于总结的人才能走的更远。写博客这种利人利己的好处我就不一 一列举了,总之,谁做谁知道,哈哈。在文章中如果有什么问题或者错误,欢迎各位的讨论和指正。好了,步入正题,来看看我们今天要讲的MediaCodec
一、概述
由于项目的需要,需要将mp3文件转码为aac音频文件,起初打算移植FFmpeg到项目中,无奈FFmpeg过于庞大,项目中的音频转码只是一个辅助util,并不是主要功能。所以打算用MediaCodec来实现这一需求。网上关于MediaCodec的文章少的可怜,写demo的过程中踩到了无数的坑。不过,在http://blog.csdn.net/tn0521/article/details/44980183
这篇文章的指引下,终于在耳机中听到了那美妙的旋律,激动的我把这一首歌听了一星期,因为他出自我的手。哈哈,开个玩笑!在此对这片文章的原创表示感谢,这也是我决定写博客的原因之一,利人利己。
二、转码实现原理
本篇文章以mp3转码成aac为例,转码实现原理:mp3->
pcm->
aac,首先将mp3解码成PCM,再将PCM编码成aac格式的音频文件。
PCM:可以将它理解为,未经过压缩的数字信号,mp3、aac等 理解为pcm压缩后的文件。播放器在播放mp3、aac等文件时要先将mp3等文件解码成PCM数据,然后再将PCM送到底层去处理播放
此处就好比 我要将rar压缩包内的文件改用zip压缩,->
解压rar-->
文件-->
压缩zip
三、遇到问题
1、编解码过程中会卡主:此为参数设置引起的,下面代码中会提到
2、编码的aac音频不能播放:在编码过程中需要为aac音频添加ADTS head,代码中有体现
3、最头痛的,转码速度太慢,转码一首歌长达5分钟。
此问题究其原因,是由于MediaExtractor每次喂给MediaCodec的数据太少,每次只喂一帧的数据,通过打印的log发现size不到1k,严重影响效率,后来尝试不用MediaExtractor去读数据,直接开流 BufferInputStream设置200k ,每次循环喂给MediaCodec200k的数据 , 最终!!! 在三星手机上完美运行,一次转码由5分钟,直接降到10多秒,但是,注意但是!!! 此方法在其他测试机上全报错,泪奔。
无奈,开线程,将解码和编码分别放到两个线程里面去执行,并且让MediaExtractor读取多次数据后再交给MediaCodec去处理,此方法转码一首歌大约1分钟左右(各位如果有好的方法不吝赐教,本人非常感激)
四、代码实现 1)初始化解码器
【android MediaCodec 音频编解码的实现——转码】MediaExtractor:可用于分离视频文件的音轨和视频轨道,如果你只想要视频,那么用selectTrack方法选中视频轨道,然后用readSampleData读出数据,这样你就得到了一个没有声音的视频。此处我们传入的是一个音频文件(mp3),所以也就只有一个轨道,音频轨道
mime:用来表示媒体文件的格式 mp3为audio/mpeg;aac为audio/mp4a-latm;mp4为video/mp4v-es
此处注意前缀 音频前缀为audio,视频前缀为video 我们可用此区别区分媒体文件内的音频轨道和视频轨道
mime的各种类型定义在MediaFormat静态常量中
MediaCodec.createDecoderByType(mime) 创建对应格式的解码器 要解码mp3 那么mime="audio/mpeg" 或者MediaFormat.MIMETYPE_AUDIO_MPEG其它同理
1 /** 2* 初始化解码器 3*/ 4private void initMediaDecode() { 5try { 6mediaExtractor=new MediaExtractor(); //此类可分离视频文件的音轨和视频轨道 7mediaExtractor.setDataSource(srcPath); //媒体文件的位置 8for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {//遍历媒体轨道 此处我们传入的是音频文件,所以也就只有一条轨道 9MediaFormat format = mediaExtractor.getTrackFormat(i); 10String mime = format.getString(MediaFormat.KEY_MIME); 11if (mime.startsWith("audio")) {//获取音频轨道 12 //format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 200 * 1024); 13mediaExtractor.selectTrack(i); //选择此音频轨道 14mediaDecode = MediaCodec.createDecoderByType(mime); //创建Decode解码器 15mediaDecode.configure(format, null, null, 0); 16break; 17} 18} 19} catch (IOException e) { 20e.printStackTrace(); 21} 22 23if (mediaDecode == null) { 24Log.e(TAG, "create mediaDecode failed"); 25return; 26} 27mediaDecode.start(); //启动MediaCodec ,等待传入数据 28decodeInputBuffers=mediaDecode.getInputBuffers(); //MediaCodec在此ByteBuffer[]中获取输入数据 29decodeOutputBuffers=mediaDecode.getOutputBuffers(); //MediaCodec将解码后的数据放到此ByteBuffer[]中 我们可以直接在这里面得到PCM数据 30decodeBufferInfo=new MediaCodec.BufferInfo(); //用于描述解码得到的byte[]数据的相关信息 31showLog("buffers:" + decodeInputBuffers.length); 32}
2)初始化编码器
编码器的创建于解码器的类似,只不过解码器的MediaFormat直接在音频文件内获取就可以了,编码器的MediaFormat需要自己来创建
1 /** 2* 初始化AAC编码器 3*/ 4 private void initAACMediaEncode() { 5try { 6MediaFormat encodeFormat = MediaFormat.createAudioFormat(encodeType, 44100, 2); //参数对应-> mime type、采样率、声道数 7encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000); //比特率 8encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); 9encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 100 * 1024); //作用于inputBuffer的大小 10mediaEncode = MediaCodec.createEncoderByType(encodeType); 11mediaEncode.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 12} catch (IOException e) { 13e.printStackTrace(); 14} 15 16if (mediaEncode == null) { 17Log.e(TAG, "create mediaEncode failed"); 18return; 19} 20mediaEncode.start(); 21encodeInputBuffers=mediaEncode.getInputBuffers(); 22encodeOutputBuffers=mediaEncode.getOutputBuffers(); 23encodeBufferInfo=new MediaCodec.BufferInfo(); 24 } 25
3)解码的实现
1 /** 2* 解码{@link #srcPath}音频文件 得到PCM数据块 3* @return 是否解码完所有数据 4*/ 5private void srcAudioFormatToPCM() { 6for (int i = 0; i < decodeInputBuffers.length-1; i++) { 7int inputIndex = mediaDecode.dequeueInputBuffer(-1); //获取可用的inputBuffer -1代表一直等待,0表示不等待 建议-1,避免丢帧 8if (inputIndex < 0) { 9codeOver =true; 10return; 11} 12 13ByteBuffer inputBuffer = decodeInputBuffers[inputIndex]; //拿到inputBuffer 14inputBuffer.clear(); //清空之前传入inputBuffer内的数据 15int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0); //MediaExtractor读取数据到inputBuffer中 16if (sampleSize < 0) {//小于0 代表所有数据已读取完成 17codeOver=true; 18}else { 19mediaDecode.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0); //通知MediaDecode解码刚刚传入的数据 20mediaExtractor.advance(); //MediaExtractor移动到下一取样处 21decodeSize+=sampleSize; 22} 23} 24 25//获取解码得到的byte[]数据 参数BufferInfo上面已介绍 10000同样为等待时间 同上-1代表一直等待,0代表不等待。此处单位为微秒 26//此处建议不要填-1 有些时候并没有数据输出,那么他就会一直卡在这 等待 27int outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000); 28 29 //showLog("decodeOutIndex:" + outputIndex); 30ByteBuffer outputBuffer; 31byte[] chunkPCM; 32while (outputIndex > = 0) {//每次解码完成的数据不一定能一次吐出 所以用while循环,保证解码器吐出所有数据 33outputBuffer = decodeOutputBuffers[outputIndex]; //拿到用于存放PCM数据的Buffer 34chunkPCM = new byte[decodeBufferInfo.size]; //BufferInfo内定义了此数据块的大小 35outputBuffer.get(chunkPCM); //将Buffer内的数据取出到字节数组中 36outputBuffer.clear(); //数据取出后一定记得清空此Buffer MediaCodec是循环使用这些Buffer的,不清空下次会得到同样的数据 37putPCMData(chunkPCM); //自己定义的方法,供编码器所在的线程获取数据,下面会贴出代码 38mediaDecode.releaseOutputBuffer(outputIndex, false); //此操作一定要做,不然MediaCodec用完所有的Buffer后 将不能向外输出数据 39outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000); //再次获取数据,如果没有数据输出则outputIndex=-1 循环结束 40} 41 42}
4)编码的实现
1 /** 2* 编码PCM数据 得到{@link #encodeType}格式的音频文件,并保存到{@link #dstPath} 3*/ 4private void dstAudioFormatFromPCM() { 5 6int inputIndex; 7ByteBuffer inputBuffer; 8int outputIndex; 9ByteBuffer outputBuffer; 10byte[] chunkAudio; 11int outBitSize; 12int outPacketSize; 13byte[] chunkPCM; 14 15 //showLog("doEncode"); 16for (int i = 0; i < encodeInputBuffers.length-1; i++) { 17chunkPCM=getPCMData(); //获取解码器所在线程输出的数据 代码后边会贴上 18if (chunkPCM == null) { 19break; 20} 21inputIndex = mediaEncode.dequeueInputBuffer(-1); //同解码器 22inputBuffer = encodeInputBuffers[inputIndex]; //同解码器 23inputBuffer.clear(); //同解码器 24inputBuffer.limit(chunkPCM.length); 25inputBuffer.put(chunkPCM); //PCM数据填充给inputBuffer 26mediaEncode.queueInputBuffer(inputIndex, 0, chunkPCM.length, 0, 0); //通知编码器 编码 27} 28 29outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000); //同解码器 30while (outputIndex > = 0) {//同解码器 31 32outBitSize=encodeBufferInfo.size; 33outPacketSize=outBitSize+7; //7为ADTS头部的大小 34outputBuffer = encodeOutputBuffers[outputIndex]; //拿到输出Buffer 35outputBuffer.position(encodeBufferInfo.offset); 36outputBuffer.limit(encodeBufferInfo.offset + outBitSize); 37chunkAudio = new byte[outPacketSize]; 38addADTStoPacket(chunkAudio,outPacketSize); //添加ADTS 代码后面会贴上 39outputBuffer.get(chunkAudio, 7, outBitSize); //将编码得到的AAC数据 取出到byte[]中 偏移量offset=7 你懂得 40outputBuffer.position(encodeBufferInfo.offset); 41 //showLog("outPacketSize:" + outPacketSize + " encodeOutBufferRemain:" + outputBuffer.remaining()); 42try { 43bos.write(chunkAudio,0,chunkAudio.length); //BufferOutputStream 将文件保存到内存卡中 *.aac 44} catch (IOException e) { 45e.printStackTrace(); 46} 47 48mediaEncode.releaseOutputBuffer(outputIndex,false); 49outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000); 50 51} 52 53} 54 55 56 57 58 /** 59* 添加ADTS头 60* @param packet 61* @param packetLen 62*/ 63private void addADTStoPacket(byte[] packet, int packetLen) { 64int profile = 2; // AAC LC 65int freqIdx = 4; // 44.1KHz 66int chanCfg = 2; // CPE 67 68 69 // fill in ADTS data 70packet[0] = (byte) 0xFF; 71packet[1] = (byte) 0xF9; 72packet[2] = (byte) (((profile - 1) < < 6) + (freqIdx < < 2) + (chanCfg > > 2)); 73packet[3] = (byte) (((chanCfg & 3) < < 6) + (packetLen > > 11)); 74packet[4] = (byte) ((packetLen & 0x7FF) > > 3); 75packet[5] = (byte) (((packetLen & 7) < < 5) + 0x1F); 76packet[6] = (byte) 0xFC; 77} 78
5)完整代码
文章图片
文章图片
1 package com.example.tinsan.mediaparser; 2 3 4import android.media.MediaCodec; 5import android.media.MediaCodecInfo; 6import android.media.MediaExtractor; 7import android.media.MediaFormat; 8import android.util.Log; 9 10import java.io.BufferedInputStream; 11import java.io.BufferedOutputStream; 12import java.io.File; 13import java.io.FileInputStream; 14import java.io.FileNotFoundException; 15import java.io.FileOutputStream; 16import java.io.IOException; 17import java.nio.ByteBuffer; 18import java.util.ArrayList; 19 20 /** 21* Created by senshan_wang on 2016/3/31. 22*/ 23 public class AudioCodec { 24 25private static final String TAG = "AudioCodec"; 26private String encodeType; 27private String srcPath; 28private String dstPath; 29private MediaCodec mediaDecode; 30private MediaCodec mediaEncode; 31private MediaExtractor mediaExtractor; 32private ByteBuffer[] decodeInputBuffers; 33private ByteBuffer[] decodeOutputBuffers; 34private ByteBuffer[] encodeInputBuffers; 35private ByteBuffer[] encodeOutputBuffers; 36private MediaCodec.BufferInfo decodeBufferInfo; 37private MediaCodec.BufferInfo encodeBufferInfo; 38private FileOutputStream fos; 39private BufferedOutputStream bos; 40private FileInputStream fis; 41private BufferedInputStream bis; 42private ArrayList< byte[]> chunkPCMDataContainer; //PCM数据块容器 43private OnCompleteListener onCompleteListener; 44private OnProgressListener onProgressListener; 45private long fileTotalSize; 46private long decodeSize; 47 48 49public static AudioCodec newInstance() { 50return new AudioCodec(); 51} 52 53/** 54* 设置编码器类型 55* @param encodeType 56*/ 57public void setEncodeType(String encodeType) { 58this.encodeType=encodeType; 59} 60 61/** 62* 设置输入输出文件位置 63* @param srcPath 64* @param dstPath 65*/ 66public void setIOPath(String srcPath, String dstPath) { 67this.srcPath=srcPath; 68this.dstPath=dstPath; 69} 70 71/** 72* 此类已经过封装 73* 调用prepare方法 会初始化Decode 、Encode 、输入输出流 等一些列操作 74*/ 75public void prepare() { 76 77if (encodeType == null) { 78throw new IllegalArgumentException("encodeType can‘t be null"); 79} 80 81if (srcPath == null) { 82throw new IllegalArgumentException("srcPath can‘t be null"); 83} 84 85if (dstPath == null) { 86throw new IllegalArgumentException("dstPath can‘t be null"); 87} 88 89try { 90fos = new FileOutputStream(new File(dstPath)); 91bos = new BufferedOutputStream(fos,200*1024); 92File file = new File(srcPath); 93fileTotalSize=file.length(); 94} catch (IOException e) { 95e.printStackTrace(); 96} 97chunkPCMDataContainer= new ArrayList< > (); 98initMediaDecode(); //解码器 99 100if (encodeType == MediaFormat.MIMETYPE_AUDIO_AAC) { 101initAACMediaEncode(); //AAC编码器 102}else if (encodeType == MediaFormat.MIMETYPE_AUDIO_MPEG) { 103initMPEGMediaEncode(); //mp3编码器 104} 105 106} 107 108/** 109* 初始化解码器 110*/ 111private void initMediaDecode() { 112try { 113mediaExtractor=new MediaExtractor(); //此类可分离视频文件的音轨和视频轨道 114mediaExtractor.setDataSource(srcPath); //媒体文件的位置 115for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {//遍历媒体轨道 此处我们传入的是音频文件,所以也就只有一条轨道 116MediaFormat format = mediaExtractor.getTrackFormat(i); 117String mime = format.getString(MediaFormat.KEY_MIME); 118if (mime.startsWith("audio")) {//获取音频轨道 119 //format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 200 * 1024); 120mediaExtractor.selectTrack(i); //选择此音频轨道 121mediaDecode = MediaCodec.createDecoderByType(mime); //创建Decode解码器 122mediaDecode.configure(format, null, null, 0); 123break; 124} 125} 126} catch (IOException e) { 127e.printStackTrace(); 128} 129 130if (mediaDecode == null) { 131Log.e(TAG, "create mediaDecode failed"); 132return; 133} 134mediaDecode.start(); //启动MediaCodec ,等待传入数据 135decodeInputBuffers=mediaDecode.getInputBuffers(); //MediaCodec在此ByteBuffer[]中获取输入数据 136decodeOutputBuffers=mediaDecode.getOutputBuffers(); //MediaCodec将解码后的数据放到此ByteBuffer[]中 我们可以直接在这里面得到PCM数据 137decodeBufferInfo=new MediaCodec.BufferInfo(); //用于描述解码得到的byte[]数据的相关信息 138showLog("buffers:" + decodeInputBuffers.length); 139} 140 141 142/** 143* 初始化AAC编码器 144*/ 145private void initAACMediaEncode() { 146try { 147MediaFormat encodeFormat = MediaFormat.createAudioFormat(encodeType, 44100, 2); //参数对应-> mime type、采样率、声道数 148encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000); //比特率 149encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); 150encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 100 * 1024); 151mediaEncode = MediaCodec.createEncoderByType(encodeType); 152mediaEncode.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 153} catch (IOException e) { 154e.printStackTrace(); 155} 156 157if (mediaEncode == null) { 158Log.e(TAG, "create mediaEncode failed"); 159return; 160} 161mediaEncode.start(); 162encodeInputBuffers=mediaEncode.getInputBuffers(); 163encodeOutputBuffers=mediaEncode.getOutputBuffers(); 164encodeBufferInfo=new MediaCodec.BufferInfo(); 165} 166 167/** 168* 初始化MPEG编码器 169*/ 170private void initMPEGMediaEncode() { 171 172} 173 174private boolean codeOver = false; 175/** 176* 开始转码 177* 音频数据{@link #srcPath}先解码成PCMPCM数据在编码成想要得到的{@link #encodeType}音频格式 178* mp3-> PCM-> aac 179*/ 180public void startAsync() { 181showLog("start"); 182 183new Thread(new DecodeRunnable()).start(); 184new Thread(new EncodeRunnable()).start(); 185 186} 187 188/** 189* 将PCM数据存入{@link #chunkPCMDataContainer} 190* @param pcmChunk PCM数据块 191*/ 192private void putPCMData(byte[] pcmChunk) { 193synchronized (AudioCodec.class) {//记得加锁 194chunkPCMDataContainer.add(pcmChunk); 195} 196} 197 198/** 199* 在Container中{@link #chunkPCMDataContainer}取出PCM数据 200* @return PCM数据块 201*/ 202private byte[] getPCMData() { 203synchronized (AudioCodec.class) {//记得加锁 204showLog("getPCM:"+chunkPCMDataContainer.size()); 205if (chunkPCMDataContainer.isEmpty()) { 206return null; 207} 208 209byte[] pcmChunk = chunkPCMDataContainer.get(0); //每次取出index 0 的数据 210chunkPCMDataContainer.remove(pcmChunk); //取出后将此数据remove掉 既能保证PCM数据块的取出顺序 又能及时释放内存 211return pcmChunk; 212} 213} 214 215 216/** 217* 解码{@link #srcPath}音频文件 得到PCM数据块 218* @return 是否解码完所有数据 219*/ 220private void srcAudioFormatToPCM() { 221for (int i = 0; i < decodeInputBuffers.length-1; i++) { 222int inputIndex = mediaDecode.dequeueInputBuffer(-1); //获取可用的inputBuffer -1代表一直等待,0表示不等待 建议-1,避免丢帧 223if (inputIndex < 0) { 224codeOver =true; 225return; 226} 227 228ByteBuffer inputBuffer = decodeInputBuffers[inputIndex]; //拿到inputBuffer 229inputBuffer.clear(); //清空之前传入inputBuffer内的数据 230int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0); //MediaExtractor读取数据到inputBuffer中 231if (sampleSize < 0) {//小于0 代表所有数据已读取完成 232codeOver=true; 233}else { 234mediaDecode.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0); //通知MediaDecode解码刚刚传入的数据 235mediaExtractor.advance(); //MediaExtractor移动到下一取样处 236decodeSize+=sampleSize; 237} 238} 239 240//获取解码得到的byte[]数据 参数BufferInfo上面已介绍 10000同样为等待时间 同上-1代表一直等待,0代表不等待。此处单位为微秒 241//此处建议不要填-1 有些时候并没有数据输出,那么他就会一直卡在这 等待 242int outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000); 243 244 //showLog("decodeOutIndex:" + outputIndex); 245ByteBuffer outputBuffer; 246byte[] chunkPCM; 247while (outputIndex > = 0) {//每次解码完成的数据不一定能一次吐出 所以用while循环,保证解码器吐出所有数据 248outputBuffer = decodeOutputBuffers[outputIndex]; //拿到用于存放PCM数据的Buffer 249chunkPCM = new byte[decodeBufferInfo.size]; //BufferInfo内定义了此数据块的大小 250outputBuffer.get(chunkPCM); //将Buffer内的数据取出到字节数组中 251outputBuffer.clear(); //数据取出后一定记得清空此Buffer MediaCodec是循环使用这些Buffer的,不清空下次会得到同样的数据 252putPCMData(chunkPCM); //自己定义的方法,供编码器所在的线程获取数据,下面会贴出代码 253mediaDecode.releaseOutputBuffer(outputIndex, false); //此操作一定要做,不然MediaCodec用完所有的Buffer后 将不能向外输出数据 254outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000); //再次获取数据,如果没有数据输出则outputIndex=-1 循环结束 255} 256 257} 258 259/** 260* 编码PCM数据 得到{@link #encodeType}格式的音频文件,并保存到{@link #dstPath} 261*/ 262private void dstAudioFormatFromPCM() { 263 264int inputIndex; 265ByteBuffer inputBuffer; 266int outputIndex; 267ByteBuffer outputBuffer; 268byte[] chunkAudio; 269int outBitSize; 270int outPacketSize; 271byte[] chunkPCM; 272 273 //showLog("doEncode"); 274for (int i = 0; i < encodeInputBuffers.length-1; i++) { 275chunkPCM=getPCMData(); //获取解码器所在线程输出的数据 代码后边会贴上 276if (chunkPCM == null) { 277break; 278} 279inputIndex = mediaEncode.dequeueInputBuffer(-1); //同解码器 280inputBuffer = encodeInputBuffers[inputIndex]; //同解码器 281inputBuffer.clear(); //同解码器 282inputBuffer.limit(chunkPCM.length); 283inputBuffer.put(chunkPCM); //PCM数据填充给inputBuffer 284mediaEncode.queueInputBuffer(inputIndex, 0, chunkPCM.length, 0, 0); //通知编码器 编码 285} 286 287outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000); //同解码器 288while (outputIndex > = 0) {//同解码器 289 290outBitSize=encodeBufferInfo.size; 291outPacketSize=outBitSize+7; //7为ADTS头部的大小 292outputBuffer = encodeOutputBuffers[outputIndex]; //拿到输出Buffer 293outputBuffer.position(encodeBufferInfo.offset); 294outputBuffer.limit(encodeBufferInfo.offset + outBitSize); 295chunkAudio = new byte[outPacketSize]; 296addADTStoPacket(chunkAudio,outPacketSize); //添加ADTS 代码后面会贴上 297outputBuffer.get(chunkAudio, 7, outBitSize); //将编码得到的AAC数据 取出到byte[]中 偏移量offset=7 你懂得 298outputBuffer.position(encodeBufferInfo.offset); 299 //showLog("outPacketSize:" + outPacketSize + " encodeOutBufferRemain:" + outputBuffer.remaining()); 300try { 301bos.write(chunkAudio,0,chunkAudio.length); //BufferOutputStream 将文件保存到内存卡中 *.aac 302} catch (IOException e) { 303e.printStackTrace(); 304} 305 306mediaEncode.releaseOutputBuffer(outputIndex,false); 307outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000); 308 309} 310} 311 312/** 313* 添加ADTS头 314* @param packet 315* @param packetLen 316*/ 317private void addADTStoPacket(byte[] packet, int packetLen) { 318int profile = 2; // AAC LC 319int freqIdx = 4; // 44.1KHz 320int chanCfg = 2; // CPE 321 322 323 // fill in ADTS data 324packet[0] = (byte) 0xFF; 325packet[1] = (byte) 0xF9; 326packet[2] = (byte) (((profile - 1) < < 6) + (freqIdx < < 2) + (chanCfg > > 2)); 327packet[3] = (byte) (((chanCfg & 3) < < 6) + (packetLen > > 11)); 328packet[4] = (byte) ((packetLen & 0x7FF) > > 3); 329packet[5] = (byte) (((packetLen & 7) < < 5) + 0x1F); 330packet[6] = (byte) 0xFC; 331} 332 333/** 334* 释放资源 335*/ 336public void release() { 337try { 338if (bos != null) { 339bos.flush(); 340} 341} catch (IOException e) { 342e.printStackTrace(); 343}finally { 344if (bos != null) { 345try { 346bos.close(); 347} catch (IOException e) { 348e.printStackTrace(); 349}finally { 350bos=null; 351} 352} 353} 354 355try { 356if (fos != null) { 357fos.close(); 358} 359} catch (IOException e) { 360e.printStackTrace(); 361}finally { 362fos=null; 363} 364 365if (mediaEncode != null) { 366mediaEncode.stop(); 367mediaEncode.release(); 368mediaEncode=null; 369} 370 371if (mediaDecode != null) { 372mediaDecode.stop(); 373mediaDecode.release(); 374mediaDecode=null; 375} 376 377if (mediaExtractor != null) { 378mediaExtractor.release(); 379mediaExtractor=null; 380} 381 382if (onCompleteListener != null) { 383onCompleteListener=null; 384} 385 386if (onProgressListener != null) { 387onProgressListener=null; 388} 389showLog("release"); 390} 391 392/** 393* 解码线程 394*/ 395private class DecodeRunnable implements Runnable{ 396 397@Override 398public void run() { 399while (!codeOver) { 400srcAudioFormatToPCM(); 401} 402} 403} 404 405/** 406* 编码线程 407*/ 408private class EncodeRunnable implements Runnable { 409 410@Override 411public void run() { 412long t=System.currentTimeMillis(); 413while (!codeOver || !chunkPCMDataContainer.isEmpty()) { 414dstAudioFormatFromPCM(); 415} 416if (onCompleteListener != null) { 417onCompleteListener.completed(); 418} 419showLog("size:"+fileTotalSize+" decodeSize:"+decodeSize+"time:"+(System.currentTimeMillis()-t)); 420} 421} 422 423 424/** 425* 转码完成回调接口 426*/ 427public interface OnCompleteListener{ 428void completed(); 429} 430 431/** 432* 转码进度监听器 433*/ 434public interface OnProgressListener{ 435void progress(); 436} 437 438/** 439* 设置转码完成监听器 440* @param onCompleteListener 441*/ 442public void setOnCompleteListener(OnCompleteListener onCompleteListener) { 443this.onCompleteListener=onCompleteListener; 444} 445 446public void setOnProgressListener(OnProgressListener onProgressListener) { 447this.onProgressListener = onProgressListener; 448} 449 450private void showLog(String msg) { 451Log.e("AudioCodec", msg); 452} 453 }
AudioCodec
6)调用
此类已经过封装,可通过下面的方法调用
String path=Environment.getExternalStorageDirectory().getAbsolutePath(); AudioCodec audioCodec=AudioCodec.newInstance(); audioCodec.setEncodeType(MediaFormat.MIMETYPE_AUDIO_MPEG); audioCodec.setIOPath(path + "/codec.aac", path + "/encode.mp3"); audioCodec.prepare(); audioCodec.startAsync(); audioCodec.setOnCompleteListener(new AudioCodec.OnCompleteListener() { @Override public void completed() { audioCodec.release(); } });
推荐阅读
- Androidandroid镜像翻转
- android 反编译和代码解读
- 原创android内存管理-hprof文件
- Android常用系统广播
- AndroidService的启动过程
- Android学习之Activity的使用
- Android流量统计TrafficStats类
- delphi 安卓程序如何读取外部配置文件
- Android系统的五种数据存储形式