原文地址: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)初始化解碼器
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];//拿到inputBuffer 14 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數據的Buffer 34 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數據填充給inputBuffer 26 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];//拿到輸出Buffer 35 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 packet 61 * @param packetLen 62 */ 63 private void addADTStoPacket(byte[] packet, int packetLen) { 64 int profile = 2; // AAC LC 65 int freqIdx = 4; // 44.1KHz 66 int chanCfg = 2; // CPE 67 68 69 // fill in ADTS data 70 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<byte[]> 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->aac 179 */ 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];//拿到inputBuffer 229 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數據的Buffer 249 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數據填充給inputBuffer 284 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];//拿到輸出Buffer 293 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 將文件保存到內存卡中 *.aac 302 } 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 packet 315 * @param packetLen 316 */ 317 private void addADTStoPacket(byte[] packet, int packetLen) { 318 int profile = 2; // AAC LC 319 int freqIdx = 4; // 44.1KHz 320 int chanCfg = 2; // CPE 321 322 323 // fill in ADTS data 324 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 @Override 398 public void run() { 399 while (!codeOver) { 400 srcAudioFormatToPCM(); 401 } 402 } 403 } 404 405 /** 406 * 編碼線程 407 */ 408 private class EncodeRunnable implements Runnable { 409 410 @Override 411 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 onCompleteListener 441 */ 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 }
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(); } });