Android提供了MediaPlayer播放器播放媒體文件,其實MediaPlyer只是對Android Media包下的MediaCodec和MediaExtractor進行了包裝,方便使用。但是最好理解下Android媒體文件的解碼,編碼和渲染流程。
Shape Of My Heart.mp4
使用android.media
包下的MediaCodec和MediaExtractor實現一個簡單的視頻解碼渲染。
使用到了:
- MediaCodec:負責媒體文件的編碼和解碼工作,內部方法均為native
- MediaExtractor:負責將指定類型的媒體文件從文件中找到軌道,並填充到MediaCodec的緩沖區中
- AudioTrack:負責將解碼之后的音頻播放
- SurfaceView:展示解碼之后的視頻
視頻被播放主要分為以下步驟:
- 將資源加載到extractor
- 獲取視頻所在軌道
- 設置extractor選中視頻所在軌道
- 創將解碼視頻的MediaCodec,decoder
- 開始循環,直到視頻資源的末尾
- 將extractor中資源以一個單位填充進decoder的輸入緩沖區
- decoder將解碼之后的視頻填充到輸出緩沖區
- decoder釋放輸出緩沖區的同時,將緩沖區中數據渲染到surface
音頻的播放類似,只多了AudioTrack部分,少了渲染到surface部分。
MediaCodec.releaseOutputBuffer(int outputBufferIndex, boolean render);
- 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;
}
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;
}
音頻的部分類似,完整源碼請移步jiyangg/MediaPlaySimpleDemo