FFmpeg 中的 Android MediaCodec
2018-12-07 本文已影响21人
GeorgeMR
MediaCodec 类可以用来访问底层媒体编解码器,即编码器/解码器的组件。它是 Android 底层多媒体支持架构的一部分。
![](https://img.haomeiwen.com/i3911394/8fcb1775516c67cb.png)
一个编解码器处理输入数据以生成输出数据。它异步地处理数据,并使用一组输入和输出缓冲器。
调用的时候需要先初始化 MediaCodec 作为视频的编码器,然后只需要不停传入原始的 YUV 数据进入编码器就可以直接输出编码好的 h264 流。解码器则对应相反。
数据类型(Data Types)
编解码器对 3 种数据进行操作:压缩后的数据,原始音频数据和原始视频数据。可以使用 ByteBuffers 处理所有三种数据,但对原始视频数据,可以使用 Surface 提高编解码的性能。Surface 使用本地视频缓冲区而不是映射或复制到 ByteBuffers,因此,效率更高。(通常在使用 Surface 时无法访问原始视频数据,但可以使用 ImageReader 类访问不安全的解码(原始)视频帧)。
压缩缓冲区(Compressed Buffers)
输入缓冲器和输出缓冲器根据格式的类型来存放已压缩的数据。对于视频类型,这是一个单一的压缩后的视频帧。对于音频数据,通常是单个访问单元(一个编码后的音频片段通常包含几毫秒音频)。
FFmpeg 中的 MediaCodec
FFmpeg 中仅支持 Android MediaCodec 解码,本文主要介绍 FFmpeg 中关于 MediaCodec 解码流程。后面会介绍如何在 FFmpeg 中添加 MediaCodec 编码。
解码流程:
-
mediacodec_deocde_init
(初始化 MediaCodec 解码器)
![](https://img.haomeiwen.com/i3911394/a0dbd8f99ac7abf4.png)
stativ av_cold int mediacodec_decode_init(AVCodecContext *avctx)
{
// MediaCodec H.264 解码相关属性
MediaCodecH264DecContext *s = avctx->priv_data;
// 调用 jni 创建 MediaFormat
FFAMediaFormat *format = ff_AMediaFormat_new();
// 根据 codec_id 获取 mimeType
const char *codec_mime = "video/avc";
// 获取 AVCodecContext 的 Extreadata 中的 sps & pps,存入H.264文件的头部
ret = h264_set_extradata(avctx, format);
// 设置 MediaFormat 属性
ff_AMediaFormat_setxxx(format, "xxx", xxx);
ret = ff_mediacodec_dec_init(avctx, s->ctx)
}
-
mediacodec_decode_frame
(解码方法)FFmpeg 中 MediaCodec 解码.png
static int mediacodec_decode_frame(AVCodecContext *avctx, void *data, int *got_frame, AVPacket *avpkt)
{
// mediacodec 私有参数
MediaCodecH264DecContext *s = avctx->priv_data;
// 解码得到的数据
AVFrame *frame = data;
int ret;
// 将要解码的 AVPacket 写入 fifo
av_fifo_generic_write(s->fifo, &input_pkt, sizeof(input_pkt), NULL);
while (!*got_frame) {
/* prepare the input data */
if (s->buffered_pkt.size <= 0) {
// 从 fifo 读到 buffered_pkt
av_fifo_generic_read(s->fifo, &s->buffered_pkt, sizeof(s->buffered_pkt), NULL);
}
// 解码
ret = mediacodec_process_data(avctx, frame, got_frame, &s->buffered_pkt);
}
}
-
ff_mediacodec_dec_decode
(解码)ff_mediacodec_dec_decode.png
int ff_mediacodec_dec_decode(AVCodecContext *avctx, MediaCodecDecContext *s,
AVFrame *frame, int *got_frame,
AVPacket *pkt)
{
while(...) {
// 查找是否有可用的 输入缓冲区
index = ff_AMediaCodec_dequeueInputBuffer(codec, input_dequeue_timeout_us);
// 获取输入缓冲区
data = ff_AMediaCodec_getInputBuffer(codec, index, &size);
if (need_draining) {
// 发送 结束信号
status = ff_AMediaCodec_queueInputBuffer(codec, index, 0, 0, pts, flags);
} else {
// 传送 要解码的数据
status = ff_AMediaCodec_queueInputBuffer(codec, index, 0, size, pts, 0);
}
}
// 获取输出缓冲区
index = ff_AMediaCodec_dequeueOutputBuffer(codec, &info, output_dequeue_timeout_us);
if (index >= 0) {
if (info.size) {
if (s->surface) {
// 直接显示
ret = mediacodec_wrap_hw_buffer(avctx, s, index, &info, frame);
} else {
// 获取解码后的数据
data = ff_AMediaCodec_getOutputBuffer(codec, index, &size);
// 转换格式
ret = mediacodec_wrap_sw_buffer(avctx, s, data, size, index, &info, frame);
}
} else {
// 没有输出数据,释放缓冲区
status = ff_AMediaCodec_releaseOutputBuffer(codec, index, 0);
}else if (ff_AMediaCodec_infoOutputFormatChanged(codec, index)) {
// 输出格式已更改
// 删除当前输出格式
status = ff_AMediaFormat_delete(s->format);
// 使用新的输出格式
s->format = ff_AMediaCodec_getOutputFormat(codec);
} else if (ff_AMediaCodec_infoOutputBuffersChanged(codec, index)) {
// 输出缓冲区已更改
ff_AMediaCodec_cleanOutputBuffers(codec);
} else if (ff_AMediaCodec_infoTryAgainLater(codec, index)) {
// 超时
} else {
// 解码错误
av_log(avctx, AV_LOG_ERROR, "Failed to dequeue output buffer (status=%zd)\n", index);
return AVERROR_EXTERNAL;
}
}
}