android MediaCodec 音訊編解碼的實現——轉碼
從今天開始 每週不定期更新部落格,把這一週在工作與學習中遇到的問題做個總結。俗話說:好記性不如寫部落格,善於總結的人才能走的更遠。寫部落格這種利人利己的好處我就不一 一列舉了,總之,誰做誰知道,哈哈。在文章中如果有什麼問題或者錯誤,歡迎各位的討論和指正。好了,步入正題,來看看我們今天要講的MediaCodec
一、概述
由於專案的需要,需要將mp3檔案轉碼為aac音訊檔案,起初打算移植FFmpeg到專案中,無奈FFmpeg過於龐大,專案中的音訊轉碼只是一個輔助util,並不是主要功能。所以打算用MediaCodec來實現這一需求。網上關於MediaCodec的文章少的可憐,寫demo的過程中踩到了無數的坑。不過,在
二、轉碼實現原理
本篇文章以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();
}
});