原文地址: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其它同理

 /**
* 初始化解码器
*/
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);
}
}

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 音频编解码的实现——转码的更多相关文章

  1. 编译 Android 版本的 Opus 音频编解码库的方法

    Opus 音频编解码库是 Speex 音频编解码库的下一代版本,从编解码性能以及质量上来讲都有了长足的进步.Opus 的编译非常简单,但是官方并未给出详细的 Android 版本编译指南,查找了大量资 ...

  2. FFMPEG视音频编解码零基础学习方法-b

    感谢大神分享,虽然现在还看不懂,留着大家一起看啦 PS:有不少人不清楚“FFmpeg”应该怎么读.它读作“ef ef em peg” 0. 背景知识 本章主要介绍一下FFMPEG都用在了哪里(在这里仅 ...

  3. [总结]FFMPEG视音频编解码零基础学习方法

    在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在“ ...

  4. [转载] FFMPEG视音频编解码零基础学习方法

    在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在“ ...

  5. [总结]FFMPEG视音频编解码零基础学习方法【转】

    本文转载自:http://blog.csdn.net/leixiaohua1020/article/details/15811977 在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFM ...

  6. [转]FFMPEG视音频编解码零基础学习方法

    在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在“ ...

  7. [总结]FFMPEG视音频编解码零基础学习方法--转

    ffmpeg编解码学习   目录(?)[-] ffmpeg程序的使用ffmpegexeffplayexeffprobeexe 1 ffmpegexe 2 ffplayexe 3 ffprobeexe ...

  8. FFMPEG视音频编解码零基础学习方法

    在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在“ ...

  9. 【转】[总结]FFMPEG视音频编解码零基础学习方法

    在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在“ ...

随机推荐

  1. For Aisha(阿伊莎)

    相见时难别亦难,东风无力百花残.by:昂思多,20160524 跟你在一起,没有拘束感,完全就像是在跟亲人对话. 很喜欢这种感觉 虽然才认识不到10天,却就像是认识了好几年的老朋友 真的喜欢叫你“阿伊 ...

  2. ​? super T ? extends T

    ​? super T ?保存的是  T类型或者T类型的父类 ​ ? extends T ?保存的是  T类型或者T类型的子类

  3. 14.6.7 Configuring the Number of Background InnoDB IO Threads 配置InnoDB IO Threads的数量

    14.6.7 Configuring the Number of Background InnoDB IO Threads 配置InnoDB IO Threads的数量 InnoDB 使用后台线程来服 ...

  4. Oracle 直接路径读

    在11g中,全表扫描可能使用direct path read方式,绕过buffer cache,这样的全表扫描就是物理读了. 在10g中,都是通过gc buffer来读的,所以不存在direct pa ...

  5. bzoj1706

    倍增好题,f[p,i,j]表示i到j经过了2^p条边走过的最短路径显然f[p+1]可以由f[p]转移来然后对n二进制拆分累加即可 ; ..,..] of int64; f,pf:..] of int6 ...

  6. OA系统配置文件

    第一章 web.xml配置文件解读 1. web.xml文件解读 lemon OA系统的核心配置文件都放在spring目录下的具有applicationContext的前缀文件.Classpath后有 ...

  7. [PHP] 跳转以及回到原来的地址

    回到原来的地址: 1.PHP(PHP代码) Header('Location:'.$_SERVER["HTTP_REFERER"]); 2.JavaScript(相当于后退按钮,- ...

  8. godaddy.com 注册域名 买卖域名

    https://www.godaddy.com/domains/searchresults.aspx?ci=83269&checkAvail=1&domainToCheck=ses.x ...

  9. RTP, RTCP, RTSP 协议介绍

    流媒体是边下载边播放的方式, 是视频会议.IP电话等应用场合的技术基础.           为什么TCP/IP协议就不能满足多媒体通信的要求呢?因为TCP有以下4个特点:1.TCP重传机制2.TCP ...

  10. wine on ubuntu linux, and source insight 绿色版的安装

    1.安装一些必要组件 winetricks msxml3 gdiplus riched20 riched30 vcrun6 vcrun2005sp1 wenquanyi 2.拷贝字体 下载网盘中的字体 ...