概述
功能很简单,大致流程为:
1) MediaCodec 解码视频文件得到 YUV、PCM 数据
2) OpenGL 将 YUV 转为 RGB,并渲染到 Surface 上
3) OpenSL/AudoTrack 获取 PCM 数据并播放
需要的前置知识有:
1) YUV、PCM 等基础音视频知识,如 YUV 转 RGB
2) MediaCodec 的使用
3) OpenGL,包括 EGL、纹理等
4) OpenSL 或 AudioTrack 的使用
MediaCodec 解码
之前写过相关的博客 MediaCodec 实现硬件解码,大致流程和普通的解码类似,在编写视频播放器这个功能时,需要注意的地方有两个:
1) 监听解码流程
public interface OnDecodeListener {
void onImageDecoded(byte[] data);
void onSampleDecoded(byte[] data);
void onDecodeEnded();
}
也可以加一个 onDecodeError() 的接口,看需要扩展即可。
2) 播放和解码同步
因为视频数据量很大,不可能把解码后的 YUV 数据保存在一个队列里,再慢慢拿出来使用 OpenGL 渲染(很容易就 OOM 了),因此,必须控制解码的速率,最简单的控制方式是和播放同步,如下所示:
ByteBuffer outputBuffer = outputBuffers[outIndex];
outputBuffer.position(bufferInfo.offset);
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
byte[] data = new byte[bufferInfo.size];
outputBuffer.get(data);
if (mIsDecodeWithPts) {
if (startTime == 0) {
startTime = System.nanoTime();
} else {
passTime = (System.nanoTime() - startTime) / 1000;
if (passTime < bufferInfo.presentationTimeUs) {
TimeUnit.MICROSECONDS.sleep(bufferInfo.presentationTimeUs - passTime);
}
}
}
if (mediaType == HWCodec.MEDIA_TYPE_VIDEO && listener != null) {
listener.onImageDecoded(data);
} else if (listener != null) {
listener.onSampleDecoded(data);
}
OpenGL 渲染 YUV 数据
和渲染纹理的流程类似,不同的地方在于需要转换 YUV 数据为 RGB,而 YUV 数据又有 YUV420P、YUV420SP 等多种格式,因此在转换 RGB 之前,需要统一 YUV 数据的格式,这里使用的是 YUV420P。
YUV 数据格式之间的转换可以自己写,比如 YUV420SP 转换为 YUV420P,只需要把最后的 U、V 数据分别逐个放入到一个数组里即可,但考虑到视频裁剪、旋转,以及之后可能用到的各种 YUV 数据处理功能,因此这里引入了一个 libyuv 的库,使用非常简单:
Yuv* convertToI420(AVModel *model) {
if (!model || model->imageLen <= 0 || model->flag != MODEL_FLAG_VIDEO || model->width <= 0
|| model->height <= 0 || model->pixelFormat <= 0 || !model->image) {
LOGE("convertToARGB failed: invalid argument");
return nullptr;
}
Yuv *yuv = new Yuv(model->