1. 简介
本文将重点介绍在 Android 平台上,WebRTC 是如何使用 MediaCodec 对视频数据进行编码,以及在整个编码过程中 webrtc native 与 java 的流程交互。
本篇开始会先回顾一下 Andorid MediaCodec 的概念和基础使用,然后再跟着问题去源码中分析。
2. MediaCodec 基础知识
MediaCodec 是 Android 提供的一个用于处理音频和视频数据的底层 API。它支持编码(将原始数据转换为压缩格式)和解码(将压缩数据转换回原始格式)的过程。MediaCodec 是自 Android 4.1(API 16)起引入的,(通常与MediaExtractor
、MediaSync
、MediaMuxer
、MediaCrypto
、 MediaDrm
、Image
、Surface
一起使用)。
以下是 MediaCodec 的一些关键概念和用法:
- 创建和配置 MediaCodec:首先,需要根据所需的编解码器类型(例如 H.264、VP8、Opus 等)创建一个 MediaCodec 实例。接下来,通过 MediaFormat 对象指定编解码器的一些参数,如分辨率、帧率、码率等。然后,使用
configure()
方法配置 MediaCodec。
try {
// 1\. 创建和配置 MediaCodec
MediaCodecInfo codecInfo = selectCodec(MIME_TYPE);
if (codecInfo == null) {
throw new RuntimeException("No codec found for " + MIME_TYPE);
}
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
encoder = MediaCodec.createByCodecName(codecInfo.getName());
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
encoder.start();
} catch (IOException e) {
throw new RuntimeException("Failed to initialize encoder", e);
}
- 输入和输出缓冲区:MediaCodec 有两个缓冲区队列,一个用于输入,另一个用于输出。输入缓冲区用于接收原始数据(例如从摄像头捕获的视频帧),输出缓冲区用于存储编码后的数据。在编解码过程中,需要将这些缓冲区填充或消费。
- 编码器工作模式:MediaCodec 支持两种工作模式,分别是同步和异步。在同步模式下,需要手动管理输入和输出缓冲区。在异步模式下,通过设置回调函数(
MediaCodec.Callback
),可以在编解码事件发生时自动通知应用程序。
同步:
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, …);
MediaFormat outputFormat = codec.getOutputFormat(); // option B
codec.start();
for (;;) {
int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
if (inputBufferId >= 0) {
ByteBuffer inputBuffer = codec.getInputBuffer(…);
// fill inputBuffer with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
int outputBufferId = codec.dequeueOutputBuffer(…);
if (outputBufferId >= 0) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
// bufferFormat is identical to outputFormat
// outputBuffer is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// Subsequent data will conform to new format.
// Can ignore if using getOutputFormat(outputBufferId)
outputFormat = codec.getOutputFormat(); // option B
}
}
codec.stop();
codec.release();
**异步(推荐使用):**
MediaCodec codec = MediaCodec.createByCodecName(name);
MediaFormat mOutputFormat; // member variable
codec.setCallback(new MediaCodec.Callback() {
@Override
void onInputBuf