android 音視頻混合

接到過這樣的一個需求:給你一個視頻(mp4)和一段音樂,合成一個新的視頻,新的視頻去掉原有的音頻,而是用該音樂作爲音頻。看似簡單的幾句話,但接到這個需求的時候,真的非常頭疼,其實這個真不簡單。關於音視頻的編輯,業界普遍使用的是FFmpeg庫,但是如果要自己去編譯優化得到一個穩定可使用FFmpeg庫,需要花費巨大的人力和時間成本,這在我當時的情境下,並不現實。在網上查找資源時候,瞭解到,自從android 16之後,谷歌開始引入了音視頻編解碼庫,並在android 18之後進行了完善,雖然穩定性上還是不足,但是在幾乎每個版本更新後,都會對音視頻庫做一些改良,所以我決定android 的這種音視頻庫開始嘗試。
囉嗦多了,開始轉入正題。閱讀本博文之前需要對android的多媒體編解碼庫有一定的瞭解,下面是幾個必須要先去了解的關鍵類。這幾個類的鏈接我都是推薦的谷歌的官方文檔,不建議各位朋友去網上搜這些相關的類的所謂詳解,因爲絕大部分雜亂無章,錯漏百出,完全就是誤人誤事,閱讀官方文檔和源碼纔是最好的方法。
MediaExtractor, 用於提取音視頻數據,獲取音視頻文件的基本信息。
Mediacodec, 是最核心的編解碼庫,通過正確的配置就能夠對音頻和視頻進行編碼或者解碼。
MediaMuxer,是將音視頻合成器,通過MediaMuxer,將音頻數據和視頻數據分別寫入同一個文件中的音頻軌道和視頻軌道,這樣就生成了一個有聲的視頻文件。
這三個類是android音視頻編解碼最基本的類。一個音視頻編解碼的基本流程如圖:
這裏寫圖片描述

下面我們實現音視頻混合需求:

import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.util.Log;

import com.example.administrator.recording.common.utils.MediaUtils;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * 音視頻合成器
 */
public class Compounder {
    private static int MP3_TYPE = 0;
    private static int AAC_TYPE = 1;
    private static int M4A_TYPE = 2;
    private static int NOT_DC_EC_TYPE = 3;//不用重新編解碼
    private static int NOT_SUPPORT_TYPE = 4;//不支持類型
    private String TAG = "mylog";
    private int MAX_INPUT_SIZE = 10240;
    private int VIDEO_READ_SAMPLE_SIZE = 524288;//512 * 1024
    private int BIT_RATE = 65536; // 64 * 1024
    private int SAMPLE_RATE = 44100;// acc sample rate
    private Long TIMEOUT_US = 1000L;

    private String mAudioPath = "/storage/emulated/0/010/aa.mp3";
    private String mVideoPath = "/storage/emulated/0/010/aa.mp4";
    private String mDstFilePath = "/storage/emulated/0/010/compound.mp4";

    private MediaExtractor mVideoExtractor = new MediaExtractor();
    private MediaExtractor mAudioExtractor = new MediaExtractor();
    private MediaMuxer mMediaMuxer = null;
    private int mAudioTrackIndex = -1;
    private int mVideoTrackIndex = -1;
    private int mAudioDEType = -1;
    private long mMaxTimeStamp = 0;

    private MediaFormat mVideoFormat;
    private MediaFormat mAudioFormat;
    private MediaCodec mDecoder;
    private MediaCodec mEncoder;

    private ByteBuffer[] mDecodeInputBuffer;
    private ByteBuffer[] mDecodeOutputBuffer;
    private ByteBuffer[] mEncodeInputBuffer;
    private ByteBuffer[] mEncodeOutputBuffer;

    /**
     *
     * @param videoPath 源視頻路徑
     * @param audioPath 音頻路徑
     * @param audioDEType 音頻類型
     * @param dstPath 目標文件路徑
     */
    private Compounder(String videoPath, String audioPath, int audioDEType, String dstPath){
        mVideoPath = videoPath;
        mAudioPath = audioPath;
        mAudioDEType = audioDEType;
        mDstFilePath = dstPath;
    }

    /**
     *
     * @param videoPath 源視頻路徑
     * @param audioPath 音頻路徑
     * @param dstPath 目標文件路徑
     * @return null || compounder
     */
    public static Compounder createCompounder(String videoPath, String audioPath, String dstPath){
        if(checkVideo(videoPath)){
            int audioEDType = checkAudio(audioPath);
            if(audioEDType != NOT_SUPPORT_TYPE)
                return new Compounder(videoPath, audioPath, audioEDType, dstPath);
        }
        return null;
    }

    /**
     * 檢查音頻是否合法
     * @param audioPath
     * @return
     */
    private static int checkAudio(String audioPath) {
        if(audioPath != null){
            File file = new File(audioPath);
            if(file.exists() && file.isFile()){
                if(audioPath.endsWith(".mp3")){
                    return MP3_TYPE;
                }
                if(audioPath.endsWith(".aac")){
                    return AAC_TYPE;
                }
                if(audioPath.endsWith(".m4a")){
                    return M4A_TYPE;
                }
            }
        }
        return -1;
    }

    /**
     * 檢查視頻是否符合要求
     * @param videoPath
     * @return
     */
    private static boolean checkVideo(String videoPath) {
        if(videoPath != null && videoPath.endsWith(".mp4")){
            File file = new File(videoPath);
            if(file.exists() && file.isFile()){
                return true;
            }
        }
        return false;
    }

    /**
     * 開始合成
     */
    public void start(){
        if(mAudioDEType == MP3_TYPE) {
            initDecodeAudio();
            initEncodeAudio();
            if (initMuxer()) {
                if (handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor)) {
                    decodeInputBuffer();//解碼--->編碼--->合成輸出文件
                }
            }
        }else if (mAudioDEType == NOT_DC_EC_TYPE){//直接合成文件
            if(initMuxer()){
                handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor);
                handleTrack(mMediaMuxer, mAudioTrackIndex, mAudioExtractor);
            }
        }
        release();
        Log.e(TAG, "finished~~~~~~~~~~~~~~~~~~~~");
    }

    /**
     * 初始化混合器
     */
    private boolean initMuxer() {
        try {
            if((mVideoFormat = MediaUtils.getMediaFormat(mVideoExtractor, MediaUtils.VIDEO_TYPE, mVideoPath)) != null) {
                mMediaMuxer = new MediaMuxer(mDstFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
                mVideoTrackIndex = mMediaMuxer.addTrack(mVideoFormat);//設置視頻格式
                Log.e(TAG, " video long is : " +  mVideoFormat.getLong(MediaFormat.KEY_DURATION));
                mMaxTimeStamp = mVideoFormat.getLong(MediaFormat.KEY_DURATION);
                if(mAudioDEType == NOT_DC_EC_TYPE){
                    MediaFormat audioFormat = MediaUtils.getMediaFormat(mAudioExtractor, MediaUtils.AUDIO_TYPE, mAudioPath);
                    if(audioFormat != null) {
                        mAudioTrackIndex = mMediaMuxer.addTrack(audioFormat);
                    }else{
                        return false;
                    }
                }
                else if(mAudioDEType == MP3_TYPE) {
                    if(mDecoder == null || mEncoder == null){
                        return false;
                    }
                    mAudioTrackIndex = mMediaMuxer.addTrack(mEncoder.getOutputFormat());//設置目標的音頻格式
                }
                mMediaMuxer.start();
                return true;
            }
        } catch (IOException e) {
            e.printStackTrace();
            mMediaMuxer = null;
        }
        return false;
    }


    /**
     * 獲取輸出的解碼後的buffer
     */
    private void decodeOutputBuffer() {
        BufferInfo info = new BufferInfo();
        int outputIndex = mDecoder.dequeueOutputBuffer(info, -1);
        if (outputIndex >= 0) {
            byte[] chunk = new byte[info.size];
            mDecodeOutputBuffer[outputIndex].position(info.offset);
            mDecodeOutputBuffer[outputIndex].limit(info.offset + info.size);
            mDecodeOutputBuffer[outputIndex].get(chunk);
            mDecodeOutputBuffer[outputIndex].clear();
            mDecoder.releaseOutputBuffer(outputIndex, false);
            if (chunk.length > 0) {
                encodData(chunk, info.presentationTimeUs);
            }
        }
    }

    /**
     * 編碼PCM數據
     * @param input pcm數據
     * @param presentationTimeUs 時間戳
     */
    private synchronized void encodData(byte[] input, long presentationTimeUs) {
        int inputBufferIndex = mEncoder.dequeueInputBuffer(-1);
        if (inputBufferIndex >= 0) {
            ByteBuffer inputBuffer = mEncodeInputBuffer[inputBufferIndex];
            inputBuffer.clear();
            inputBuffer.put(input);
            mEncoder.queueInputBuffer(inputBufferIndex, 0, input.length, presentationTimeUs, 0);
        }

        BufferInfo bufferInfo = new BufferInfo();
        int outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
        while (outputBufferIndex >= 0) {
            int outBitsSize = bufferInfo.size;
            ByteBuffer outputBuffer = mEncodeOutputBuffer[outputBufferIndex];
            outputBuffer.position(bufferInfo.offset);
            outputBuffer.limit(bufferInfo.offset + outBitsSize);
            outputBuffer.position(bufferInfo.offset);
            mMediaMuxer.writeSampleData(mAudioTrackIndex, outputBuffer, bufferInfo);
            mEncoder.releaseOutputBuffer(outputBufferIndex, false);
            outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
        }
    }

    /**
     * 輸入要解碼的buffer
     * @return 輸入是否成功
     */
    private void decodeInputBuffer() {
        while (true) {
            int inputBufIndex = mDecoder.dequeueInputBuffer(TIMEOUT_US);
            if (inputBufIndex >= 0) {
                mDecodeInputBuffer[inputBufIndex].clear();
                long presentationTimeUs = mAudioExtractor.getSampleTime();
                int sampleSize = mAudioExtractor.readSampleData(mDecodeInputBuffer[inputBufIndex], 0);
                if (sampleSize > 0) {
                    if (presentationTimeUs > mMaxTimeStamp) {
                        return;//超過最大時間戳的音頻不處理
                    }
                    mDecoder.queueInputBuffer(inputBufIndex, 0, sampleSize, presentationTimeUs, 0);
                    mAudioExtractor.advance();
                    decodeOutputBuffer();
                }
            }
        }
    }

    /**
     * 初始化編碼器
     * @return 初始化是否成功
     */
    private boolean initEncodeAudio() {
        try {
            //設置目標音頻格式
            mEncoder = MediaCodec.createByCodecName("OMX.google.aac.encoder");
            MediaFormat mediaFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", SAMPLE_RATE, 2);
            mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
            mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, MAX_INPUT_SIZE);
            mEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            mEncoder.start();
            mEncodeInputBuffer = mEncoder.getInputBuffers();
            mEncodeOutputBuffer = mEncoder.getOutputBuffers();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "initEncodeAudio---" + e.getMessage());
        }
        return false;
    }

    /**
     * 初始化音頻解碼器
     */
    private boolean initDecodeAudio() {
        try {
            mAudioFormat = MediaUtils.getMediaFormat(mAudioExtractor, MediaUtils.AUDIO_TYPE, mAudioPath);
            String mime = mAudioFormat.getString(MediaFormat.KEY_MIME);
            Log.e(TAG, "設置decoder的音頻格式是:" + mime);
            mDecoder = MediaCodec.createDecoderByType(mime);
            mDecoder.configure(mAudioFormat, null, null, 0);
            mDecoder.start();

            mDecodeInputBuffer = mDecoder.getInputBuffers();
            mDecodeOutputBuffer = mDecoder.getOutputBuffers();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "initDecodeAudio---" + e.getMessage());
        }
        return false;
    }

    /**
     * 寫入提取的數據到目標文件
     * @param mediaMuxer
     * @param trackIndex
     * @param extractor
     */
    private boolean handleTrack(MediaMuxer mediaMuxer, int trackIndex, MediaExtractor extractor) {
        if(mediaMuxer == null || trackIndex < 0 || extractor == null){
            return false;
        }
        ByteBuffer inputBuffer = ByteBuffer.allocate(VIDEO_READ_SAMPLE_SIZE);
        BufferInfo info = new BufferInfo();
        int sampleSize;
        while ((sampleSize = extractor.readSampleData(inputBuffer, 0)) > 0){
            info.offset = 0;
            info.size = sampleSize;
            info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;
            info.presentationTimeUs = extractor.getSampleTime();
            if(mMaxTimeStamp < info.presentationTimeUs){
                break;
            }
            extractor.advance();
            mediaMuxer.writeSampleData(trackIndex, inputBuffer, info);
        }
        return true;
    }

    /**
     * 釋放資源
     */
    private void release() {
        try {
            if(mEncoder != null) {
                mEncoder.stop();
                mEncoder.release();
            }
            if(mMediaMuxer != null) {
                mMediaMuxer.stop();
                mMediaMuxer.release();
            }
            if(mDecoder != null) {
                mDecoder.stop();
                mDecoder.release();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

代碼看上去比較多,爲了更好的理解,下面我將詳細解釋具體步驟,首先是初始化混合器:通過統一的createCompounder創建混合器,創建混合器除了一些常規的文件檢查之外,還有一個很重要的是音樂類型的檢查,因爲MediaMuxer是不能直接寫入mp3音樂格式的數據,但是MediaMuxer支持aac和m4a格式的音樂直接寫入合成文件。同時因爲MediaMuxer對mp4視頻數據也支持直接寫入,所以對於視頻數據不需要重新編解碼所以需要在創建混合器的時候檢查一下音樂類型。

    /**
     *
     * @param videoPath 源視頻路徑
     * @param audioPath 音頻路徑
     * @param audioDEType 音頻類型
     * @param dstPath 目標文件路徑
     */
    private Compounder(String videoPath, String audioPath, int audioDEType, String dstPath){
        mVideoPath = videoPath;
        mAudioPath = audioPath;
        mAudioDEType = audioDEType;
        mDstFilePath = dstPath;
    }

    /**
     *
     * @param videoPath 源視頻路徑
     * @param audioPath 音頻路徑
     * @param dstPath 目標文件路徑
     * @return null || compounder
     */
    public static Compounder createCompounder(String videoPath, String audioPath, String dstPath){
        if(checkVideo(videoPath)){
            int audioEDType = checkAudio(audioPath);
            if(audioEDType != NOT_SUPPORT_TYPE)
                return new Compounder(videoPath, audioPath, audioEDType, dstPath);
        }
        return null;
    }

創建好混合器Compounder之後,調用start函數就開始混合流程:

    /**
     * 開始合成
     */
    public void start(){
        if(mAudioDEType == MP3_TYPE) {
            initDecodeAudio();
            initEncodeAudio();
            if (initMuxer()) {
                if (handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor)) {
                    decodeInputBuffer();//解碼--->編碼--->合成輸出文件
                }
            }
        }else if (mAudioDEType == NOT_DC_EC_TYPE){//直接合成文件
            if(initMuxer()){
                handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor);
                handleTrack(mMediaMuxer, mAudioTrackIndex, mAudioExtractor);
            }
        }
        release();
        Log.e(TAG, "finished~~~~~~~~~~~~~~~~~~~~");
    }

爲了更好的理解,下面是混合的流程圖:
這裏寫圖片描述

下面我將一一詳述各個步驟。再次強調一遍哈!

當音頻是m4a或者aac格式的時候,混合器可以直接把音頻數據寫入文件。當音頻數據是mp3格式的時候,需要將音頻數據編解碼成m4a或者aac格式,才能進行音視頻數據混合

先看音頻爲m4a和aac這條線:流程圖如下
這裏寫圖片描述


    /**
     * 開始合成
     */
    public void start(){
        if(mAudioDEType == MP3_TYPE) {
            initDecodeAudio();
            initEncodeAudio();
            if (initMuxer()) {
                if (handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor)) {
                    decodeInputBuffer();//解碼--->編碼--->合成輸出文件
                }
            }
        }
//===========m4a/aac主線流程代碼====================
        else if (mAudioDEType == NOT_DC_EC_TYPE){//直接合成文件
            if(initMuxer()){
                handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor);
                handleTrack(mMediaMuxer, mAudioTrackIndex, mAudioExtractor);
            }
        }
        release();
        Log.e(TAG, "finished~~~~~~~~~~~~~~~~~~~~");
    }
    //=====================================

1,初始化混合器
初始化混合器,首先要獲取原視頻文件的視頻格式

/**
* 初始化混合器
*/
private boolean initMuxer() {
   try {
       //獲取視頻格式
       mVideoFormat = MediaUtils.getMediaFormat(mVideoExtractor,
               MediaUtils.VIDEO_TYPE, mVideoPath);
       if(mVideoFormat != null) {
           //設置mp4輸出格式
           mMediaMuxer = new MediaMuxer(mDstFilePath,
                   MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
           //加入視頻軌道,設置視頻格式
           mVideoTrackIndex = mMediaMuxer.addTrack(mVideoFormat);
           mMaxTimeStamp = mVideoFormat.getLong(MediaFormat.KEY_DURATION);
           if(mAudioDEType == NOT_DC_EC_TYPE){
               //獲取音頻格式
               MediaFormat audioFormat = MediaUtils.getMediaFormat(mAudioExtractor,
                                               MediaUtils.AUDIO_TYPE, mAudioPath);
               if(audioFormat != null) {
                   mAudioTrackIndex = mMediaMuxer.addTrack(audioFormat);
               }else{
                   return false;
               }
           }
           else if(mAudioDEType == MP3_TYPE) {
               if(mDecoder == null || mEncoder == null){
                   return false;
               }
               //加入音頻軌道,設置目標的音頻格式
               mAudioTrackIndex = mMediaMuxer.addTrack(mEncoder.getOutputFormat());
           }
           mMediaMuxer.start();
           return true;
       }
   } catch (IOException e) {
       e.printStackTrace();
       mMediaMuxer = null;
   }
   return false;
}

這個步驟涉及到一個獲取多媒體數據格式的函數,下面也貼出對應的代碼:

 /**
  * 獲取媒體編碼格式
  * @param extractor
  * @param type
  * @param mediaPath
  * @return
  */
 public static MediaFormat getMediaFormat(MediaExtractor extractor, String type, String mediaPath){
     if(extractor == null || mediaPath == null || mediaPath.equals("")){
         return null;
     }
     try {
         MediaFormat format;
         extractor.setDataSource(mediaPath);
         for(int i = 0; i < extractor.getTrackCount(); i++){
             format = extractor.getTrackFormat(i);
             if(format.getString(MediaFormat.KEY_MIME).startsWith(type)){
                 extractor.selectTrack(i);
                 return format;
             }
         }
     }catch (IOException e){
         Log.e(TAG, e.getMessage());
     }
     return null;
 }

通過MediaMuxer.addTrack來標明音頻軌道和視頻數據軌道。這個是非常重要的,因爲只有加入了音視頻對應的數據軌道,後面才能把對應的數據寫入對應的軌道。

2,寫入視頻數據 && 3,寫入音頻數據
通過MediaExtractor提取MP4數據,因爲MediaMuxer可以直接合成mp4數據,所以此處不用對mp4數據重新編解碼,同樣的MediaMuxer也支持直接合成m4a和aac數據,所以也不用重新編解碼音頻數據。此處爲了避免代碼重複,寫了一個通用的handleTrack函數來寫入指定的音視頻數據,直接通過MediaExtractor提取數據,然後通過MediaMuxer寫入數據。

  /**
   * 開始合成
   */
  public void start(){
      if(mAudioDEType == MP3_TYPE) {
          initDecodeAudio();
          initEncodeAudio();
          if (initMuxer()) {
              //寫入視頻數據
              if (handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor)) {
                  decodeInputBuffer();//mp3音頻:解碼--->編碼--->合成輸出文件
              }
          }
      }else if (mAudioDEType == NOT_DC_EC_TYPE){//音頻爲m4a/aac直接合成文件
          if(initMuxer()){
              //寫入視頻數據
              handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor);
              //寫入音頻數據
              handleTrack(mMediaMuxer, mAudioTrackIndex, mAudioExtractor);
          }
      }
      release();
      Log.e(TAG, "finished~~~~~~~~~~~~~~~~~~~~");
  }
/**
 * 寫入提取的數據到目標文件
 * @param mediaMuxer 數據合成器
 * @param trackIndex  數據軌道
 * @param extractor   數據提取器
 */
private boolean handleTrack(MediaMuxer mediaMuxer, int trackIndex, MediaExtractor extractor) {
    if(mediaMuxer == null || trackIndex < 0 || extractor == null){
        return false;
    }
    ByteBuffer inputBuffer = ByteBuffer.allocate(VIDEO_READ_SAMPLE_SIZE);
    BufferInfo info = new BufferInfo();
    int sampleSize;
    while ((sampleSize = extractor.readSampleData(inputBuffer, 0)) > 0){
        info.offset = 0;
        info.size = sampleSize;
        info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;
        info.presentationTimeUs = extractor.getSampleTime();
        if(mMaxTimeStamp < info.presentationTimeUs){
            break;
        }
        extractor.advance();
        mediaMuxer.writeSampleData(trackIndex, inputBuffer, info);
    }
    return true;
}

4,釋放資源,合成結束
合成完成之後,務必要釋放資源,否則容易引起內存泄漏,以及一系列崩潰

    /**
     * 釋放資源
     */
    private void release() {
        try {
            if(mEncoder != null) {
                mEncoder.stop();
                mEncoder.release();
            }
            if(mMediaMuxer != null) {
                mMediaMuxer.stop();
                mMediaMuxer.release();
            }
            if(mDecoder != null) {
                mDecoder.stop();
                mDecoder.release();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

說完m4a/aac這條線,接下來說的是mp3這條線:
MediaMuxer不支持直接合成mp3音頻數據,所以需要先對mp3數據進行解碼成pcm數據,然後編碼成m4a或者aac數據,才能夠寫入合成文件。


    /**
     * 開始合成
     */
    public void start(){
    //========================Mp3主線代碼流程======================
        if(mAudioDEType == MP3_TYPE) {
            initDecodeAudio();
            initEncodeAudio();
            if (initMuxer()) {
                if (handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor)) {
                    decodeInputBuffer();//解碼--->編碼--->合成輸出文件
                }
            }
        }
//=========================================================
        else if (mAudioDEType == NOT_DC_EC_TYPE){//直接合成文件
            if(initMuxer()){
                handleTrack(mMediaMuxer, mVideoTrackIndex, mVideoExtractor);
                handleTrack(mMediaMuxer, mAudioTrackIndex, mAudioExtractor);
            }
        }
        release();
        Log.e(TAG, "finished~~~~~~~~~~~~~~~~~~~~");
    }

流程圖如下:
這裏寫圖片描述

1,初始化音頻解碼器
通過MediaExtractor獲取音頻數據格式,並用它來初始化解碼器,初始化解碼器後,需要獲取對應的輸入數據緩存和輸出數據緩存

    /**
     * 初始化音頻解碼器
     */
    private boolean initDecodeAudio() {
        try {
            mAudioFormat = MediaUtils.getMediaFormat(mAudioExtractor, MediaUtils.AUDIO_TYPE, mAudioPath);
            String mime = mAudioFormat.getString(MediaFormat.KEY_MIME);
            Log.e(TAG, "設置decoder的音頻格式是:" + mime);
            mDecoder = MediaCodec.createDecoderByType(mime);
            mDecoder.configure(mAudioFormat, null, null, 0);
            mDecoder.start();//記得需要start
            //解碼器輸入數據緩存
            mDecodeInputBuffer = mDecoder.getInputBuffers();
            //解碼器輸出數據緩存
            mDecodeOutputBuffer = mDecoder.getOutputBuffers();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "initDecodeAudio---" + e.getMessage());
        }
        return false;
    }

2,初始化音頻解碼器
指定一個m4a或者aac編碼器,代碼中使用的是OMX.google.aac.encoder,這個編碼器絕大部分android手機都有,是google自帶的編碼器,所以屬於軟編碼,但是音頻數據編碼速度很快,所以不用太在意軟硬編碼的問題(想編碼視頻數據朋友就得注意了,硬編碼纔是第一選擇),同樣的,初始化完後需要獲取對應的編碼器輸入數據緩存和編碼器輸出數據緩存。

    /**
     * 初始化編碼器
     * @return 初始化是否成功
     */
    private boolean initEncodeAudio() {
        try {
            //設置目標音頻格式
            mEncoder = MediaCodec.createByCodecName("OMX.google.aac.encoder");
            MediaFormat mediaFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", SAMPLE_RATE, 2);
            mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
            mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, MAX_INPUT_SIZE);
            mEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            mEncoder.start();//記得需要start
            //編碼器數據輸入緩存
            mEncodeInputBuffer = mEncoder.getInputBuffers();
            //編碼器數據輸出緩存
            mEncodeOutputBuffer = mEncoder.getOutputBuffers();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "initEncodeAudio---" + e.getMessage());
        }
        return false;
    }

3,初始化混合器
和m4a/aac主線一樣,請參考上面描述

4,寫入視頻數據
和m4a/aac主線一樣,請參考上面描述

5,解碼mp3數據爲pcm && 6,編碼mp3數據爲m4a&&7,寫入音頻數據
解碼–編碼–寫入音頻數據,三個步驟是線性同步的,因爲不可能把數據都緩存在內從中,那樣音頻稍微大一點,內存就爆了。解碼完一定的數據,就要把解碼好的數據輸出,然後進行編碼,接着把編碼好的數據進行輸出,最後把編碼好的m4a數據寫入mp4文件對應的音頻軌道。下面是解碼-編碼-寫入音頻數據的流程圖:
這裏寫圖片描述

下面是具體的代碼流程:

 /**
  * 輸入要解碼的buffer
  * @return 輸入是否成功
  */
 private void decodeInputBuffer() {
     while (true) {
         int inputBufIndex = mDecoder.dequeueInputBuffer(TIMEOUT_US);
         if (inputBufIndex >= 0) {
             mDecodeInputBuffer[inputBufIndex].clear();
             long presentationTimeUs = mAudioExtractor.getSampleTime();
              //讀取數據到解碼器輸入緩存
             int sampleSize = 
             mAudioExtractor.readSampleData(mDecodeInputBuffer[inputBufIndex], 0);
             if (sampleSize > 0) {
                 if (presentationTimeUs > mMaxTimeStamp) {
                     return;//超過最大時間戳的音頻不處理
                 }
                 //將對數據進行解碼
                 mDecoder.queueInputBuffer(inputBufIndex, 0, sampleSize, 
                                          presentationTimeUs, 0);
                 mAudioExtractor.advance();
                 //輸出解碼後數據,然後進行編碼輸出------important
                 decodeOutputBuffer();
             }
         }
     }
 }

    /**
     * 獲取輸出的解碼後的buffer
     */
    private void decodeOutputBuffer() {
        BufferInfo info = new BufferInfo();
        //輸出解碼的pcm數據
        int outputIndex = mDecoder.dequeueOutputBuffer(info, -1);
        if (outputIndex >= 0) {
            byte[] chunk = new byte[info.size];
            mDecodeOutputBuffer[outputIndex].position(info.offset);
            mDecodeOutputBuffer[outputIndex].limit(info.offset + info.size);
            //將解碼數據轉爲byte
            mDecodeOutputBuffer[outputIndex].get(chunk);
            mDecodeOutputBuffer[outputIndex].clear();
            //必須要釋放解碼器緩存
            mDecoder.releaseOutputBuffer(outputIndex, false);
            if (chunk.length > 0) {
            //將pcm數據提供給編碼器進行編碼
                encodData(chunk, info.presentationTimeUs);
            }
        }
    }

    /**
     * 編碼PCM數據
     * @param input pcm數據
     * @param presentationTimeUs 時間戳
     */
    private synchronized void encodData(byte[] input, long presentationTimeUs) {
        int inputBufferIndex = mEncoder.dequeueInputBuffer(-1);
        if (inputBufferIndex >= 0) {
            ByteBuffer inputBuffer = mEncodeInputBuffer[inputBufferIndex];
            inputBuffer.clear();
            inputBuffer.put(input);
            //將pcm數據輸入編碼器緩存
            mEncoder.queueInputBuffer(inputBufferIndex, 0, input.length, presentationTimeUs, 0);
        }

        BufferInfo bufferInfo = new BufferInfo();
        //編碼器中輸出m4a數據
        int outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
        while (outputBufferIndex >= 0) {
            int outBitsSize = bufferInfo.size;
            ByteBuffer outputBuffer = mEncodeOutputBuffer[outputBufferIndex];
            outputBuffer.position(bufferInfo.offset);
            outputBuffer.limit(bufferInfo.offset + outBitsSize);
            outputBuffer.position(bufferInfo.offset);
            //將編碼器輸出緩存寫入到mp4文件---寫入音頻數據
            mMediaMuxer.writeSampleData(mAudioTrackIndex, outputBuffer, bufferInfo);
            mEncoder.releaseOutputBuffer(outputBufferIndex, false);
            outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
        }
    }

上面就是一個很完整的解碼–編碼流程,請各位朋友仔細看代碼註釋,這些地方都是關鍵步驟,通過這些步驟能夠很清晰的明白代碼邏輯。

8,結束合成,釋放資源
釋放資源時候,需要釋放所有的編解碼器,以及混合器。

    /**
     * 釋放資源
     */
    private void release() {
        try {
            if(mEncoder != null) {
                mEncoder.stop();
                mEncoder.release();
            }
            if(mMediaMuxer != null) {
                mMediaMuxer.stop();
                mMediaMuxer.release();
            }
            if(mDecoder != null) {
                mDecoder.stop();
                mDecoder.release();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

好了,上面就是音視頻混合的所有流程,有疑問的朋友歡迎一起溝通交流,共同進步,發現代碼有問題的朋友,也非常歡迎指出來,大家一起學習。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章