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

 

你可能感兴趣的:(android MediaCodec 音频编解码的实现——转码)