標籤:步驟 ref creat tag mem err 資源 actor 輸入
Android提供了MediaPlayer播放器播放媒體檔案,其實MediaPlyer只是對Android Media包下的MediaCodec和MediaExtractor進行了封裝,方便使用。但是最好理解下Android媒體檔案的解碼,編碼和渲染流程。
使用android.media
包下的MediaCodec和MediaExtractor實現一個簡單的視頻解碼渲染。
使用到了:
視頻被播放主要分為以下步驟:
- 將資源載入到extractor
- 擷取視頻所在軌道
- 設定extractor選中視頻所在軌道
- 創將解碼視頻的MediaCodec,decoder
- 開始迴圈,直到視頻資源的末尾
- 將extractor中資源以一個單位填充進decoder的輸入緩衝區
- decoder將解碼之後的視頻填充到輸出緩衝區
- decoder釋放輸出緩衝區的同時,將緩衝區中資料渲染到surface
音訊播放類似,只多了AudioTrack部分,少了渲染到surface部分。
MediaCodec.releaseOutputBuffer(int outputBufferIndex, boolean render);
播放的控制,視頻和音頻各自擁有一個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; }
VideoThread
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
Android媒體解碼MediaCodec MediaExtractor學習