Android硬件编解码-MediaCodec(C-Api)

在 Android 平台上,MediaCodec 提供了一套 C API(通常通过 NDK 使用)来进行视频和音频的编解码。这个 API 允许开发者直接在 C/C++ 层面操作编解码器,而不是通过 Java 层的 MediaCodec 类。使用 C API 可以更好地控制性能,特别是在需要处理高效率、低延迟的多媒体应用时。

MediaCodec 编解码流程

解码

创建解码器

使用 AMediaCodec_createDecoderByType 创建解码器实例。

获取解码器名称

1
2
3
4
5
6
7
8
9
10
11
12
std::string mime_type;
switch (src) {
case CodecType::CODEC_TYPE_H264:
mime_type = "video/avc";
break;
case CodecType::CODEC_TYPE_H265:
mime_type = "video/hevc";
break;
default:
break;
}
return mime_type;

设置解码格式参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
AMediaFormat* MediaCodecDecoderImpl::CreateMediaFormat(
const VideoInfo& video_info, const ExtraData& extra_data) {
std::string mime_type = CodecTypeConvert<CodecType, std::string>(video_info.codec_type);

AMediaFormat* format = AMediaFormat_new();
// 设置宽高,解码名称
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, video_info.width);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, video_info.height);
AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, mime_type.c_str());
// 设置输出的颜色格式
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, pix_fmt);
// 通过csd-0 csd-1 csd-2来设置vps sps pps数据
switch (video_info.codec_type) {
case CodecType::CODEC_TYPE_H264: {
auto* avc_ps_ = new avc::ParamSets();
himawari::avc::ParseExtraDataToParamSet(extra_data, *avc_ps_);
if (avc_ps_->sps_list.empty() || avc_ps_->pps_list.empty()) {
return nullptr;
}
AMediaFormat_setBuffer(format, "csd-0", avc_ps_->sps_list[0].data,
avc_ps_->sps_list[0].data_size);
AMediaFormat_setBuffer(format, "csd-1", avc_ps_->pps_list[0].data,
avc_ps_->pps_list[0].data_size);
delete avc_ps_;
} break;
case CodecType::CODEC_TYPE_H265: {
auto* hevc_ps = new hevc::ParamSets();
himawari::hevc::ParseExtraDataToParamSet(extra_data, *hevc_ps);
if (hevc_ps->vps_list.empty() || hevc_ps->sps_list.empty() ||
hevc_ps->pps_list.empty()) {
return nullptr;
}
AMediaFormat_setBuffer(format, "csd-0", hevc_ps->vps_list[0].data,
hevc_ps->vps_list[0].data_size);
AMediaFormat_setBuffer(format, "csd-1", hevc_ps->sps_list[0].data,
hevc_ps->sps_list[0].data_size);
AMediaFormat_setBuffer(format, "csd-2", hevc_ps->pps_list[0].data,
hevc_ps->pps_list[0].data_size);
delete hevc_ps;
} break;
default:
return nullptr;
}
return format;
}

创建解码器实例

1
2
3
4
5
6
7
8
9
// 获取解码器名称
std::string mime_type = CodecTypeConvert<CodecType, std::string>(video_info_.codec_type);
// 创建解码器实例
codec_ = AMediaCodec_createDecoderByType(mime_type.c_str());
// 设置解码格式参数
format_ = CreateMediaFormat(video_info_, extra_data);
// 配置解码器,并启动
AMediaCodec_configure(codec_, format_, nullptr, nullptr, 0);
AMediaCodec_start(codec_);

处理数据

AMediaCodec_dequeueInputBufferAMediaCodec_queueInputBufferAMediaCodec_dequeueOutputBuffer 等函数处理数据。

MediaCodec支持Annex-B格式的码流,不支持avc格式的码流,需要提前将packet的格式进行转换,sps pps也需要从avc的ExtraData中提取出来

输入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
signed int input_index;
do {
// 获取空闲的输入Buffer Index
input_index = AMediaCodec_dequeueInputBuffer(codec_, 1000);
} while (input_index < 0);

if (packet->byte_data) {
// 获取空闲的输入Buffer
uint8_t* buffer = AMediaCodec_getInputBuffer(codec_, input_index, nullptr);
if (!buffer) {
return HMError::FAILED;
}
// 将输入数据拷贝到输入Buffer中
memcpy(buffer, packet->byte_data, packet->data_size);
// 将准备好的输入Buffer Index通知给解码器处理
AMediaCodec_queueInputBuffer(codec_, input_index, 0, packet->data_size,
packet->pts, 0);
} else {
send_eos_ = true;
// 输入数据结束了,需要通知解码器end_of_stream,让解码器把剩余的帧都吐出来,送入空的一帧
AMediaCodec_queueInputBuffer(codec_, input_index, 0, 0, 0,
AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
}

获取输出数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
AMediaCodecBufferInfo info;
// 获取解码完毕的输出Buffer
ssize_t status = AMediaCodec_dequeueOutputBuffer(codec_, &info, 1000);
if (status >= 0) {
if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
// 最后一帧数据是空的,收到最后一帧表明所有帧都已经解完了
receive_eos_ = true;
return HMError::END_OF_STREAM;
}

uint8_t* buffer = AMediaCodec_getOutputBuffer(codec_, status, nullptr);
size_t size = info.size;
if (buffer != nullptr && size != 0) {
// 获取解码后的数据信息,这里只能获取到pts,dts无法获取,所以对于B帧还是不太友好
frame->timestamp = info.presentationTimeUs;
AMediaFormat_getInt64(format_, AMEDIAFORMAT_KEY_DURATION,
&frame->timespan);
AMediaFormat_getInt32(format_, AMEDIAFORMAT_KEY_WIDTH,
&frame->video_frame_info.width);
AMediaFormat_getInt32(format_, AMEDIAFORMAT_KEY_HEIGHT,
&frame->video_frame_info.height);
int pix_fmt;
AMediaFormat_getInt32(format_, AMEDIAFORMAT_KEY_COLOR_FORMAT, &pix_fmt);
frame->video_frame_info.pix_fmt =
PixFormatConvert<int, PixFormat>(pix_fmt);
...
// 处理解码后的帧数据
err = HMError::OK;
} else {
err = HMError::WAIT_AGAIN;
}

AMediaCodec_releaseOutputBuffer(codec_, status, false);
} else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
err = HMError::WAIT_AGAIN;
} else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
// format发生了改变,需要重新获取新的format
AMediaFormat_delete(format_);
format_ = AMediaCodec_getOutputFormat(codec_);
err = HMError::WAIT_AGAIN;
} else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
err = HMError::WAIT_AGAIN;
} else {
err = HMError::FAILED;
}
return err;

处理解码后的帧数据

Android平台给的解码数据都是packed格式的,不是planar格式,处理时需要注意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
uint8_t* src_data;
unsigned long len;
switch (dst->video_frame_info.pix_fmt) {
case PixFormat::PIX_FMT_NV12:
if (!dst->byte_data.data) {
FrameDataAlloc(&dst->byte_data, PixFormat::PIX_FMT_NV12, dst->video_frame_info.width,
dst->video_frame_info.height);
}
dst->byte_data.linesize[0] = dst->video_frame_info.width;
len = dst->byte_data.linesize[0] * dst->video_frame_info.height * sizeof(uint8_t);
src_data = src;
memcpy(dst->byte_data.data[0], src_data, len);
src += len;

dst->byte_data.linesize[1] = dst->video_frame_info.width;
len = dst->byte_data.linesize[1] * (dst->video_frame_info.height / 2) * sizeof(uint8_t);
src_data = src;
memcpy(dst->byte_data.data[1], src_data, len);
src += len;
break;
case PixFormat::PIX_FMT_I420:
if (!dst->byte_data.data) {
FrameDataAlloc(&dst->byte_data, PixFormat::PIX_FMT_I420, dst->video_frame_info.width,
dst->video_frame_info.height);
}
dst->byte_data.linesize[0] = dst->video_frame_info.width;
len = dst->byte_data.linesize[0] * dst->video_frame_info.height * sizeof(uint8_t);
src_data = src;
memcpy(dst->byte_data.data[0], src_data, len);
src += len;

dst->byte_data.linesize[1] = dst->video_frame_info.width / 2;
len = dst->byte_data.linesize[1] * (dst->video_frame_info.height / 2) * sizeof(uint8_t);
src_data = src;
memcpy(dst->byte_data.data[1], src_data, len);
src += len;

dst->byte_data.linesize[2] = dst->video_frame_info.width / 2;
len = dst->byte_data.linesize[2] * (dst->video_frame_info.height / 2) * sizeof(uint8_t);
src_data = src;
memcpy(dst->byte_data.data[2], src_data, len);
src += len;
break;
case PixFormat::PIX_FMT_ARGB:
if (!dst->byte_data.data) {
FrameDataAlloc(&dst->byte_data, PixFormat::PIX_FMT_ARGB, dst->video_frame_info.width,
dst->video_frame_info.height);
}
dst->byte_data.linesize[0] = dst->video_frame_info.width;
len = dst->byte_data.linesize[0] * dst->video_frame_info.height * sizeof(uint8_t);
src_data = src;
memcpy(dst->byte_data.data[0], src_data, len);
break;
default:
return dst;
}
return dst;

停止和释放解码器

调用 AMediaCodec_stopAMediaCodec_delete

1
2
3
4
5
6
7
if (format_) {
AMediaFormat_delete(format_);
}
if (codec_) {
AMediaCodec_stop(codec_);
AMediaCodec_delete(codec_);
}

编码

创建编码器

获取编码器名称

1
2
3
4
5
6
7
8
9
10
11
12
std::string mime_type;
switch (src) {
case CodecType::CODEC_TYPE_H264:
mime_type = "video/avc";
break;
case CodecType::CODEC_TYPE_H265:
mime_type = "video/hevc";
break;
default:
break;
}
return mime_type;

设置解码格式参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
AMediaFormat* MediaCodecDecoderImpl::CreateMediaFormat(
const VideoInfo& video_info, const ExtraData& extra_data) {
std::string mime_type = CodecTypeConvert<CodecType, std::string>(video_info.codec_type);

AMediaFormat* format = AMediaFormat_new();
// 设置宽高和颜色格式
AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, mime_type.c_str());
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, video_info.width);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, video_info.height);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, pix_fmt);
// 设置帧率 码率 GOP
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, video_info.bit_rate);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, video_info.fps);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, 100);
AMediaFormat_setInt32(format, "level", 1);
AMediaFormat_setInt32(format, "profile", 1);
}

创建编码器实例

使用 AMediaCodec_createCodecByNameAMediaCodec_createEncoderByType 创建一个编码器实例。

1
2
3
4
5
6
7
8
9
10
std::string mime_type = CodecTypeConvert<CodecType, std::string>(video_info_.codec_type);
// 创建编码器实例
codec_ = AMediaCodec_createEncoderByType(mime_type.c_str());

// 设置编码格式参数
format_ = CreateMediaFormat(video_info_);

// 设置编码器配置
AMediaCodec_configure(codec_, format_, nullptr, nullptr, AMEDIACODEC_CONFIGURE_FLAG_ENCODE);
AMediaCodec_start(codec_);

处理数据

MediaCodec支持Annex-B格式的码流,不支持avc格式的码流,输出的packet格式需要注意

输入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
signed int input_index;
do {
// 获取空闲的输入Buffer Index
input_index = AMediaCodec_dequeueInputBuffer(codec_, 1000);
} while (input_index < 0);

if (frame->byte_data.data) {
size_t size = 0;
// 获取空闲的输入Buffer
uint8_t* buffer = AMediaCodec_getInputBuffer(codec_, input_index, &size);
if (!buffer) {
return HMError::FAILED;
}

// 将输入数据拷贝到输入Buffer中
...
// 将准备好的输入Buffer Index通知给编码器处理
AMediaCodec_queueInputBuffer(codec_, input_index, 0, size, frame->timestamp,
0);
} else {
send_eos_ = true;
// 输入数据结束了,需要通知编码器end_of_stream,让编码器把剩余的帧都吐出来,送入空的一帧
AMediaCodec_queueInputBuffer(codec_, input_index, 0, 0, 0,
AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
}

将输入数据拷贝到输入Buffer中

Android平台接收的编码数据都是packed格式的,不是planar格式,处理时需要注意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
uint8_t* dst_data = dst;
unsigned long len;
switch (src->video_frame_info.pix_fmt) {
case PixFormat::PIX_FMT_NV12:
len = src->byte_data.linesize[0] * src->video_frame_info.height * sizeof(uint8_t);
memcpy(dst_data, src->byte_data.data[0], len);
dst_data += len;

len = src->byte_data.linesize[1] * (src->video_frame_info.height / 2) * sizeof(uint8_t);
memcpy(dst_data, src->byte_data.data[1], len);
dst_data += len;
break;
case PixFormat::PIX_FMT_I420:
len = src->byte_data.linesize[0] * src->video_frame_info.height * sizeof(uint8_t);
memcpy(dst_data, src->byte_data.data[0], len);
dst_data += len;

len = src->byte_data.linesize[1] * (src->video_frame_info.height / 2) * sizeof(uint8_t);
memcpy(dst_data, src->byte_data.data[1], len);
dst_data += len;

len = src->byte_data.linesize[2] * (src->video_frame_info.height / 2) * sizeof(uint8_t);
memcpy(dst_data, src->byte_data.data[2], len);
dst_data += len;
break;
case PixFormat::PIX_FMT_ARGB:
len = src->byte_data.linesize[0] * src->video_frame_info.height * sizeof(uint8_t);
memcpy(dst_data, src->byte_data.data[0], len);
break;
default:
return dst;
}
return dst;

获取输出数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
std::error_code err;
AMediaCodecBufferInfo info;
// 获取编码完毕的输出Buffer
ssize_t status = AMediaCodec_dequeueOutputBuffer(codec_, &info, 1000);
if (status >= 0) {
if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
// 最后一帧数据是空的,收到最后一帧表明所有帧都已经编码完成了
receive_eos_ = true;
return HMError::END_OF_STREAM;
}

uint8_t* buffer = AMediaCodec_getOutputBuffer(codec_, status, nullptr);
size_t size = info.size;
if (buffer != nullptr && size != 0) {
size_t len = size * sizeof(uint8_t);
if (!packet->byte_data) {
packet->byte_data = static_cast<uint8_t*>(malloc(len));
memset(packet->byte_data, 0, len);
}
// 获取编码后的数据信息,这里只能获取到pts,dts无法获取,所以对于B帧还是不太友好
memcpy(packet->byte_data, buffer, len);
packet->data_size = size;
packet->pts = info.presentationTimeUs;
packet->dts = packet->pts;
// dst->duration = src->duration;

err = HMError::OK;
if (!extra_data_.extradata) {
// 从第一帧中解析vps sps pps信息
}
} else {
err = HMError::WAIT_AGAIN;
}

AMediaCodec_releaseOutputBuffer(codec_, status, false);
} else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
err = HMError::WAIT_AGAIN;
} else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
err = HMError::WAIT_AGAIN;
} else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
err = HMError::WAIT_AGAIN;
} else {
err = HMError::FAILED;
}
return err;

停止和释放编码器

调用 AMediaCodec_stopAMediaCodec_delete 来停止和释放编码器资源。

1
2
3
4
5
6
7
8
9
if (format_) {
AMediaFormat_delete(format_);
format_ = nullptr;
}
if (codec_) {
AMediaCodec_stop(codec_);
AMediaCodec_delete(codec_);
codec_ = nullptr;
}