使用MediaCodec硬解碼h.265視頻及音頻進行播放

h.265這個視頻是不少播放器不支持的,就算是bilibili開源的ijkplayer也不能直接播放,須要本身去從新編譯 才能夠支持。 這裏經過這個demo來演示一下如何硬解碼視頻,播放h.265視頻,其實編碼的視頻一樣道理。git

視頻的播放主要在surfaceView中顯示,而解碼過程則在音頻解碼線程視頻解碼線程兩個線程中分別執行。github

視頻解碼

主要是用到了一個MediaCodec這個類來進行解碼。緩存

設置數據源

MediaExtractor mediaExtractor = new MediaExtractor();
try {
    mediaExtractor.setDataSource(path); // 設置數據源
} catch (IOException e1) {
    e1.printStackTrace();
}
複製代碼

根據視頻的編碼信息來初始化MediaCodec:

視頻的mineType是video類型。ide

String mimeType = null;
for (int i = 0; i < mediaExtractor.getTrackCount(); i++) { // 信道總數
    MediaFormat format = mediaExtractor.getTrackFormat(i); // 音頻文件信息
    mimeType = format.getString(MediaFormat.KEY_MIME);
    if (mimeType.startsWith("video/")) { // 視頻信道
        mediaExtractor.selectTrack(i); // 切換到視頻信道
        try {
            mediaCodec = MediaCodec.createDecoderByType(mimeType); // 建立解碼器,提供數據輸出
        } catch (IOException e) {
            e.printStackTrace();
        }
        mediaCodec.configure(format, surface, null, 0);
        break;
    }
}
mediaCodec.start(); // 啓動MediaCodec ,等待傳入數據
複製代碼

獲取緩存器

// 輸入
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); // 用來存放目標文件的數據
// 輸出
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers(); // 解碼後的數據
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); // 用於描述解碼獲得的byte[]數據的相關信息
複製代碼

開始解碼

while (!Thread.interrupted()) {

    if (!bIsEos) {
        int inIndex = mediaCodec.dequeueInputBuffer(0);
        if (inIndex >= 0) {
            ByteBuffer buffer = inputBuffers[inIndex];
            int nSampleSize = mediaExtractor.readSampleData(buffer, 0); // 讀取一幀數據至buffer中
            if (nSampleSize < 0) {
                Log.d(TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM");
                mediaCodec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                bIsEos = true;
            } else {
                // 填數據
                mediaCodec.queueInputBuffer(inIndex, 0, nSampleSize, mediaExtractor.getSampleTime(), 0); // 通知MediaDecode解碼剛剛傳入的數據
                mediaExtractor.advance(); // 繼續下一取樣
            }
        }
    }

    int outIndex = mediaCodec.dequeueOutputBuffer(info, 0);
    switch (outIndex) {
        case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
            Log.d(TAG, "INFO_OUTPUT_BUFFERS_CHANGED");
            outputBuffers = mediaCodec.getOutputBuffers();
            break;
        case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
            Log.d(TAG, "New format " + mediaCodec.getOutputFormat());
            break;
        case MediaCodec.INFO_TRY_AGAIN_LATER:
            Log.d(TAG, "dequeueOutputBuffer timed out!");
            break;
        default:
            ByteBuffer buffer = outputBuffers[outIndex];
            Log.v(TAG, "We can't use this buffer but render it due to the API limit, " + buffer);

            mediaCodec.releaseOutputBuffer(outIndex, true);
            break;
    }

    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
        Log.d("DecodeActivity", "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
        break;
    }
}
複製代碼

解碼完成後釋放資源

mediaCodec.stop();
mediaCodec.release();
mediaExtractor.release();
複製代碼

這樣視頻的解碼就已經完成了,此時surfaceView已經能夠播放視頻了,接下來是音頻解碼。this

音頻解碼

音頻解碼的過程和上面大同小異,主要區別在於,視頻是用surfaceView播放顯示的,而音頻咱們須要使用AudioTrack來播放。編碼

建立一個AudioPlayer類用於播放音頻

public class AudioPlayer {
    private int mFrequency;// 採樣率
    private int mChannel;// 聲道
    private int mSampBit;// 採樣精度
    private AudioTrack mAudioTrack;

    public AudioPlayer(int frequency, int channel, int sampbit) {
        this.mFrequency = frequency;
        this.mChannel = channel;
        this.mSampBit = sampbit;
    }

    /**
     * 初始化
     */
    public void init() {
        if (mAudioTrack != null) {
            release();
        }
        // 得到構建對象的最小緩衝區大小
        int minBufSize = AudioTrack.getMinBufferSize(mFrequency, mChannel, mSampBit);
        mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                mFrequency, mChannel, mSampBit, minBufSize, AudioTrack.MODE_STREAM);
        mAudioTrack.play();
    }

    /**
     * 釋放資源
     */
    private void release() {
        if (mAudioTrack != null) {
            mAudioTrack.stop();
            mAudioTrack.release();
        }
    }

    /**
     * 將解碼後的pcm數據寫入audioTrack播放
     *
     * @param data   數據
     * @param offset 偏移
     * @param length 須要播放的長度
     */
    public void play(byte[] data, int offset, int length) {
        if (data == null || data.length == 0) {
            return;
        }
        try {
            mAudioTrack.write(data, offset, length);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
複製代碼

初始化音頻解碼器:

音頻的mineType是audio類型,咱們根據這個來去音頻信息便可。spa

String mimeType;
for (int i = 0; i < mediaExtractor.getTrackCount(); i++) { // 信道總數
    MediaFormat format = mediaExtractor.getTrackFormat(i); // 音頻文件信息
    mimeType = format.getString(MediaFormat.KEY_MIME);
    if (mimeType.startsWith("audio/")) { // 音頻信道
        mediaExtractor.selectTrack(i); // 切換到 音頻信道
        try {
            mediaCodec = MediaCodec.createDecoderByType(mimeType); // 建立解碼器,提供數據輸出
        } catch (IOException e) {
            e.printStackTrace();
        }
        mediaCodec.configure(format, null, null, 0);
        mPlayer = new AudioPlayer(format.getInteger(MediaFormat.KEY_SAMPLE_RATE), AudioFormat
                .CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
        mPlayer.init();
        break;
    }
}
if (mediaCodec == null) {
    Log.e(TAG, "Can't find video info!");
    return;
}

mediaCodec.start(); // 啓動MediaCodec ,等待傳入數據
複製代碼

音頻解碼:

音頻解碼過程與視頻解碼大同小異,只須要額外調用一下咱們建立的AudioPlayer來播放音頻便可。線程

while (!Thread.interrupted()) {

    if (!bIsEos) {
        int inIndex = mediaCodec.dequeueInputBuffer(0);
        if (inIndex >= 0) {
            ByteBuffer buffer = inputBuffers[inIndex];
            int nSampleSize = mediaExtractor.readSampleData(buffer, 0); // 讀取一幀數據至buffer中
            if (nSampleSize < 0) {
                Log.d(TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM");
                mediaCodec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                bIsEos = true;
            } else {
                // 填數據
                mediaCodec.queueInputBuffer(inIndex, 0, nSampleSize, mediaExtractor.getSampleTime(), 0); // 通知MediaDecode解碼剛剛傳入的數據
                mediaExtractor.advance(); // 繼續下一取樣
            }
        }
    }

    int outIndex = mediaCodec.dequeueOutputBuffer(info, 0);
    switch (outIndex) {
        case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
            Log.d(TAG, "INFO_OUTPUT_BUFFERS_CHANGED");
            outputBuffers = mediaCodec.getOutputBuffers();
            break;
        case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
            Log.d(TAG, "New format " + mediaCodec.getOutputFormat());
            break;
        case MediaCodec.INFO_TRY_AGAIN_LATER:
            Log.d(TAG, "dequeueOutputBuffer timed out!");
            break;
        default:
            ByteBuffer buffer = outputBuffers[outIndex];
            Log.v(TAG, "We can't use this buffer but render it due to the API limit, " + buffer);

            while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {
                try {
                    sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
            //用來保存解碼後的數據
            byte[] outData = new byte[info.size];
            buffer.get(outData);
            //清空緩存
            buffer.clear();
            //播放解碼後的數據
            mPlayer.play(outData, 0, info.size);
            mediaCodec.releaseOutputBuffer(outIndex, true);
            break;
    }

    // All decoded frames have been rendered, we can stop playing
    // now
    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
        Log.d("DecodeActivity", "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
        break;
    }
}
複製代碼

效果展現

視頻編碼信息,爲h.265:code

播放效果(帶聲音):orm

視頻播放

獲取MediaCodec支持解碼的編碼格式:

HashMap<String, MediaCodecInfo.CodecCapabilities> mEncoderInfos = new HashMap<>();
for(int i = MediaCodecList.getCodecCount() - 1; i >= 0; i--){
    MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
    if(codecInfo.isEncoder()){
        for(String t : codecInfo.getSupportedTypes()){
            try{
                mEncoderInfos.put(t, codecInfo.getCapabilitiesForType(t));
            } catch(IllegalArgumentException e){
                e.printStackTrace();
            }
        }
    }
}
複製代碼

完整demo地址

使用的時候將assets下的h265.mp4複製到sd卡便可

gitHub地址:github.com/JavaNoober/…

相關文章
相關標籤/搜索