在 Android NDK 开发中,直接使用 C++ 调用 MediaCodec 通常是为了追求极致性能或与现有的 FFmpeg/OpenGL 管道集成。自 Android 4.1 (API 16) 起,libmediacodec.so 提供了底层 NDK 接口(NdkMediaCodec.h)。
以下是使用 C++ 实现视频编码与解码的核心流程。
1、环境配置
在 CMakeLists.txt 中需要链接 mediandk 库:
target_link_libraries(your_target mediandk log)
并在代码中包含头文件:
#include <media/NdkMediaCodec.h>
#include <media/NdkMediaFormat.h>
2、视频编码实现 (Video Encoding)
编码流程通常是将原始的 YUV 数据或 Surface 纹理转换为 H.264/H.265 比特流。
2.1、初始化编码器
// 1. 创建 MediaFormat
AMediaFormat* format = AMediaFormat_new();
AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "video/avc"); // H.264
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, 1920);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, 1080);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, 5000000); // 5Mbps
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, 30);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, 1); // 1s
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, 21); // COLOR_FormatYUV420SemiPlanar (NV12)
// 2. 创建并配置编码器
AMediaCodec* encoder = AMediaCodec_createEncoderByType("video/avc");
media_status_t status = AMediaCodec_configure(encoder, format, nullptr, nullptr, AMEDIACODEC_CONFIGURE_FLAG_ENCODE);
if (status == AMEDIA_OK) {
AMediaCodec_start(encoder);
}
2.2、编码循环
// 获取可用的输入 Buffer 索引
ssize_t inputIdx = AMediaCodec_dequeueInputBuffer(encoder, 10000); // 等待10ms
if (inputIdx >= 0) {
size_t bufSize;
uint8_t* buf = AMediaCodec_getInputBuffer(encoder, inputIdx, &bufSize);
// 将你的 YUV 数据拷贝进 buf...
// memcpy(buf, yuvData, yuvSize);
uint64_t pts = getNowInUs(); // 必须提供正确的微秒时间戳
AMediaCodec_queueInputBuffer(encoder, inputIdx, 0, yuvSize, pts, 0);
}
// 获取编码后的输出
AMediaCodecBufferInfo info;
ssize_t outIdx = AMediaCodec_dequeueOutputBuffer(encoder, &info, 10000);
if (outIdx >= 0) {
size_t outSize;
uint8_t* outBuf = AMediaCodec_getOutputBuffer(encoder, outIdx, &outSize);
// outBuf 此时即为 H.264 NALU 数据,可写入文件或发送
AMediaCodec_releaseOutputBuffer(encoder, outIdx, false);
}
3、视频解码实现 (Video Decoding)
解码流程是将 H.264/H.265 比特流转换为可显示的像素。
3.1、初始化解码器
如果你希望解码后直接显示在屏幕上,需要传入 ANativeWindow。
AMediaCodec* decoder = AMediaCodec_createDecoderByType("video/avc");
AMediaFormat* format = AMediaFormat_new();
AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "video/avc");
// 必须设置宽度和高度,或通过解析 SPS/PPS 获取
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, 1920);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, 1080);
// surface 是 ANativeWindow* 类型,由 Java 层的 Surface 传递到 C++
AMediaCodec_configure(decoder, format, surface, nullptr, 0);
AMediaCodec_start(decoder);
3.2、解码数据投递
ssize_t inIdx = AMediaCodec_dequeueInputBuffer(decoder, 10000);
if (inIdx >= 0) {
size_t bufSize;
uint8_t* buf = AMediaCodec_getInputBuffer(decoder, inIdx, &bufSize);
// 将 H.264 帧填充到 buf
// AMediaCodec_queueInputBuffer(decoder, inIdx, 0, frameSize, pts, 0);
}
AMediaCodecBufferInfo info;
ssize_t outIdx = AMediaCodec_dequeueOutputBuffer(decoder, &info, 10000);
if (outIdx >= 0) {
// 如果之前配置了 Surface,render 设为 true 即可直接在屏幕显示
AMediaCodec_releaseOutputBuffer(decoder, outIdx, true);
} elseif (outIdx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
// 可以在此处获取解码后的实际宽高、颜色空间等
auto newFormat = AMediaCodec_getOutputFormat(decoder);
}
4、核心注意事项
- 数据格式 (Color Format): 编码时,MediaCodec 对输入 YUV 的格式(如 NV12 vs YV12)非常挑剔。建议先调用
AMediaCodec_getOutputFormat检查硬件支持的格式。 - 异步 vs 同步 (Async API): 上面的示例是同步循环模式。Android 5.0+ 支持异步回调(
AMediaCodecOnAsyncCallbacks),在 C++ 中需要手动设置回调结构体,适合更复杂的音视频同步需求。 - QP 控制与画质: 与 iOS 不同,Android 的 NDK 接口并不总是直接暴露 QP 参数控制。在 NDK 层,通常通过
AMEDIAFORMAT_KEY_BITRATE_MODE设置为CQ(Constant Quality) 或VBR。
5、总结对比
| 特性 | 编码 (Encoder) | 解码 (Decoder) |
|---|---|---|
| 关键参数 | Bitrate, FrameRate, GOP | MIME type, Width/Height |
| 输入 | YUV 原始数据 / Surface | H.264/H.265 码流 |
| 输出 | H.264/H.265 码流 | 像素数据 / Surface 显示 |
| 难点 | 码率控制、关键帧强制插入 | SPS/PPS 的解析与头信息处理 |
学习和提升音视频开发技术,欢迎你加入我们的知识星球

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。