1. 程式人生 > >android MediaCodec 音訊編解碼的實現——轉碼

android MediaCodec 音訊編解碼的實現——轉碼

從今天開始 每週不定期更新部落格,把這一週在工作與學習中遇到的問題做個總結。俗話說:好記性不如寫部落格,善於總結的人才能走的更遠。寫部落格這種利人利己的好處我就不一 一列舉了,總之,誰做誰知道,哈哈。在文章中如果有什麼問題或者錯誤,歡迎各位的討論和指正。好了,步入正題,來看看我們今天要講的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();
    }
});