Android媒體解碼MediaCodec MediaExtractor學習

Android提供了MediaPlayer播放器播放媒體文件,其實MediaPlyer只是對Android Media包下的MediaCodec和MediaExtractor進行了包裝,方便使用。可是最好理解下Android媒體文件的解碼,編碼和渲染流程。java

shapeofmyheart.jpeg

使用android.media包下的MediaCodec和MediaExtractor實現一個簡單的視頻解碼渲染。android

使用到了:git

  • MediaCodec:負責媒體文件的編碼和解碼工做,內部方法均爲native方法
  • MediaExtractor:負責將指定類型的媒體文件從文件中找到軌道,並填充到MediaCodec的緩衝區中,內部方法均爲native方法github

  • AudioTrack:負責將解碼以後的音頻播放
  • SurfaceView:展現解碼以後的視頻ide

視頻被播放主要分爲如下步驟:編碼

  1. 將資源加載到extractor
  2. 獲取視頻所在軌道
  3. 設置extractor選中視頻所在軌道
  4. 創將解碼視頻的MediaCodec,decoder
  5. 開始循環,直到視頻資源的末尾
  6. 將extractor中資源以一個單位填充進decoder的輸入緩衝區
  7. decoder將解碼以後的視頻填充到輸出緩衝區
  8. decoder釋放輸出緩衝區的同時,將緩衝區中數據渲染到surface

音頻的播放相似,只多了AudioTrack部分,少了渲染到surface部分。code

MediaCodec.releaseOutputBuffer(int outputBufferIndex, boolean render);orm

  • render爲true就會渲染到surface

播放的控制,視頻和音頻各自擁有一個Thread。視頻

public void play() {
        isPlaying = true;
        if (videoThread == null) {
            videoThread = new VideoThread();
            videoThread.start();
        }
        if (audioThread == null) {
            audioThread = new AudioThread();
            audioThread.start();
        }
    }

    public void stop() {
        isPlaying = false;
    }

VideoThreadblog

private class VideoThread extends Thread {
        @Override
        public void run() {
            MediaExtractor videoExtractor = new MediaExtractor();
            MediaCodec videoCodec = null;
            try {
                videoExtractor.setDataSource(filePath);
            } catch (IOException e) {
                e.printStackTrace();
            }
            int videoTrackIndex;
            //獲取視頻所在軌道
            videoTrackIndex = getMediaTrackIndex(videoExtractor, "video/");
            if (videoTrackIndex >= 0) {
                MediaFormat mediaFormat = videoExtractor.getTrackFormat(videoTrackIndex);
                int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
                int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
                //視頻長度:秒
                float time = mediaFormat.getLong(MediaFormat.KEY_DURATION) / 1000000;
                callBack.videoAspect(width, height, time);
                videoExtractor.selectTrack(videoTrackIndex);
                try {
                    videoCodec = MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME));
                    videoCodec.configure(mediaFormat, surface, null, 0);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (videoCodec == null) {
                Log.v(TAG, "MediaCodec null");
                return;
            }
            videoCodec.start();

            MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo();
            ByteBuffer[] inputBuffers = videoCodec.getInputBuffers();
//            ByteBuffer[] outputBuffers = videoCodec.getOutputBuffers();
            boolean isVideoEOS = false;

            long startMs = System.currentTimeMillis();
            while (!Thread.interrupted()) {
                if (!isPlaying) {
                    continue;
                }
                //將資源傳遞到解碼器
                if (!isVideoEOS) {
                    isVideoEOS = putBufferToCoder(videoExtractor, videoCodec, inputBuffers);
                }
                int outputBufferIndex = videoCodec.dequeueOutputBuffer(videoBufferInfo, TIMEOUT_US);
                switch (outputBufferIndex) {
                    case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                        Log.v(TAG, "format changed");
                        break;
                    case MediaCodec.INFO_TRY_AGAIN_LATER:
                        Log.v(TAG, "解碼當前幀超時");
                        break;
                    case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                        //outputBuffers = videoCodec.getOutputBuffers();
                        Log.v(TAG, "output buffers changed");
                        break;
                    default:
                        //直接渲染到Surface時使用不到outputBuffer
                        //ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                        //延時操做
                        //若是緩衝區裏的可展現時間>當前視頻播放的進度,就休眠一下
                        sleepRender(videoBufferInfo, startMs);
                        //渲染
                        videoCodec.releaseOutputBuffer(outputBufferIndex, true);
                        break;
                }

                if ((videoBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    Log.v(TAG, "buffer stream end");
                    break;
                }
            }//end while
            videoCodec.stop();
            videoCodec.release();
            videoExtractor.release();
        }
    }

獲取指定類型媒體文件所在軌道

//獲取指定類型媒體文件所在軌道
    private int getMediaTrackIndex(MediaExtractor videoExtractor, String MEDIA_TYPE) {
        int trackIndex = -1;
        for (int i = 0; i < videoExtractor.getTrackCount(); i++) {
            //獲取視頻所在軌道
            MediaFormat mediaFormat = videoExtractor.getTrackFormat(i);
            String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
            if (mime.startsWith(MEDIA_TYPE)) {
                trackIndex = i;
                break;
            }
        }
        return trackIndex;
    }

將緩衝區傳遞至解碼器

//將緩衝區傳遞至解碼器
    private boolean putBufferToCoder(MediaExtractor extractor, MediaCodec decoder, ByteBuffer[] inputBuffers) {
        boolean isMediaEOS = false;
        int inputBufferIndex = decoder.dequeueInputBuffer(TIMEOUT_US);
        if (inputBufferIndex >= 0) {
            ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
            int sampleSize = extractor.readSampleData(inputBuffer, 0);
            if (sampleSize < 0) {
                decoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                isMediaEOS = true;
                Log.v(TAG, "media eos");
            } else {
                decoder.queueInputBuffer(inputBufferIndex, 0, sampleSize, extractor.getSampleTime(), 0);
                extractor.advance();
            }
        }
        return isMediaEOS;
    }

音頻的部分相似,完整源碼請移步stefanJi/MediaPlaySimpleDemo

相關文章
相關標籤/搜索