博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
android MediaCodec 音频编解码的实现——转码
阅读量:6680 次
发布时间:2019-06-25

本文共 25644 字,大约阅读时间需要 85 分钟。

原文地址:http://blog.csdn.net/tinsanmr/article/details/51049179

从今天开始 每周不定期更新博客,把这一周在工作与学习中遇到的问题做个总结。俗话说:好记性不如写博客,善于总结的人才能走的更远。写博客这种利人利己的好处我就不一 一列举了,总之,谁做谁知道,哈哈。在文章中如果有什么问题或者错误,欢迎各位的讨论和指正。好了,步入正题,来看看我们今天要讲的MediaCodec

一、概述

由于项目的需要,需要将mp3文件转码为aac音频文件,起初打算移植FFmpeg到项目中,无奈FFmpeg过于庞大,项目中的音频转码只是一个辅助util,并不是主要功能。所以打算用MediaCodec来实现这一需求。网上关于MediaCodec的文章少的可怜,写demo的过程中踩到了无数的坑。不过,在这篇文章的指引下,终于在耳机中听到了那美妙的旋律,激动的我把这一首歌听了一星期,因为他出自我的手。哈哈,开个玩笑!在此对这片文章的原创表示感谢,这也是我决定写博客的原因之一,利人利己。

二、转码实现原理

本篇文章以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)初始化解码器

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      */ 4     private void initMediaDecode() { 5         try { 6             mediaExtractor=new MediaExtractor();//此类可分离视频文件的音轨和视频轨道 7             mediaExtractor.setDataSource(srcPath);//媒体文件的位置 8             for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {
//遍历媒体轨道 此处我们传入的是音频文件,所以也就只有一条轨道 9 MediaFormat format = mediaExtractor.getTrackFormat(i);10 String mime = format.getString(MediaFormat.KEY_MIME);11 if (mime.startsWith("audio")) {
//获取音频轨道12 // format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 200 * 1024);13 mediaExtractor.selectTrack(i);//选择此音频轨道14 mediaDecode = MediaCodec.createDecoderByType(mime);//创建Decode解码器15 mediaDecode.configure(format, null, null, 0);16 break;17 }18 }19 } catch (IOException e) {20 e.printStackTrace();21 }22 23 if (mediaDecode == null) {24 Log.e(TAG, "create mediaDecode failed");25 return;26 }27 mediaDecode.start();//启动MediaCodec ,等待传入数据28 decodeInputBuffers=mediaDecode.getInputBuffers();//MediaCodec在此ByteBuffer[]中获取输入数据29 decodeOutputBuffers=mediaDecode.getOutputBuffers();//MediaCodec将解码后的数据放到此ByteBuffer[]中 我们可以直接在这里面得到PCM数据30 decodeBufferInfo=new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息31 showLog("buffers:" + decodeInputBuffers.length);32 }

 

 

2)初始化编码器 

编码器的创建于解码器的类似,只不过解码器的MediaFormat直接在音频文件内获取就可以了,编码器的MediaFormat需要自己来创建

 

1 /** 2  * 初始化AAC编码器 3  */ 4 private void initAACMediaEncode() { 5     try { 6         MediaFormat encodeFormat = MediaFormat.createAudioFormat(encodeType, 44100, 2);//参数对应-> mime type、采样率、声道数 7         encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);//比特率 8         encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); 9         encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 100 * 1024);//作用于inputBuffer的大小10         mediaEncode = MediaCodec.createEncoderByType(encodeType);11         mediaEncode.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);12     } catch (IOException e) {13         e.printStackTrace();14     }15 16     if (mediaEncode == null) {17         Log.e(TAG, "create mediaEncode failed");18         return;19     }20     mediaEncode.start();21     encodeInputBuffers=mediaEncode.getInputBuffers();22     encodeOutputBuffers=mediaEncode.getOutputBuffers();23     encodeBufferInfo=new MediaCodec.BufferInfo();24 }25

 

3)解码的实现

 

1 /** 2      * 解码{
@link #srcPath}音频文件 得到PCM数据块 3 * @return 是否解码完所有数据 4 */ 5 private void srcAudioFormatToPCM() { 6 for (int i = 0; i < decodeInputBuffers.length-1; i++) { 7 int inputIndex = mediaDecode.dequeueInputBuffer(-1);//获取可用的inputBuffer -1代表一直等待,0表示不等待 建议-1,避免丢帧 8 if (inputIndex < 0) { 9 codeOver =true;10 return;11 }12 13 ByteBuffer inputBuffer = decodeInputBuffers[inputIndex];//拿到inputBuffer14 inputBuffer.clear();//清空之前传入inputBuffer内的数据15 int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);//MediaExtractor读取数据到inputBuffer中16 if (sampleSize <0) {
//小于0 代表所有数据已读取完成17 codeOver=true;18 }else {19 mediaDecode.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0);//通知MediaDecode解码刚刚传入的数据20 mediaExtractor.advance();//MediaExtractor移动到下一取样处21 decodeSize+=sampleSize;22 }23 }24 25 //获取解码得到的byte[]数据 参数BufferInfo上面已介绍 10000同样为等待时间 同上-1代表一直等待,0代表不等待。此处单位为微秒26 //此处建议不要填-1 有些时候并没有数据输出,那么他就会一直卡在这 等待27 int outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);28 29 // showLog("decodeOutIndex:" + outputIndex);30 ByteBuffer outputBuffer;31 byte[] chunkPCM;32 while (outputIndex >= 0) {
//每次解码完成的数据不一定能一次吐出 所以用while循环,保证解码器吐出所有数据33 outputBuffer = decodeOutputBuffers[outputIndex];//拿到用于存放PCM数据的Buffer34 chunkPCM = new byte[decodeBufferInfo.size];//BufferInfo内定义了此数据块的大小35 outputBuffer.get(chunkPCM);//将Buffer内的数据取出到字节数组中36 outputBuffer.clear();//数据取出后一定记得清空此Buffer MediaCodec是循环使用这些Buffer的,不清空下次会得到同样的数据37 putPCMData(chunkPCM);//自己定义的方法,供编码器所在的线程获取数据,下面会贴出代码38 mediaDecode.releaseOutputBuffer(outputIndex, false);//此操作一定要做,不然MediaCodec用完所有的Buffer后 将不能向外输出数据39 outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);//再次获取数据,如果没有数据输出则outputIndex=-1 循环结束40 }41 42 }

 

 

4)编码的实现

 

1 /** 2      * 编码PCM数据 得到{
@link #encodeType}格式的音频文件,并保存到{
@link #dstPath} 3 */ 4 private void dstAudioFormatFromPCM() { 5 6 int inputIndex; 7 ByteBuffer inputBuffer; 8 int outputIndex; 9 ByteBuffer outputBuffer;10 byte[] chunkAudio;11 int outBitSize;12 int outPacketSize;13 byte[] chunkPCM;14 15 // showLog("doEncode");16 for (int i = 0; i < encodeInputBuffers.length-1; i++) {17 chunkPCM=getPCMData();//获取解码器所在线程输出的数据 代码后边会贴上18 if (chunkPCM == null) {19 break;20 }21 inputIndex = mediaEncode.dequeueInputBuffer(-1);//同解码器22 inputBuffer = encodeInputBuffers[inputIndex];//同解码器23 inputBuffer.clear();//同解码器24 inputBuffer.limit(chunkPCM.length);25 inputBuffer.put(chunkPCM);//PCM数据填充给inputBuffer26 mediaEncode.queueInputBuffer(inputIndex, 0, chunkPCM.length, 0, 0);//通知编码器 编码27 }28 29 outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);//同解码器30 while (outputIndex >= 0) {
//同解码器31 32 outBitSize=encodeBufferInfo.size;33 outPacketSize=outBitSize+7;//7为ADTS头部的大小34 outputBuffer = encodeOutputBuffers[outputIndex];//拿到输出Buffer35 outputBuffer.position(encodeBufferInfo.offset);36 outputBuffer.limit(encodeBufferInfo.offset + outBitSize);37 chunkAudio = new byte[outPacketSize];38 addADTStoPacket(chunkAudio,outPacketSize);//添加ADTS 代码后面会贴上39 outputBuffer.get(chunkAudio, 7, outBitSize);//将编码得到的AAC数据 取出到byte[]中 偏移量offset=7 你懂得40 outputBuffer.position(encodeBufferInfo.offset);41 // showLog("outPacketSize:" + outPacketSize + " encodeOutBufferRemain:" + outputBuffer.remaining());42 try {43 bos.write(chunkAudio,0,chunkAudio.length);//BufferOutputStream 将文件保存到内存卡中 *.aac 44 } catch (IOException e) {45 e.printStackTrace();46 }47 48 mediaEncode.releaseOutputBuffer(outputIndex,false);49 outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);50 51 }52 53 }54 55 56 57 58 /**59 * 添加ADTS头60 * @param packet61 * @param packetLen62 */63 private void addADTStoPacket(byte[] packet, int packetLen) {64 int profile = 2; // AAC LC65 int freqIdx = 4; // 44.1KHz66 int chanCfg = 2; // CPE67 68 69 // fill in ADTS data70 packet[0] = (byte) 0xFF;71 packet[1] = (byte) 0xF9;72 packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));73 packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));74 packet[4] = (byte) ((packetLen & 0x7FF) >> 3);75 packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);76 packet[6] = (byte) 0xFC;77 }78

 

5)完整代码

1 package com.example.tinsan.mediaparser;  2   3   4         import android.media.MediaCodec;  5         import android.media.MediaCodecInfo;  6         import android.media.MediaExtractor;  7         import android.media.MediaFormat;  8         import android.util.Log;  9  10         import java.io.BufferedInputStream; 11         import java.io.BufferedOutputStream; 12         import java.io.File; 13         import java.io.FileInputStream; 14         import java.io.FileNotFoundException; 15         import java.io.FileOutputStream; 16         import java.io.IOException; 17         import java.nio.ByteBuffer; 18         import java.util.ArrayList; 19  20 /** 21  * Created by senshan_wang on 2016/3/31. 22  */ 23 public class AudioCodec { 24  25     private static final String TAG = "AudioCodec"; 26     private String encodeType; 27     private String srcPath; 28     private String dstPath; 29     private MediaCodec mediaDecode; 30     private MediaCodec mediaEncode; 31     private MediaExtractor mediaExtractor; 32     private ByteBuffer[] decodeInputBuffers; 33     private ByteBuffer[] decodeOutputBuffers; 34     private ByteBuffer[] encodeInputBuffers; 35     private ByteBuffer[] encodeOutputBuffers; 36     private MediaCodec.BufferInfo decodeBufferInfo; 37     private MediaCodec.BufferInfo encodeBufferInfo; 38     private FileOutputStream fos; 39     private BufferedOutputStream bos; 40     private FileInputStream fis; 41     private BufferedInputStream bis; 42     private ArrayList
chunkPCMDataContainer;//PCM数据块容器 43 private OnCompleteListener onCompleteListener; 44 private OnProgressListener onProgressListener; 45 private long fileTotalSize; 46 private long decodeSize; 47 48 49 public static AudioCodec newInstance() { 50 return new AudioCodec(); 51 } 52 53 /** 54 * 设置编码器类型 55 * @param encodeType 56 */ 57 public void setEncodeType(String encodeType) { 58 this.encodeType=encodeType; 59 } 60 61 /** 62 * 设置输入输出文件位置 63 * @param srcPath 64 * @param dstPath 65 */ 66 public void setIOPath(String srcPath, String dstPath) { 67 this.srcPath=srcPath; 68 this.dstPath=dstPath; 69 } 70 71 /** 72 * 此类已经过封装 73 * 调用prepare方法 会初始化Decode 、Encode 、输入输出流 等一些列操作 74 */ 75 public void prepare() { 76 77 if (encodeType == null) { 78 throw new IllegalArgumentException("encodeType can't be null"); 79 } 80 81 if (srcPath == null) { 82 throw new IllegalArgumentException("srcPath can't be null"); 83 } 84 85 if (dstPath == null) { 86 throw new IllegalArgumentException("dstPath can't be null"); 87 } 88 89 try { 90 fos = new FileOutputStream(new File(dstPath)); 91 bos = new BufferedOutputStream(fos,200*1024); 92 File file = new File(srcPath); 93 fileTotalSize=file.length(); 94 } catch (IOException e) { 95 e.printStackTrace(); 96 } 97 chunkPCMDataContainer= new ArrayList<>(); 98 initMediaDecode();//解码器 99 100 if (encodeType == MediaFormat.MIMETYPE_AUDIO_AAC) {101 initAACMediaEncode();//AAC编码器102 }else if (encodeType == MediaFormat.MIMETYPE_AUDIO_MPEG) {103 initMPEGMediaEncode();//mp3编码器104 }105 106 }107 108 /**109 * 初始化解码器110 */111 private void initMediaDecode() {112 try {113 mediaExtractor=new MediaExtractor();//此类可分离视频文件的音轨和视频轨道114 mediaExtractor.setDataSource(srcPath);//媒体文件的位置115 for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {
//遍历媒体轨道 此处我们传入的是音频文件,所以也就只有一条轨道116 MediaFormat format = mediaExtractor.getTrackFormat(i);117 String mime = format.getString(MediaFormat.KEY_MIME);118 if (mime.startsWith("audio")) {
//获取音频轨道119 // format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 200 * 1024);120 mediaExtractor.selectTrack(i);//选择此音频轨道121 mediaDecode = MediaCodec.createDecoderByType(mime);//创建Decode解码器122 mediaDecode.configure(format, null, null, 0);123 break;124 }125 }126 } catch (IOException e) {127 e.printStackTrace();128 }129 130 if (mediaDecode == null) {131 Log.e(TAG, "create mediaDecode failed");132 return;133 }134 mediaDecode.start();//启动MediaCodec ,等待传入数据135 decodeInputBuffers=mediaDecode.getInputBuffers();//MediaCodec在此ByteBuffer[]中获取输入数据136 decodeOutputBuffers=mediaDecode.getOutputBuffers();//MediaCodec将解码后的数据放到此ByteBuffer[]中 我们可以直接在这里面得到PCM数据137 decodeBufferInfo=new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息138 showLog("buffers:" + decodeInputBuffers.length);139 }140 141 142 /**143 * 初始化AAC编码器144 */145 private void initAACMediaEncode() {146 try {147 MediaFormat encodeFormat = MediaFormat.createAudioFormat(encodeType, 44100, 2);//参数对应-> mime type、采样率、声道数148 encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);//比特率149 encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);150 encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 100 * 1024);151 mediaEncode = MediaCodec.createEncoderByType(encodeType);152 mediaEncode.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);153 } catch (IOException e) {154 e.printStackTrace();155 }156 157 if (mediaEncode == null) {158 Log.e(TAG, "create mediaEncode failed");159 return;160 }161 mediaEncode.start();162 encodeInputBuffers=mediaEncode.getInputBuffers();163 encodeOutputBuffers=mediaEncode.getOutputBuffers();164 encodeBufferInfo=new MediaCodec.BufferInfo();165 }166 167 /**168 * 初始化MPEG编码器169 */170 private void initMPEGMediaEncode() {171 172 }173 174 private boolean codeOver = false;175 /**176 * 开始转码177 * 音频数据{
@link #srcPath}先解码成PCM PCM数据在编码成想要得到的{
@link #encodeType}音频格式178 * mp3->PCM->aac179 */180 public void startAsync() {181 showLog("start");182 183 new Thread(new DecodeRunnable()).start();184 new Thread(new EncodeRunnable()).start();185 186 }187 188 /**189 * 将PCM数据存入{
@link #chunkPCMDataContainer}190 * @param pcmChunk PCM数据块191 */192 private void putPCMData(byte[] pcmChunk) {193 synchronized (AudioCodec.class) {
//记得加锁194 chunkPCMDataContainer.add(pcmChunk);195 }196 }197 198 /**199 * 在Container中{
@link #chunkPCMDataContainer}取出PCM数据200 * @return PCM数据块201 */202 private byte[] getPCMData() {203 synchronized (AudioCodec.class) {
//记得加锁204 showLog("getPCM:"+chunkPCMDataContainer.size());205 if (chunkPCMDataContainer.isEmpty()) {206 return null;207 }208 209 byte[] pcmChunk = chunkPCMDataContainer.get(0);//每次取出index 0 的数据210 chunkPCMDataContainer.remove(pcmChunk);//取出后将此数据remove掉 既能保证PCM数据块的取出顺序 又能及时释放内存211 return pcmChunk;212 }213 }214 215 216 /**217 * 解码{
@link #srcPath}音频文件 得到PCM数据块218 * @return 是否解码完所有数据219 */220 private void srcAudioFormatToPCM() {221 for (int i = 0; i < decodeInputBuffers.length-1; i++) {222 int inputIndex = mediaDecode.dequeueInputBuffer(-1);//获取可用的inputBuffer -1代表一直等待,0表示不等待 建议-1,避免丢帧223 if (inputIndex < 0) {224 codeOver =true;225 return;226 }227 228 ByteBuffer inputBuffer = decodeInputBuffers[inputIndex];//拿到inputBuffer229 inputBuffer.clear();//清空之前传入inputBuffer内的数据230 int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);//MediaExtractor读取数据到inputBuffer中231 if (sampleSize <0) {
//小于0 代表所有数据已读取完成232 codeOver=true;233 }else {234 mediaDecode.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0);//通知MediaDecode解码刚刚传入的数据235 mediaExtractor.advance();//MediaExtractor移动到下一取样处236 decodeSize+=sampleSize;237 }238 }239 240 //获取解码得到的byte[]数据 参数BufferInfo上面已介绍 10000同样为等待时间 同上-1代表一直等待,0代表不等待。此处单位为微秒241 //此处建议不要填-1 有些时候并没有数据输出,那么他就会一直卡在这 等待242 int outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);243 244 // showLog("decodeOutIndex:" + outputIndex);245 ByteBuffer outputBuffer;246 byte[] chunkPCM;247 while (outputIndex >= 0) {
//每次解码完成的数据不一定能一次吐出 所以用while循环,保证解码器吐出所有数据248 outputBuffer = decodeOutputBuffers[outputIndex];//拿到用于存放PCM数据的Buffer249 chunkPCM = new byte[decodeBufferInfo.size];//BufferInfo内定义了此数据块的大小250 outputBuffer.get(chunkPCM);//将Buffer内的数据取出到字节数组中251 outputBuffer.clear();//数据取出后一定记得清空此Buffer MediaCodec是循环使用这些Buffer的,不清空下次会得到同样的数据252 putPCMData(chunkPCM);//自己定义的方法,供编码器所在的线程获取数据,下面会贴出代码253 mediaDecode.releaseOutputBuffer(outputIndex, false);//此操作一定要做,不然MediaCodec用完所有的Buffer后 将不能向外输出数据254 outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);//再次获取数据,如果没有数据输出则outputIndex=-1 循环结束255 }256 257 }258 259 /**260 * 编码PCM数据 得到{
@link #encodeType}格式的音频文件,并保存到{
@link #dstPath}261 */262 private void dstAudioFormatFromPCM() {263 264 int inputIndex;265 ByteBuffer inputBuffer;266 int outputIndex;267 ByteBuffer outputBuffer;268 byte[] chunkAudio;269 int outBitSize;270 int outPacketSize;271 byte[] chunkPCM;272 273 // showLog("doEncode");274 for (int i = 0; i < encodeInputBuffers.length-1; i++) {275 chunkPCM=getPCMData();//获取解码器所在线程输出的数据 代码后边会贴上276 if (chunkPCM == null) {277 break;278 }279 inputIndex = mediaEncode.dequeueInputBuffer(-1);//同解码器280 inputBuffer = encodeInputBuffers[inputIndex];//同解码器281 inputBuffer.clear();//同解码器282 inputBuffer.limit(chunkPCM.length);283 inputBuffer.put(chunkPCM);//PCM数据填充给inputBuffer284 mediaEncode.queueInputBuffer(inputIndex, 0, chunkPCM.length, 0, 0);//通知编码器 编码285 }286 287 outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);//同解码器288 while (outputIndex >= 0) {
//同解码器289 290 outBitSize=encodeBufferInfo.size;291 outPacketSize=outBitSize+7;//7为ADTS头部的大小292 outputBuffer = encodeOutputBuffers[outputIndex];//拿到输出Buffer293 outputBuffer.position(encodeBufferInfo.offset);294 outputBuffer.limit(encodeBufferInfo.offset + outBitSize);295 chunkAudio = new byte[outPacketSize];296 addADTStoPacket(chunkAudio,outPacketSize);//添加ADTS 代码后面会贴上297 outputBuffer.get(chunkAudio, 7, outBitSize);//将编码得到的AAC数据 取出到byte[]中 偏移量offset=7 你懂得298 outputBuffer.position(encodeBufferInfo.offset);299 // showLog("outPacketSize:" + outPacketSize + " encodeOutBufferRemain:" + outputBuffer.remaining());300 try {301 bos.write(chunkAudio,0,chunkAudio.length);//BufferOutputStream 将文件保存到内存卡中 *.aac302 } catch (IOException e) {303 e.printStackTrace();304 }305 306 mediaEncode.releaseOutputBuffer(outputIndex,false);307 outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);308 309 }310 }311 312 /**313 * 添加ADTS头314 * @param packet315 * @param packetLen316 */317 private void addADTStoPacket(byte[] packet, int packetLen) {318 int profile = 2; // AAC LC319 int freqIdx = 4; // 44.1KHz320 int chanCfg = 2; // CPE321 322 323 // fill in ADTS data324 packet[0] = (byte) 0xFF;325 packet[1] = (byte) 0xF9;326 packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));327 packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));328 packet[4] = (byte) ((packetLen & 0x7FF) >> 3);329 packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);330 packet[6] = (byte) 0xFC;331 }332 333 /**334 * 释放资源335 */336 public void release() {337 try {338 if (bos != null) {339 bos.flush();340 }341 } catch (IOException e) {342 e.printStackTrace();343 }finally {344 if (bos != null) {345 try {346 bos.close();347 } catch (IOException e) {348 e.printStackTrace();349 }finally {350 bos=null;351 }352 }353 }354 355 try {356 if (fos != null) {357 fos.close();358 }359 } catch (IOException e) {360 e.printStackTrace();361 }finally {362 fos=null;363 }364 365 if (mediaEncode != null) {366 mediaEncode.stop();367 mediaEncode.release();368 mediaEncode=null;369 }370 371 if (mediaDecode != null) {372 mediaDecode.stop();373 mediaDecode.release();374 mediaDecode=null;375 }376 377 if (mediaExtractor != null) {378 mediaExtractor.release();379 mediaExtractor=null;380 }381 382 if (onCompleteListener != null) {383 onCompleteListener=null;384 }385 386 if (onProgressListener != null) {387 onProgressListener=null;388 }389 showLog("release");390 }391 392 /**393 * 解码线程394 */395 private class DecodeRunnable implements Runnable{396 397 @Override398 public void run() {399 while (!codeOver) {400 srcAudioFormatToPCM();401 }402 }403 }404 405 /**406 * 编码线程407 */408 private class EncodeRunnable implements Runnable {409 410 @Override411 public void run() {412 long t=System.currentTimeMillis();413 while (!codeOver || !chunkPCMDataContainer.isEmpty()) {414 dstAudioFormatFromPCM();415 }416 if (onCompleteListener != null) {417 onCompleteListener.completed();418 }419 showLog("size:"+fileTotalSize+" decodeSize:"+decodeSize+"time:"+(System.currentTimeMillis()-t));420 }421 }422 423 424 /**425 * 转码完成回调接口426 */427 public interface OnCompleteListener{428 void completed();429 }430 431 /**432 * 转码进度监听器433 */434 public interface OnProgressListener{435 void progress();436 }437 438 /**439 * 设置转码完成监听器440 * @param onCompleteListener441 */442 public void setOnCompleteListener(OnCompleteListener onCompleteListener) {443 this.onCompleteListener=onCompleteListener;444 }445 446 public void setOnProgressListener(OnProgressListener onProgressListener) {447 this.onProgressListener = onProgressListener;448 }449 450 private void showLog(String msg) {451 Log.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();    }});

 

转载于:https://www.cnblogs.com/Sharley/p/5964490.html

你可能感兴趣的文章
C/S和B/S结构区别整理
查看>>
python基础===理解Class的一道题
查看>>
Bootstrap3 概述
查看>>
Django中的APP
查看>>
Adobe:彻底解决Firefox与Flash插件卡顿
查看>>
source insight 使用说明
查看>>
Simplify Path
查看>>
JSP放入Jar包支持
查看>>
依赖注入Bean属性
查看>>
Android中的IPC方式
查看>>
计算机网络基础知识(待补充)
查看>>
工作5年半了,最近准备做一些工作的小结了
查看>>
zabbix监控tengine upstream状态
查看>>
新手教程
查看>>
mysql-binlog日志恢复数据库
查看>>
python之使用单元测试框架unittest执行自动化测试
查看>>
java反射学习笔记
查看>>
day10-多进程的基本语法
查看>>
凡客和锤子
查看>>
设计模式(5)--单例模式
查看>>