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)初始化解碼器

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 }
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();
    }
});

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM