任务目标
通过MediaCodec API实现视频的硬解
流程介绍
本例采用的视频格式为mp4,先把mp4格式的视频解码,然后通过SurfaceView进行播放。具体流程如下:
- 初始化SurfaceView,并实现SurfaceHolder.Callback接口
- 创建 MediaExtractor,设置视频源,通过MediaExtractor获取视频的MediaFormat
- 通过2获取到的MediaFormat创建解码器,并进行解码器的配置,开始解码
- 通过解码器不断获取缓冲区,并通过queueInputBuffer()发送解码数据
- 解码器调用dequeueOutputBuffer()获取解码数据并播放。
主要代码
1.同步方式
要放到线程中调用
initManager();
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
long startTime = System.currentTimeMillis();
while (!isDecodeFinish) {
int inputIndex = mDecodeMediaCodec.dequeueInputBuffer(10000);
if (inputIndex >= 0) {
ByteBuffer buffer = mDecodeMediaCodec.getInputBuffer(inputIndex);
int sampleSize = mExtractor.readSampleData(buffer, 0);
if (sampleSize < 0) {
mDecodeMediaCodec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
isDecodeFinish = true;
} else {
mDecodeMediaCodec.queueInputBuffer(inputIndex, 0, sampleSize, mExtractor.getSampleTime(), 0);
mExtractor.advance();
}
}
int outputIndex = mDecodeMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
while (bufferInfo.presentationTimeUs / 1000 > System.currentTimeMillis() - startTime) {
try {
sleep(10);//注意这儿,如果不加视频可能会播放很快
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (outputIndex >= 0) {
mDecodeMediaCodec.releaseOutputBuffer(outputIndex, true);
}
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
Log.d("decode", "BUFFER_FLAG_END_OF_STREAM");
break;
}
}
mDecodeMediaCodec.stop();
mDecodeMediaCodec.release();
mExtractor.release();
private void initManager() {
try {
mExtractor = new MediaExtractor();
mExtractor.setDataSource(mVideoPath); //设置视频源
for (int i = 0; i < mExtractor.getTrackCount(); i++) {
MediaFormat format = mExtractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("video/")) {
mExtractor.selectTrack(i);
mDecodeMediaCodec = MediaCodec.createDecoderByType(mime);
mDecodeMediaCodec.configure(format, mSurface, null, 0);
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
if (mDecodeMediaCodec == null) {
Log.e("decode", "MediaCodec is null");
}
mDecodeMediaCodec.start();
}
2.异步方式
初始化与同步方式相似,只不过在调用解码器的start()方法前设置解码回调setCallback(MediaCodec.CallBack),回调方法的主要代码如下:
/**
* 解码回调
*/
private MediaCodec.Callback codecCallback = new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
ByteBuffer buffer = codec.getInputBuffer(index);
int sampleSize = mExtractor.readSampleData(buffer, index);
if (sampleSize < 0) {
mDecoderCodec.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
mDecoderCodec.queueInputBuffer(index, 0, sampleSize, mExtractor.getSampleTime(), 0);
mExtractor.advance();
}
}
@Override
public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
try {
Thread.sleep(30); //不休眠播放很快
} catch (InterruptedException e) {
e.printStackTrace();
}
if (index >= 0) {
codec.releaseOutputBuffer(index, true);
}
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
release();
}
}
@Override
public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
Log.e("decoder", "error");
}
@Override
public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
Log.e("decoder", "Format Changed");
}
};
代码已上传GitHub