【音视频】C++ 实现视频编码与解码

在 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、核心注意事项

  1. 数据格式 (Color Format): 编码时,MediaCodec 对输入 YUV 的格式(如 NV12 vs YV12)非常挑剔。建议先调用 AMediaCodec_getOutputFormat 检查硬件支持的格式。
  2. 异步 vs 同步 (Async API): 上面的示例是同步循环模式。Android 5.0+ 支持异步回调(AMediaCodecOnAsyncCallbacks),在 C++ 中需要手动设置回调结构体,适合更复杂的音视频同步需求。
  3. QP 控制与画质: 与 iOS 不同,Android 的 NDK 接口并不总是直接暴露 QP 参数控制。在 NDK 层,通常通过 AMEDIAFORMAT_KEY_BITRATE_MODE 设置为 CQ (Constant Quality) 或 VBR

5、总结对比

特性编码 (Encoder)解码 (Decoder)
关键参数Bitrate, FrameRate, GOPMIME type, Width/Height
输入YUV 原始数据 / SurfaceH.264/H.265 码流
输出H.264/H.265 码流像素数据 / Surface 显示
难点码率控制、关键帧强制插入SPS/PPS 的解析与头信息处理

学习和提升音视频开发技术,欢迎你加入我们的知识星球

【音视频】C++ 实现视频编码与解码

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

(0)

相关推荐