从今天开始 每周不定期更新博客,把这一周在工作与学习中遇到的问题做个总结。俗话说:好记性不如写博客,善于总结的人才能走的更远。写博客这种利人利己的好处我就不一 一列举了,总之,谁做谁知道,哈哈。在文章中如果有什么问题或者错误,欢迎各位的讨论和指正。好了,步入正题,来看看我们今天要讲的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其它同理
- /**
- * 初始化解码器
- */
- private void initMediaDecode() {
- try {
- mediaExtractor=new MediaExtractor();//此类可分离视频文件的音轨和视频轨道
- mediaExtractor.setDataSource(srcPath);//媒体文件的位置
- for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {//遍历媒体轨道 此处我们传入的是音频文件,所以也就只有一条轨道
- MediaFormat format = mediaExtractor.getTrackFormat(i);
- String mime = format.getString(MediaFormat.KEY_MIME);
- if (mime.startsWith("audio")) {//获取音频轨道
- // format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 200 * 1024);
- mediaExtractor.selectTrack(i);//选择此音频轨道
- mediaDecode = MediaCodec.createDecoderByType(mime);//创建Decode解码器
- mediaDecode.configure(format, null, null, 0);
- break;
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- if (mediaDecode == null) {
- Log.e(TAG, "create mediaDecode failed");
- return;
- }
- mediaDecode.start();//启动MediaCodec ,等待传入数据
- decodeInputBuffers=mediaDecode.getInputBuffers();//MediaCodec在此ByteBuffer[]中获取输入数据
- decodeOutputBuffers=mediaDecode.getOutputBuffers();//MediaCodec将解码后的数据放到此ByteBuffer[]中 我们可以直接在这里面得到PCM数据
- decodeBufferInfo=new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息
- showLog("buffers:" + decodeInputBuffers.length);
- }
2)初始化编码器
编码器的创建于解码器的类似,只不过解码器的MediaFormat直接在音频文件内获取就可以了,编码器的MediaFormat需要自己来创建
- /**
- * 初始化AAC编码器
- */
- private void initAACMediaEncode() {
- try {
- MediaFormat encodeFormat = MediaFormat.createAudioFormat(encodeType, 44100, 2);//参数对应-> mime type、采样率、声道数
- encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);//比特率
- encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
- encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 100 * 1024);//作用于inputBuffer的大小
- mediaEncode = MediaCodec.createEncoderByType(encodeType);
- mediaEncode.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
- } catch (IOException e) {
- e.printStackTrace();
- }
- if (mediaEncode == null) {
- Log.e(TAG, "create mediaEncode failed");
- return;
- }
- mediaEncode.start();
- encodeInputBuffers=mediaEncode.getInputBuffers();
- encodeOutputBuffers=mediaEncode.getOutputBuffers();
- encodeBufferInfo=new MediaCodec.BufferInfo();
- }
3)解码的实现
- /**
- * 解码{@link #srcPath}音频文件 得到PCM数据块
- * @return 是否解码完所有数据
- */
- private void srcAudioFormatToPCM() {
- for (int i = 0; i < decodeInputBuffers.length-1; i++) {
- int inputIndex = mediaDecode.dequeueInputBuffer(-1);//获取可用的inputBuffer -1代表一直等待,0表示不等待 建议-1,避免丢帧
- if (inputIndex < 0) {
- codeOver =true;
- return;
- }
- ByteBuffer inputBuffer = decodeInputBuffers[inputIndex];//拿到inputBuffer
- inputBuffer.clear();//清空之前传入inputBuffer内的数据
- int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);//MediaExtractor读取数据到inputBuffer中
- if (sampleSize <0) {//小于0 代表所有数据已读取完成
- codeOver=true;
- }else {
- mediaDecode.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0);//通知MediaDecode解码刚刚传入的数据
- mediaExtractor.advance();//MediaExtractor移动到下一取样处
- decodeSize+=sampleSize;
- }
- }
- //获取解码得到的byte[]数据 参数BufferInfo上面已介绍 10000同样为等待时间 同上-1代表一直等待,0代表不等待。此处单位为微秒
- //此处建议不要填-1 有些时候并没有数据输出,那么他就会一直卡在这 等待
- int outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);
- // showLog("decodeOutIndex:" + outputIndex);
- ByteBuffer outputBuffer;
- byte[] chunkPCM;
- while (outputIndex >= 0) {//每次解码完成的数据不一定能一次吐出 所以用while循环,保证解码器吐出所有数据
- outputBuffer = decodeOutputBuffers[outputIndex];//拿到用于存放PCM数据的Buffer
- chunkPCM = new byte[decodeBufferInfo.size];//BufferInfo内定义了此数据块的大小
- outputBuffer.get(chunkPCM);//将Buffer内的数据取出到字节数组中
- outputBuffer.clear();//数据取出后一定记得清空此Buffer MediaCodec是循环使用这些Buffer的,不清空下次会得到同样的数据
- putPCMData(chunkPCM);//自己定义的方法,供编码器所在的线程获取数据,下面会贴出代码
- mediaDecode.releaseOutputBuffer(outputIndex, false);//此操作一定要做,不然MediaCodec用完所有的Buffer后 将不能向外输出数据
- outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);//再次获取数据,如果没有数据输出则outputIndex=-1 循环结束
- }
- }
4)编码的实现
- /**
- * 编码PCM数据 得到{@link #encodeType}格式的音频文件,并保存到{@link #dstPath}
- */
- private void dstAudioFormatFromPCM() {
- int inputIndex;
- ByteBuffer inputBuffer;
- int outputIndex;
- ByteBuffer outputBuffer;
- byte[] chunkAudio;
- int outBitSize;
- int outPacketSize;
- byte[] chunkPCM;
- // showLog("doEncode");
- for (int i = 0; i < encodeInputBuffers.length-1; i++) {
- chunkPCM=getPCMData();//获取解码器所在线程输出的数据 代码后边会贴上
- if (chunkPCM == null) {
- break;
- }
- inputIndex = mediaEncode.dequeueInputBuffer(-1);//同解码器
- inputBuffer = encodeInputBuffers[inputIndex];//同解码器
- inputBuffer.clear();//同解码器
- inputBuffer.limit(chunkPCM.length);
- inputBuffer.put(chunkPCM);//PCM数据填充给inputBuffer
- mediaEncode.queueInputBuffer(inputIndex, 0, chunkPCM.length, 0, 0);//通知编码器 编码
- }
- outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);//同解码器
- while (outputIndex >= 0) {//同解码器
- outBitSize=encodeBufferInfo.size;
- outPacketSize=outBitSize+7;//7为ADTS头部的大小
- outputBuffer = encodeOutputBuffers[outputIndex];//拿到输出Buffer
- outputBuffer.position(encodeBufferInfo.offset);
- outputBuffer.limit(encodeBufferInfo.offset + outBitSize);
- chunkAudio = new byte[outPacketSize];
- addADTStoPacket(chunkAudio,outPacketSize);//添加ADTS 代码后面会贴上
- outputBuffer.get(chunkAudio, 7, outBitSize);//将编码得到的AAC数据 取出到byte[]中 偏移量offset=7 你懂得
- outputBuffer.position(encodeBufferInfo.offset);
- // showLog("outPacketSize:" + outPacketSize + " encodeOutBufferRemain:" + outputBuffer.remaining());
- try {
- bos.write(chunkAudio,0,chunkAudio.length);//BufferOutputStream 将文件保存到内存卡中 *.aac
- } catch (IOException e) {
- e.printStackTrace();
- }
- mediaEncode.releaseOutputBuffer(outputIndex,false);
- outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);
- }
- }
- /**
- * 添加ADTS头
- * @param packet
- * @param packetLen
- */
- private void addADTStoPacket(byte[] packet, int packetLen) {
- int profile = 2; // AAC LC
- int freqIdx = 4; // 44.1KHz
- int chanCfg = 2; // CPE
- // fill in ADTS data
- packet[0] = (byte) 0xFF;
- packet[1] = (byte) 0xF9;
- packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
- packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
- packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
- packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
- packet[6] = (byte) 0xFC;
- }
5)完整代码
- package com.example.tinsan.mediaparser;
- import android.media.MediaCodec;
- import android.media.MediaCodecInfo;
- import android.media.MediaExtractor;
- import android.media.MediaFormat;
- import android.util.Log;
- import java.io.BufferedInputStream;
- import java.io.BufferedOutputStream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.nio.ByteBuffer;
- import java.util.ArrayList;
- /**
- * Created by senshan_wang on 2016/3/31.
- */
- public class AudioCodec {
- private static final String TAG = "AudioCodec";
- private String encodeType;
- private String srcPath;
- private String dstPath;
- private MediaCodec mediaDecode;
- private MediaCodec mediaEncode;
- private MediaExtractor mediaExtractor;
- private ByteBuffer[] decodeInputBuffers;
- private ByteBuffer[] decodeOutputBuffers;
- private ByteBuffer[] encodeInputBuffers;
- private ByteBuffer[] encodeOutputBuffers;
- private MediaCodec.BufferInfo decodeBufferInfo;
- private MediaCodec.BufferInfo encodeBufferInfo;
- private FileOutputStream fos;
- private BufferedOutputStream bos;
- private FileInputStream fis;
- private BufferedInputStream bis;
- private ArrayList<byte[]> chunkPCMDataContainer;//PCM数据块容器
- private OnCompleteListener onCompleteListener;
- private OnProgressListener onProgressListener;
- private long fileTotalSize;
- private long decodeSize;
- public static AudioCodec newInstance() {
- return new AudioCodec();
- }
- /**
- * 设置编码器类型
- * @param encodeType
- */
- public void setEncodeType(String encodeType) {
- this.encodeType=encodeType;
- }
- /**
- * 设置输入输出文件位置
- * @param srcPath
- * @param dstPath
- */
- public void setIOPath(String srcPath, String dstPath) {
- this.srcPath=srcPath;
- this.dstPath=dstPath;
- }
- /**
- * 此类已经过封装
- * 调用prepare方法 会初始化Decode 、Encode 、输入输出流 等一些列操作
- */
- public void prepare() {
- if (encodeType == null) {
- throw new IllegalArgumentException("encodeType can't be null");
- }
- if (srcPath == null) {
- throw new IllegalArgumentException("srcPath can't be null");
- }
- if (dstPath == null) {
- throw new IllegalArgumentException("dstPath can't be null");
- }
- try {
- fos = new FileOutputStream(new File(dstPath));
- bos = new BufferedOutputStream(fos,200*1024);
- File file = new File(srcPath);
- fileTotalSize=file.length();
- } catch (IOException e) {
- e.printStackTrace();
- }
- chunkPCMDataContainer= new ArrayList<>();
- initMediaDecode();//解码器
- if (encodeType == MediaFormat.MIMETYPE_AUDIO_AAC) {
- initAACMediaEncode();//AAC编码器
- }else if (encodeType == MediaFormat.MIMETYPE_AUDIO_MPEG) {
- initMPEGMediaEncode();//mp3编码器
- }
- }
- /**
- * 初始化解码器
- */
- private void initMediaDecode() {
- try {
- mediaExtractor=new MediaExtractor();//此类可分离视频文件的音轨和视频轨道
- mediaExtractor.setDataSource(srcPath);//媒体文件的位置
- for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {//遍历媒体轨道 此处我们传入的是音频文件,所以也就只有一条轨道
- MediaFormat format = mediaExtractor.getTrackFormat(i);
- String mime = format.getString(MediaFormat.KEY_MIME);
- if (mime.startsWith("audio")) {//获取音频轨道
- // format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 200 * 1024);
- mediaExtractor.selectTrack(i);//选择此音频轨道
- mediaDecode = MediaCodec.createDecoderByType(mime);//创建Decode解码器
- mediaDecode.configure(format, null, null, 0);
- break;
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- if (mediaDecode == null) {
- Log.e(TAG, "create mediaDecode failed");
- return;
- }
- mediaDecode.start();//启动MediaCodec ,等待传入数据
- decodeInputBuffers=mediaDecode.getInputBuffers();//MediaCodec在此ByteBuffer[]中获取输入数据
- decodeOutputBuffers=mediaDecode.getOutputBuffers();//MediaCodec将解码后的数据放到此ByteBuffer[]中 我们可以直接在这里面得到PCM数据
- decodeBufferInfo=new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息
- showLog("buffers:" + decodeInputBuffers.length);
- }
- /**
- * 初始化AAC编码器
- */
- private void initAACMediaEncode() {
- try {
- MediaFormat encodeFormat = MediaFormat.createAudioFormat(encodeType, 44100, 2);//参数对应-> mime type、采样率、声道数
- encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);//比特率
- encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
- encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 100 * 1024);
- mediaEncode = MediaCodec.createEncoderByType(encodeType);
- mediaEncode.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
- } catch (IOException e) {
- e.printStackTrace();
- }
- if (mediaEncode == null) {
- Log.e(TAG, "create mediaEncode failed");
- return;
- }
- mediaEncode.start();
- encodeInputBuffers=mediaEncode.getInputBuffers();
- encodeOutputBuffers=mediaEncode.getOutputBuffers();
- encodeBufferInfo=new MediaCodec.BufferInfo();
- }
- /**
- * 初始化MPEG编码器
- */
- private void initMPEGMediaEncode() {
- }
- private boolean codeOver = false;
- /**
- * 开始转码
- * 音频数据{@link #srcPath}先解码成PCM PCM数据在编码成想要得到的{@link #encodeType}音频格式
- * mp3->PCM->aac
- */
- public void startAsync() {
- showLog("start");
- new Thread(new DecodeRunnable()).start();
- new Thread(new EncodeRunnable()).start();
- }
- /**
- * 将PCM数据存入{@link #chunkPCMDataContainer}
- * @param pcmChunk PCM数据块
- */
- private void putPCMData(byte[] pcmChunk) {
- synchronized (AudioCodec.class) {//记得加锁
- chunkPCMDataContainer.add(pcmChunk);
- }
- }
- /**
- * 在Container中{@link #chunkPCMDataContainer}取出PCM数据
- * @return PCM数据块
- */
- private byte[] getPCMData() {
- synchronized (AudioCodec.class) {//记得加锁
- showLog("getPCM:"+chunkPCMDataContainer.size());
- if (chunkPCMDataContainer.isEmpty()) {
- return null;
- }
- byte[] pcmChunk = chunkPCMDataContainer.get(0);//每次取出index 0 的数据
- chunkPCMDataContainer.remove(pcmChunk);//取出后将此数据remove掉 既能保证PCM数据块的取出顺序 又能及时释放内存
- return pcmChunk;
- }
- }
- /**
- * 解码{@link #srcPath}音频文件 得到PCM数据块
- * @return 是否解码完所有数据
- */
- private void srcAudioFormatToPCM() {
- for (int i = 0; i < decodeInputBuffers.length-1; i++) {
- int inputIndex = mediaDecode.dequeueInputBuffer(-1);//获取可用的inputBuffer -1代表一直等待,0表示不等待 建议-1,避免丢帧
- if (inputIndex < 0) {
- codeOver =true;
- return;
- }
- ByteBuffer inputBuffer = decodeInputBuffers[inputIndex];//拿到inputBuffer
- inputBuffer.clear();//清空之前传入inputBuffer内的数据
- int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);//MediaExtractor读取数据到inputBuffer中
- if (sampleSize <0) {//小于0 代表所有数据已读取完成
- codeOver=true;
- }else {
- mediaDecode.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0);//通知MediaDecode解码刚刚传入的数据
- mediaExtractor.advance();//MediaExtractor移动到下一取样处
- decodeSize+=sampleSize;
- }
- }
- //获取解码得到的byte[]数据 参数BufferInfo上面已介绍 10000同样为等待时间 同上-1代表一直等待,0代表不等待。此处单位为微秒
- //此处建议不要填-1 有些时候并没有数据输出,那么他就会一直卡在这 等待
- int outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);
- // showLog("decodeOutIndex:" + outputIndex);
- ByteBuffer outputBuffer;
- byte[] chunkPCM;
- while (outputIndex >= 0) {//每次解码完成的数据不一定能一次吐出 所以用while循环,保证解码器吐出所有数据
- outputBuffer = decodeOutputBuffers[outputIndex];//拿到用于存放PCM数据的Buffer
- chunkPCM = new byte[decodeBufferInfo.size];//BufferInfo内定义了此数据块的大小
- outputBuffer.get(chunkPCM);//将Buffer内的数据取出到字节数组中
- outputBuffer.clear();//数据取出后一定记得清空此Buffer MediaCodec是循环使用这些Buffer的,不清空下次会得到同样的数据
- putPCMData(chunkPCM);//自己定义的方法,供编码器所在的线程获取数据,下面会贴出代码
- mediaDecode.releaseOutputBuffer(outputIndex, false);//此操作一定要做,不然MediaCodec用完所有的Buffer后 将不能向外输出数据
- outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);//再次获取数据,如果没有数据输出则outputIndex=-1 循环结束
- }
- }
- /**
- * 编码PCM数据 得到{@link #encodeType}格式的音频文件,并保存到{@link #dstPath}
- */
- private void dstAudioFormatFromPCM() {
- int inputIndex;
- ByteBuffer inputBuffer;
- int outputIndex;
- ByteBuffer outputBuffer;
- byte[] chunkAudio;
- int outBitSize;
- int outPacketSize;
- byte[] chunkPCM;
- // showLog("doEncode");
- for (int i = 0; i < encodeInputBuffers.length-1; i++) {
- chunkPCM=getPCMData();//获取解码器所在线程输出的数据 代码后边会贴上
- if (chunkPCM == null) {
- break;
- }
- inputIndex = mediaEncode.dequeueInputBuffer(-1);//同解码器
- inputBuffer = encodeInputBuffers[inputIndex];//同解码器
- inputBuffer.clear();//同解码器
- inputBuffer.limit(chunkPCM.length);
- inputBuffer.put(chunkPCM);//PCM数据填充给inputBuffer
- mediaEncode.queueInputBuffer(inputIndex, 0, chunkPCM.length, 0, 0);//通知编码器 编码
- }
- outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);//同解码器
- while (outputIndex >= 0) {//同解码器
- outBitSize=encodeBufferInfo.size;
- outPacketSize=outBitSize+7;//7为ADTS头部的大小
- outputBuffer = encodeOutputBuffers[outputIndex];//拿到输出Buffer
- outputBuffer.position(encodeBufferInfo.offset);
- outputBuffer.limit(encodeBufferInfo.offset + outBitSize);
- chunkAudio = new byte[outPacketSize];
- addADTStoPacket(chunkAudio,outPacketSize);//添加ADTS 代码后面会贴上
- outputBuffer.get(chunkAudio, 7, outBitSize);//将编码得到的AAC数据 取出到byte[]中 偏移量offset=7 你懂得
- outputBuffer.position(encodeBufferInfo.offset);
- // showLog("outPacketSize:" + outPacketSize + " encodeOutBufferRemain:" + outputBuffer.remaining());
- try {
- bos.write(chunkAudio,0,chunkAudio.length);//BufferOutputStream 将文件保存到内存卡中 *.aac
- } catch (IOException e) {
- e.printStackTrace();
- }
- mediaEncode.releaseOutputBuffer(outputIndex,false);
- outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);
- }
- }
- /**
- * 添加ADTS头
- * @param packet
- * @param packetLen
- */
- private void addADTStoPacket(byte[] packet, int packetLen) {
- int profile = 2; // AAC LC
- int freqIdx = 4; // 44.1KHz
- int chanCfg = 2; // CPE
- // fill in ADTS data
- packet[0] = (byte) 0xFF;
- packet[1] = (byte) 0xF9;
- packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
- packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
- packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
- packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
- packet[6] = (byte) 0xFC;
- }
- /**
- * 释放资源
- */
- public void release() {
- try {
- if (bos != null) {
- bos.flush();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }finally {
- if (bos != null) {
- try {
- bos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }finally {
- bos=null;
- }
- }
- }
- try {
- if (fos != null) {
- fos.close();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }finally {
- fos=null;
- }
- if (mediaEncode != null) {
- mediaEncode.stop();
- mediaEncode.release();
- mediaEncode=null;
- }
- if (mediaDecode != null) {
- mediaDecode.stop();
- mediaDecode.release();
- mediaDecode=null;
- }
- if (mediaExtractor != null) {
- mediaExtractor.release();
- mediaExtractor=null;
- }
- if (onCompleteListener != null) {
- onCompleteListener=null;
- }
- if (onProgressListener != null) {
- onProgressListener=null;
- }
- showLog("release");
- }
- /**
- * 解码线程
- */
- private class DecodeRunnable implements Runnable{
- @Override
- public void run() {
- while (!codeOver) {
- srcAudioFormatToPCM();
- }
- }
- }
- /**
- * 编码线程
- */
- private class EncodeRunnable implements Runnable {
- @Override
- public void run() {
- long t=System.currentTimeMillis();
- while (!codeOver || !chunkPCMDataContainer.isEmpty()) {
- dstAudioFormatFromPCM();
- }
- if (onCompleteListener != null) {
- onCompleteListener.completed();
- }
- showLog("size:"+fileTotalSize+" decodeSize:"+decodeSize+"time:"+(System.currentTimeMillis()-t));
- }
- }
- /**
- * 转码完成回调接口
- */
- public interface OnCompleteListener{
- void completed();
- }
- /**
- * 转码进度监听器
- */
- public interface OnProgressListener{
- void progress();
- }
- /**
- * 设置转码完成监听器
- * @param onCompleteListener
- */
- public void setOnCompleteListener(OnCompleteListener onCompleteListener) {
- this.onCompleteListener=onCompleteListener;
- }
- public void setOnProgressListener(OnProgressListener onProgressListener) {
- this.onProgressListener = onProgressListener;
- }
- private void showLog(String msg) {
- Log.e("AudioCodec", msg);
- }
- }
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();
- }
- });