FFmpeg 添加 MediaCodec 硬编解码
在移动多媒体处理领域,FFmpeg因其强大的编解码能力而备受推崇。然而,在Android平台上,FFmpeg的性能受限于其软件编解码能力。为了充分利用Android设备的硬件加速功能,我们可以将MediaCodec集成到FFmpeg中,从而实现硬编解码。本文将介绍如何在FFmpeg中集成MediaCodec,以及在Android平台上使用FFmpeg调用MediaCodec进行硬编解码。
实现
1、ffmpeg MediaCodec 编码实现
#include "libavutil/imgutils.h"
#include "libavutil/opt.h"
#include "libavutil/avassert.h"
#include "libavutil/pixfmt.h"
#include "libavcodec/internal.h"
#include "hlmediacodec.h"
#include "hlmediacodec_codec.h"
static av_cold int hlmediacodec_encode_init(AVCodecContext* avctx) {
AMediaFormat* format = NULL;
HLMediaCodecEncContext* ctx = avctx->priv_data;
int ret = 0;
do {
hi_logi(avctx, "%s %d init start globalHdr: [%d %s] rc_mode: %d", __FUNCTION__, __LINE__,
avctx->flags, (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) ? "yes" : "no", ctx->rc_mode);
ctx->stats.init_stamp = av_gettime_relative();
if (!(ctx->mediaformat = AMediaFormat_new())) {
ret = AVERROR_EXTERNAL;
hi_loge(avctx, "AMediaFormat_new fail");
break;
}
if ((ret = hlmediacodec_fill_format(avctx, ctx->mediaformat)) != 0) {
hi_loge(avctx, "%s %d mediacodec_encode_fill_format failed (%d)!", __FUNCTION__, __LINE__, ret);
break;
}
if ((ret = hlmediacodec_encode_header(avctx)) != 0) {
hi_loge(avctx, "%s %d mediacodec_encode_header failed (%d)!", __FUNCTION__, __LINE__, ret);
break;
}
const char* mime = ff_hlmediacodec_get_mime(avctx->codec_id);
if (!mime) {
ret = AVERROR_EXTERNAL;
hi_loge(avctx, "%s %d codec (%d) unsupport!", __FUNCTION__, __LINE__, avctx->codec_id);
break;
}
if (!(ctx->mediacodec = AMediaCodec_createEncoderByType(mime))) {
ret = AVERROR_EXTERNAL;
hi_loge(avctx, "%s %d AMediaCodec_createEncoderByType failed!", __FUNCTION__, __LINE__);
break;
}
hi_logi(avctx, "%s %d AMediaCodec_configure %s format %s", __FUNCTION__, __LINE__, mime, AMediaFormat_toString(ctx->mediaformat));
media_status_t status = AMEDIA_OK;
if ((status = AMediaCodec_configure(ctx->mediacodec, ctx->mediaformat, NULL, 0, HLMEDIACODEC_CONFIGURE_FLAG_ENCODE))) {
ret = AVERROR(EINVAL);
hi_loge(avctx, "%s %d AMediaCodec_configure failed (%d) !", __FUNCTION__, __LINE__, status);
break;
}
if ((status = AMediaCodec_start(ctx->mediacodec))) {
ret = AVERROR(EIO);
hi_loge(avctx, "%s %d AMediaCodec_start failed (%d) !", __FUNCTION__, __LINE__, status);
break;
}
ctx->inited = true;
} while (0);
hi_logi(avctx, "%s %d init ret (%d)", __FUNCTION__, __LINE__, ret);
return ret;
}
static int hlmediacodec_enc_send(AVCodecContext* avctx, const AVFrame *frame) {
HLMediaCodecEncContext* ctx = avctx->priv_data;
int ret = 0;
do {
if (ctx->flushed) {
ret = AVERROR_EOF;
break;
}
int in_times = ctx->in_timeout_times;
while (true) {
ssize_t in_bufidx = AMediaCodec_dequeueInputBuffer(ctx->mediacodec, ctx->in_timeout);
if (in_bufidx < 0) {
hi_logd(avctx, "%s %d AMediaCodec_dequeueInputBuffer codec: %p fail (%d) times: %d", __FUNCTION__, __LINE__, ctx->mediacodec, in_bufidx, in_times);
ctx->stats.in_fail_cnt ++;
if (in_times -- <= 0) {
hi_logd(avctx, "%s %d AMediaCodec_dequeueInputBuffer timeout", __FUNCTION__, __LINE__);
ret = AVERROR_EXTERNAL;
break;
}
continue;
}
size_t in_buffersize = 0;
uint8_t* in_buffer = AMediaCodec_getInputBuffer(ctx->mediacodec, in_bufidx, &in_buffersize);
if (!in_buffer) {
hi_loge(avctx, "%s %d AMediaCodec_getInputBuffer codec: %p fail", __FUNCTION__, __LINE__, ctx->mediacodec);
ctx->stats.in_fail_cnt ++;
ret = AVERROR_EXTERNAL;
break;
}
if (frame) {
size_t copy_size = in_buffersize;
int copy_ret = av_image_copy_to_buffer(in_buffer, in_buffersize, (const uint8_t **)frame->data,
frame->linesize, frame->format, frame->width, frame->height, 1);
if (copy_ret > 0) {
copy_size = copy_ret;
}
int64_t pts = av_rescale_q(frame->pts, avctx->time_base, AV_TIME_BASE_Q);
int64_t duration = av_rescale_q(frame->pkt_duration, avctx->time_base, AV_TIME_BASE_Q);
in_bufidx = AMediaCodec_queueInputBuffer(ctx->mediacodec, in_bufidx, 0, copy_size, pts, 0);
ctx->in_pts = pts;
ctx->in_duration = duration;
} else {
in_bufidx = AMediaCodec_queueInputBuffer(ctx->mediacodec, in_bufidx, 0, 0, 0, HLMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
ctx->in_pts += ctx->in_duration;
ctx->flushed = true;
hi_logi(avctx, "%s %d AMediaCodec_queueInputBuffer eof flush", __FUNCTION__, __LINE__);
}
if (in_bufidx != 0) {
ret = AVERROR_EXTERNAL;
ctx->stats.in_fail_cnt ++;
hi_loge(avctx,"AMediaCodec_queueInputBuffer fail (%d)", in_bufidx);
} else {
ctx->stats.in_succ_cnt ++;
}
break;
}
} while (false);
return ret;
}
static int hlmediacodec_enc_recv(AVCodecContext* avctx, AVPacket* pkt) {
HLMediaCodecEncContext* ctx = avctx->priv_data;
int ret = 0;
int ou_times = ctx->ou_timeout_times;
int ou_timeout = ctx->flushed ? ctx->eof_timeout : ctx->ou_timeout;
int try_times = 5;
while (try_times --) {
AMediaCodecBufferInfo bufferInfo = {0};
ssize_t ou_bufidx = AMediaCodec_dequeueOutputBuffer(ctx->mediacodec, &bufferInfo, ctx->ou_timeout);
hi_logt(avctx, "%s %d AMediaCodec_dequeueOutputBuffer ret (%d) times: %d offset: %d size: %d pts: %llu flags: %u", __FUNCTION__, __LINE__,
ou_bufidx, ctx->ou_timeout_times - ou_times, bufferInfo.offset, bufferInfo.size, bufferInfo.presentationTimeUs, bufferInfo.flags);
if (ou_bufidx >= 0) {
ctx->stats.ou_succ_cnt ++;
size_t ou_bufsize = 0;
uint8_t* ou_buf = AMediaCodec_getOutputBuffer(ctx->mediacodec, ou_bufidx, &ou_bufsize);
if (!ou_buf) {
hi_loge(avctx, "%s %d AMediaCodec_getOutputBuffer codec: %p fail", __FUNCTION__, __LINE__, ctx->mediacodec);
AMediaCodec_releaseOutputBuffer(ctx->mediacodec, ou_bufidx, false);
ret = AVERROR_EXTERNAL;
break;
}
int64_t pts = av_rescale_q(bufferInfo.presentationTimeUs, AV_TIME_BASE_Q, avctx->time_base);
hi_logt(avctx, "%s %d AMediaCodec OutputBuffer status: %d outsize: %u flags: %d offset: %d size: %d pts: [%lld %lld] nalu: [%x %x %x %x %x %x]",
__FUNCTION__, __LINE__, ou_bufidx, ou_bufsize, bufferInfo.flags, bufferInfo.offset, bufferInfo.size, bufferInfo.presentationTimeUs, pts, ou_buf[0], ou_buf[1], ou_buf[2], ou_buf[3], ou_buf[4], ou_buf[5]);
if ((ret = ff_alloc_packet2(avctx, pkt, bufferInfo.size, bufferInfo.size) < 0)) {
hi_loge(avctx, "ff_alloc_packet2 fail(%d)", ret);
AMediaCodec_releaseOutputBuffer(ctx->mediacodec, ou_bufidx, false);
ret = AVERROR_EXTERNAL;
break;
}
memcpy(pkt->data, ou_buf, bufferInfo.size);
pkt->dts = pkt->pts = pts;
ctx->stats.ou_succ_frame_cnt ++;
if (bufferInfo.flags & HLMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) {
ctx->stats.ou_succ_conf_cnt ++;
pkt->flags |= AV_PKT_FLAG_KEY;
}
if (bufferInfo.flags & HLMEDIACODEC_CONFIGURE_FLAG_ENCODE) {
ctx->stats.ou_succ_idr_cnt ++;
pkt->flags |= AV_PKT_FLAG_KEY;
}
if (bufferInfo.flags & HLMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
pkt->dts = pkt->pts = ctx->in_pts;
ctx->stats.ou_succ_end_cnt ++;
hi_logi(avctx, "%s %d AMediaCodec_dequeueOutputBuffer HLMEDIACODEC_BUFFER_FLAG_END_OF_STREAM", __FUNCTION__, __LINE__);
}
AMediaCodec_releaseOutputBuffer(ctx->mediacodec, ou_bufidx, false);
break;
}
-- ou_times;
ctx->stats.ou_fail_cnt ++;
if (ou_bufidx == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
hi_logd(avctx, "%s %d AMediaCodec_dequeueOutputBuffer AMEDIACODEC_INFO_TRY_AGAIN_LATER ", __FUNCTION__, __LINE__);
ctx->stats.ou_fail_again_cnt ++;
if (ou_times <= 0) {
ret = AVERROR(EAGAIN);
hi_loge(avctx, "%s %d AMediaCodec_dequeueOutputBuffer timeout ", __FUNCTION__, __LINE__);
break;
} else {
continue;
}
} else if (ou_bufidx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
ctx->stats.ou_fail_format_cnt ++;
AMediaFormat *format = AMediaCodec_getOutputFormat(ctx->mediacodec);
if (format) {
hi_logi(avctx, "%s %d AMediaCodec_dequeueOutputBuffer AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED %s",
__FUNCTION__, __LINE__, AMediaFormat_toString(format));
AMediaFormat_delete(format);
}
continue;
} else if (ou_bufidx == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
ctx->stats.ou_fail_buffer_cnt ++;
hi_logi(avctx, "%s %d AMediaCodec_dequeueOutputBuffer AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED",
__FUNCTION__, __LINE__);
continue;
} else {
hi_loge(avctx, "%s %d AMediaCodec_dequeueOutputBuffer fail (%d)", __FUNCTION__, __LINE__, ou_bufidx);
ctx->stats.ou_fail_oth_cnt ++;
ret = AVERROR(EAGAIN);
break;
}
}
return ret;
}
static int hlmediacodec_encode_send_frame(AVCodecContext *avctx, const AVFrame *frame) {
HLMediaCodecEncContext* ctx = avctx->priv_data;
if (!ctx->inited) {
return AVERROR_EXTERNAL;
}
return hlmediacodec_enc_send(avctx, frame);
}
static int hlmediacodec_encode_receive_packet(AVCodecContext* avctx, AVPacket* pkt) {
HLMediaCodecEncContext* ctx = avctx->priv_data;
if (!ctx->inited) {
return AVERROR_EXTERNAL;
}
return hlmediacodec_enc_recv(avctx, pkt);
}
static int hlmediacodec_encode(AVCodecContext *avctx, AVPacket *pkt,
const AVFrame *frame, int *got_packet) {
HLMediaCodecEncContext* ctx = avctx->priv_data;
if (!ctx->inited) {
return AVERROR_EXTERNAL;
}
int ret = 0;
if (!ctx->flushed) {
if ((ret = hlmediacodec_enc_send(avctx, frame)) != 0) {
return ret;
}
}
ret = hlmediacodec_enc_recv(avctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
*got_packet = 0;
} else if (ret >= 0) {
*got_packet = 1;
}
return ret;
}
static av_cold int hlmediacodec_encode_close(AVCodecContext *avctx) {
hi_logi(avctx, "%s %d", __FUNCTION__, __LINE__);
HLMediaCodecEncContext* ctx = avctx->priv_data;
ctx->flushed = false;
ctx->stats.uint_stamp = av_gettime_relative();
hlmediacodec_show_stats(avctx, ctx->stats);
if (ctx->mediacodec) {
AMediaCodec_stop(ctx->mediacodec);
AMediaCodec_delete(ctx->mediacodec);
ctx->mediacodec = NULL;
}
if (ctx->mediaformat) {
AMediaFormat_delete(ctx->mediaformat);
ctx->mediaformat = NULL;
}
return 0;
}
#define OFFSET(x) offsetof(HLMediaCodecEncContext, x)
#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
static const AVOption ff_hlmediacodec_enc_options[] = {
{ "rc-mode", "The bitrate mode to use", OFFSET(rc_mode), AV_OPT_TYPE_INT, { .i64 = HLMEDIACODEC_BITRATE_MODE_VBR }, HLMEDIACODEC_BITRATE_MODE_CQ, HLMEDIACODEC_BITRATE_MODE_CBR, VE, "rc_mode"},
{ "cq", "Constant quality", 0, AV_OPT_TYPE_CONST, {.i64 = HLMEDIACODEC_BITRATE_MODE_CQ}, INT_MIN, INT_MAX, VE, "rc_mode" },
{ "vbr", "Variable bitrate", 0, AV_OPT_TYPE_CONST, {.i64 = HLMEDIACODEC_BITRATE_MODE_VBR}, INT_MIN, INT_MAX, VE, "rc_mode" },
{ "cbr", "Constant bitrate", 0, AV_OPT_TYPE_CONST, {.i64 = HLMEDIACODEC_BITRATE_MODE_CBR}, INT_MIN, INT_MAX, VE, "rc_mode" },
{ "in_timeout", "in buff timeout", OFFSET(in_timeout), AV_OPT_TYPE_INT, {.i64 = HLMEDIACODEC_IN_SET_TIMEOUT_USEC}, HLMEDIACODEC_MIN_TIMEOUT_USEC, HLMEDIACODEC_MAX_TIMEOUT_USEC, VE },
{ "ou_timeout", "ou buff timeout", OFFSET(ou_timeout), AV_OPT_TYPE_INT, {.i64 = HLMEDIACODEC_OU_SET_TIMEOUT_USEC}, HLMEDIACODEC_MIN_TIMEOUT_USEC, HLMEDIACODEC_MAX_TIMEOUT_USEC, VE },
{ "eof_timeout", "eof buff timeout", OFFSET(eof_timeout), AV_OPT_TYPE_INT, {.i64 = HLMEDIACODEC_EOF_SET_TIMEOUT_USEC}, HLMEDIACODEC_MIN_TIMEOUT_USEC, HLMEDIACODEC_MAX_TIMEOUT_USEC, VE },
{ "in_timeout_times", "in buff timeout times", OFFSET(in_timeout_times), AV_OPT_TYPE_INT, {.i64 = HLMEDIACODEC_IN_SET_TIMEOUT_TIMES}, HLMEDIACODEC_MIN_TIMEOUT_TIMES, HLMEDIACODEC_MAX_TIMEOUT_TIMES, VE },
{ "ou_timeout_times", "ou buff timeout times", OFFSET(ou_timeout_times), AV_OPT_TYPE_INT, {.i64 = HLMEDIACODEC_ENC_OU_SET_TIMEOUT_TIMES}, HLMEDIACODEC_MIN_TIMEOUT_TIMES, HLMEDIACODEC_MAX_TIMEOUT_TIMES, VE },
{ NULL },
};
#define DECLARE_HLMEDIACODEC_VCLASS(short_name) \
static const AVClass ff_##short_name##_hlmediacodec_enc_class = { \
.class_name = #short_name "_hlmediacodec", \
.item_name = av_default_item_name, \
.option = ff_hlmediacodec_enc_options, \
.version = LIBAVUTIL_VERSION_INT, \
};
#define DECLARE_HLMEDIACODEC_ENC(short_name, full_name, codec_id, codec_type) \
DECLARE_HLMEDIACODEC_VCLASS(short_name) \
AVCodec ff_##short_name##_hlmediacodec_encoder = { \
.name = #short_name "_hlmediacodec", \
.long_name = full_name " (Ffmpeg MediaCodec NDK)", \
.type = codec_type, \
.id = codec_id, \
.priv_class = &ff_##short_name##_hlmediacodec_enc_class, \
.priv_data_size = sizeof(HLMediaCodecEncContext), \
.init = hlmediacodec_encode_init, \
.encode2 = hlmediacodec_encode, \
.close = hlmediacodec_encode_close, \
.capabilities = AV_CODEC_CAP_DELAY, \
.caps_internal = FF_CODEC_CAP_INIT_THREADSAFE | FF_CODEC_CAP_INIT_CLEANUP, \
.pix_fmts = (const enum AVPixelFormat[]){AV_PIX_FMT_NV12, AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE}, \
.wrapper_name = "hlmediacodec", \
}; \
#ifdef CONFIG_H264_HLMEDIACODEC_ENCODER
DECLARE_HLMEDIACODEC_ENC(h264, "H.264", AV_CODEC_ID_H264, AVMEDIA_TYPE_VIDEO)
#endif
2、ffmpeg MediaCodec 解码
#include "libavutil/opt.h"
#include "libavutil/buffer_internal.h"
#include "libavutil/avassert.h"
#include "libavcodec/avcodec.h"
#include "libavcodec/decode.h"
#include "libavcodec/internal.h"
#include "libavcodec/h264.h"
#include "libavcodec/bsf.h"
#include "hlmediacodec.h"
#include "hlmediacodec_codec.h"
static av_cold int hlmediacodec_decode_init(AVCodecContext *avctx) {
hi_logi(avctx, "%s %d codecId: %d thread_cnt: %d width: %d height: %d pix_fmt: %d",
__FUNCTION__, __LINE__, avctx->codec_id, avctx->thread_count, avctx->width, avctx->height, avctx->pix_fmt);
HLMediaCodecDecContext *ctx = avctx->priv_data;
int ret = 0;
do {
ctx->stats.init_stamp = av_gettime_relative();
int buff_size = hlmediacodec_get_buffer_size(avctx, 1);
if (buff_size <= 0) {
ret = AVERROR_EXTERNAL;
hi_loge(avctx, "hlmediacodec_get_buffer_size fail");
break;
}
if (!(ctx->buffer = av_malloc(buff_size))) {
ret = AVERROR_EXTERNAL;
hi_loge(avctx, "av_malloc fail");
break;
}
ctx->buffer_size = buff_size;
if (!(ctx->mediaformat = AMediaFormat_new())) {
ret = AVERROR_EXTERNAL;
hi_loge(avctx, "AMediaFormat_new fail");
break;
}
const char* mime = ff_hlmediacodec_get_mime(avctx->codec_id);
if (!mime) {
ret = AVERROR_PROTOCOL_NOT_FOUND;
hi_loge(avctx, "ff_hlmediacodec_get_mime fail (%d)", avctx->codec_id);
break;
}
if (!(ctx->mediacodec = AMediaCodec_createDecoderByType(mime))) {
ret = AVERROR_EXTERNAL;
hi_loge(avctx, "AMediaCodec_createDecoderByType fail (%s)", mime);
break;
}
if ((ret = hlmediacodec_fill_format(avctx, ctx->mediaformat))) {
break;
}
hi_logi(avctx, "AMediaCodec_configure %s format: %s", mime, AMediaFormat_toString(ctx->mediaformat));
media_status_t status = AMEDIA_OK;
if ((status = AMediaCodec_configure(ctx->mediacodec, ctx->mediaformat, NULL, NULL, 0))) {
ret = AVERROR_EXTERNAL;
hi_loge(avctx, "AMediaCodec_configure fail (%d)", status);
break;
}
if ((status = AMediaCodec_start(ctx->mediacodec))) {
ret = AVERROR_EXTERNAL;
hi_loge(avctx, "AMediaCodec_start fail (%d)", status);
break;
}
ctx->inited = true;
} while (false);
hi_logi(avctx, "%s %d init codec: %p ret (%d)", __FUNCTION__, __LINE__, ctx->mediacodec, ret);
return ret;
}
static int hlmediacodec_dec_send(AVCodecContext *avctx) {
HLMediaCodecDecContext *ctx = avctx->priv_data;
int ret = 0;
do {
if (ctx->flushed) {
ret = AVERROR_EOF;
break;
}
int get_ret = ff_decode_get_packet(avctx, &ctx->packet);
hi_logd(avctx, "%s %d ff_decode_get_packet ret (%d)", __FUNCTION__, __LINE__, get_ret);
if (get_ret != 0) {
ctx->stats.get_fail_cnt ++;
if (get_ret == AVERROR_EOF) {
ctx->flushed = true;// flush
hi_logd(avctx, "%s %d ff_decode_get_packet eof", __FUNCTION__, __LINE__);
} else {
ret = AVERROR(EAGAIN);
hi_logd(avctx, "%s %d ff_decode_get_packet fail (%d)", __FUNCTION__, __LINE__, get_ret);
break;
}
} else {
ctx->stats.get_succ_cnt ++;
}
int in_times = ctx->in_timeout_times;
while (true) {
if (in_times -- <= 0) {
hi_logd(avctx, "%s %d AMediaCodec_dequeueInputBuffer timeout", __FUNCTION__, __LINE__);
ret = AVERROR_EXTERNAL;
break;
}
ssize_t in_bufidx = AMediaCodec_dequeueInputBuffer(ctx->mediacodec, ctx->in_timeout);
hi_logt(avctx, "%s %d AMediaCodec_dequeueInputBuffer ret (%d) times: %d getret: %d", __FUNCTION__, __LINE__, in_bufidx, ctx->in_timeout_times - in_times, get_ret);
if (in_bufidx < 0) {
hi_logd(avctx, "%s %d AMediaCodec_dequeueInputBuffer codec: %p fail (%d)", __FUNCTION__, __LINE__, ctx->mediacodec, in_bufidx);
ctx->stats.in_fail_cnt ++;
continue;
}
size_t in_buffersize = 0;
uint8_t* in_buffer = AMediaCodec_getInputBuffer(ctx->mediacodec, in_bufidx, &in_buffersize);
if (!in_buffer) {
hi_loge(avctx, "%s %d AMediaCodec_getInputBuffer codec: %p fail", __FUNCTION__, __LINE__, ctx->mediacodec);
ctx->stats.in_fail_cnt ++;
ret = AVERROR_EXTERNAL;
break;
}
if (!ctx->flushed) {
if (ctx->packet.size > in_buffersize) {
hi_loge(avctx, "%s %d AMediaCodec_queueInputBuffer codec: %p fail (%u %u)", __FUNCTION__, __LINE__, ctx->mediacodec, ctx->packet.size, in_buffersize);
ret = AVERROR_EXTERNAL;
ctx->stats.in_fail_cnt ++;
break;
}
memcpy(in_buffer, ctx->packet.data, ctx->packet.size);
in_bufidx = AMediaCodec_queueInputBuffer(ctx->mediacodec, in_bufidx, 0, ctx->packet.size, ctx->packet.pts, 0);
ctx->in_pts = ctx->packet.pts;
ctx->in_duration = ctx->packet.duration;
} else {
in_bufidx = AMediaCodec_queueInputBuffer(ctx->mediacodec, in_bufidx, 0, 0, 0, HLMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
ctx->in_pts += ctx->in_duration;
hi_logi(avctx, "%s %d AMediaCodec_queueInputBuffer eof flush", __FUNCTION__, __LINE__);
}
if (in_bufidx != 0) {
ret = AVERROR_EXTERNAL;
ctx->stats.in_fail_cnt ++;
hi_loge(avctx,"AMediaCodec_queueInputBuffer fail (%d)", in_bufidx);
} else {
ctx->stats.in_succ_cnt ++;
}
break;
}
} while (false);
av_packet_unref(&ctx->packet);
return ret;
}
static int hlmediacodec_dec_recv(AVCodecContext *avctx, AVFrame* frame) {
HLMediaCodecDecContext *ctx = avctx->priv_data;
int ret = 0;
int ou_times = ctx->ou_timeout_times;
int ou_timeout = ctx->flushed ? ctx->eof_timeout : ctx->ou_timeout;
while (true) {
-- ou_times;
AMediaCodecBufferInfo bufferInfo = {0};
ssize_t ou_bufidx = AMediaCodec_dequeueOutputBuffer(ctx->mediacodec, &bufferInfo, ou_timeout);
hi_logt(avctx, "%s %d AMediaCodec_dequeueOutputBuffer ret (%d) times: %d offset: %d size: %d pts: %llu flags: %u", __FUNCTION__, __LINE__,
ou_bufidx, ctx->ou_timeout_times - ou_times, bufferInfo.offset, bufferInfo.size, bufferInfo.presentationTimeUs, bufferInfo.flags);
if (ou_bufidx >= 0) {
ctx->stats.ou_succ_cnt ++;
size_t ou_bufsize = 0;
uint8_t* ou_buf = AMediaCodec_getOutputBuffer(ctx->mediacodec, ou_bufidx, &ou_bufsize);
if (!ou_buf) {
hi_loge(avctx, "%s %d AMediaCodec_getOutputBuffer codec: %p fail", __FUNCTION__, __LINE__, ctx->mediacodec);
AMediaCodec_releaseOutputBuffer(ctx->mediacodec, ou_bufidx, false);
ret = AVERROR_EXTERNAL;
break;
}
if ((ret = ff_decode_frame_props(avctx, frame)) < 0) {
hi_loge(avctx, "ff_decode_frame_props fail(%d)", ret);
AMediaCodec_releaseOutputBuffer(ctx->mediacodec, ou_bufidx, false);
ret = AVERROR_EXTERNAL;
break;
}
if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
frame->width = avctx->width;
frame->height = avctx->height;
} else if (avctx->codec_type == AVMEDIA_TYPE_AUDIO) {
frame->nb_samples = avctx->frame_size;
}
if ((ret = ff_get_buffer(avctx, frame, 0))) {
hi_loge(avctx, "ff_get_buffer fail(%d)", ret);
AMediaCodec_releaseOutputBuffer(ctx->mediacodec, ou_bufidx, false);
ret = AVERROR_EXTERNAL;
break;
}
if (bufferInfo.size) {
if (ctx->buffer_size < bufferInfo.size) {
if (ctx->buffer) {
av_free(ctx->buffer);
ctx->buffer = NULL;
ctx->buffer_size = 0;
}
ctx->buffer = av_malloc(bufferInfo.size);
ctx->buffer_size = bufferInfo.size;
hi_logi(avctx, "%s %d av_malloc %u", __FUNCTION__, __LINE__, ctx->buffer_size);
}
memcpy(ctx->buffer, ou_buf, bufferInfo.size);
uint32_t remain_size = ctx->buffer_size - bufferInfo.size;
if (remain_size > 0) {
memset(ctx->buffer + bufferInfo.size, 0, remain_size);
}
}
AMediaCodec_releaseOutputBuffer(ctx->mediacodec, ou_bufidx, false);
ctx->stats.ou_succ_frame_cnt ++;
if (bufferInfo.flags & HLMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) {
ctx->stats.ou_succ_conf_cnt ++;
}
if (bufferInfo.flags & HLMEDIACODEC_CONFIGURE_FLAG_ENCODE) {
ctx->stats.ou_succ_idr_cnt ++;
}
frame->pts = frame->pkt_pts = frame->pkt_dts = bufferInfo.presentationTimeUs;
frame->pkt_duration = ctx->in_duration;
if (bufferInfo.flags & HLMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
frame->pts = frame->pkt_pts = frame->pkt_dts = ctx->in_pts;
ctx->stats.ou_succ_end_cnt ++;
hi_logi(avctx, "%s %d AMediaCodec_dequeueOutputBuffer HLMEDIACODEC_BUFFER_FLAG_END_OF_STREAM", __FUNCTION__, __LINE__);
ret = AVERROR_EOF;
break;
}
ret = hlmediacodec_decode_buffer_to_frame(avctx, bufferInfo, frame);
break;
}
ctx->stats.ou_fail_cnt ++;
if (ou_bufidx == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
hi_logd(avctx, "%s %d AMediaCodec_dequeueOutputBuffer AMEDIACODEC_INFO_TRY_AGAIN_LATER ", __FUNCTION__, __LINE__);
ctx->stats.ou_fail_again_cnt ++;
if (ou_times <= 0) {
ret = AVERROR(EAGAIN);
hi_loge(avctx, "%s %d AMediaCodec_dequeueOutputBuffer timeout ", __FUNCTION__, __LINE__);
break;
}
continue;
} else if (ou_bufidx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
hi_logi(avctx, "%s %d AMediaCodec_dequeueOutputBuffer AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED ", __FUNCTION__, __LINE__);
ctx->stats.ou_fail_format_cnt ++;
AMediaFormat* format = AMediaCodec_getOutputFormat(ctx->mediacodec);
if (format) {
hi_logi(avctx, "%s %d AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED %s", __FUNCTION__, __LINE__, AMediaFormat_toString(format));
hlmediacodec_fill_context(format, avctx);
AMediaFormat_delete(format);
}
continue;
} else if (ou_bufidx == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
hi_logi(avctx, "%s %d AMediaCodec_dequeueOutputBuffer AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED", __FUNCTION__, __LINE__);
ctx->stats.ou_fail_buffer_cnt ++;
continue;
} else {
hi_loge(avctx, "%s %d AMediaCodec_dequeueOutputBuffer fail (%d)", __FUNCTION__, __LINE__, ou_bufidx);
ctx->stats.ou_fail_oth_cnt ++;
ret = AVERROR(EAGAIN);
break;
}
}
return ret;
}
static av_cold int hlmediacodec_receive_frame(AVCodecContext *avctx, AVFrame* frame) {
HLMediaCodecDecContext *ctx = avctx->priv_data;
if (!ctx->inited) {
return AVERROR_EXTERNAL;
}
int ret = 0;
if (!ctx->flushed) {
if ((ret = hlmediacodec_dec_send(avctx)) != 0) {
return ret;
}
}
return hlmediacodec_dec_recv(avctx, frame);
}
static av_cold void hlmediacodec_decode_flush(AVCodecContext *avctx) {
hi_logi(avctx, "%s %d", __FUNCTION__, __LINE__);
HLMediaCodecDecContext *ctx = avctx->priv_data;
if (!ctx->inited) {
return;
}
ctx->flushed = false;
ctx->inited = false;
if (ctx->mediacodec) {
AMediaCodec_flush(ctx->mediacodec);
AMediaCodec_stop(ctx->mediacodec);
AMediaCodec_delete(ctx->mediacodec);
ctx->mediacodec = NULL;
}
const char* mime = ff_hlmediacodec_get_mime(avctx->codec_id);
if (!mime) {
hi_loge(avctx, "ff_hlmediacodec_get_mime fail (%d)", avctx->codec_id);
return;
}
if (!(ctx->mediacodec = AMediaCodec_createDecoderByType(mime))) {
hi_loge(avctx, "AMediaCodec_createDecoderByType fail (%s)", mime);
return;
}
media_status_t status = AMEDIA_OK;
if ((status = AMediaCodec_configure(ctx->mediacodec, ctx->mediaformat, NULL, NULL, 0))) {
hi_loge(avctx, "AMediaCodec_configure fail (%d)", status);
return;
}
if ((status = AMediaCodec_start(ctx->mediacodec))) {
hi_loge(avctx, "AMediaCodec_start fail (%d)", status);
return;
}
ctx->inited = true;
}
static av_cold int hlmediacodec_decode_close(AVCodecContext *avctx) {
hi_logi(avctx, "%s %d", __FUNCTION__, __LINE__);
HLMediaCodecDecContext *ctx = avctx->priv_data;
ctx->flushed = false;
ctx->stats.uint_stamp = av_gettime_relative();
hlmediacodec_show_stats(avctx, ctx->stats);
if (ctx->mediacodec) {
AMediaCodec_flush(ctx->mediacodec);
AMediaCodec_stop(ctx->mediacodec);
AMediaCodec_delete(ctx->mediacodec);
ctx->mediacodec = NULL;
}
if (ctx->mediaformat) {
AMediaFormat_delete(ctx->mediaformat);
ctx->mediaformat = NULL;
}
av_packet_unref(&ctx->packet);
if (ctx->buffer) {
av_free(ctx->buffer);
ctx->buffer = NULL;
ctx->buffer_size = 0;
}
ctx->inited = false;
return 0;
}
static const AVCodecHWConfigInternal *const hlmediacodec_hw_configs[] = {
&(const AVCodecHWConfigInternal) {
.public = {
.pix_fmt = AV_PIX_FMT_MEDIACODEC,
.methods = AV_CODEC_HW_CONFIG_METHOD_AD_HOC |
AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX,
.device_type = AV_HWDEVICE_TYPE_MEDIACODEC,
},
.hwaccel = NULL,
},
NULL
};
#define OFFSET(x) offsetof(HLMediaCodecDecContext, x)
#define VD AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM
static const AVOption ff_hlmediacodec_dec_options[] = {
{ "in_timeout", "in buff timeout", OFFSET(in_timeout), AV_OPT_TYPE_INT, {.i64 = HLMEDIACODEC_IN_SET_TIMEOUT_USEC}, HLMEDIACODEC_MIN_TIMEOUT_USEC, HLMEDIACODEC_MAX_TIMEOUT_USEC, VD },
{ "ou_timeout", "ou buff timeout", OFFSET(ou_timeout), AV_OPT_TYPE_INT, {.i64 = HLMEDIACODEC_OU_SET_TIMEOUT_USEC}, HLMEDIACODEC_MIN_TIMEOUT_USEC, HLMEDIACODEC_MAX_TIMEOUT_USEC, VD },
{ "eof_timeout", "eof buff timeout", OFFSET(eof_timeout), AV_OPT_TYPE_INT, {.i64 = HLMEDIACODEC_EOF_SET_TIMEOUT_USEC}, HLMEDIACODEC_MIN_TIMEOUT_USEC, HLMEDIACODEC_MAX_TIMEOUT_USEC, VD },
{ "in_timeout_times", "in buff timeout times", OFFSET(in_timeout_times), AV_OPT_TYPE_INT, {.i64 = HLMEDIACODEC_IN_SET_TIMEOUT_TIMES}, HLMEDIACODEC_MIN_TIMEOUT_TIMES, HLMEDIACODEC_MAX_TIMEOUT_TIMES, VD },
{ "ou_timeout_times", "ou buff timeout times", OFFSET(ou_timeout_times), AV_OPT_TYPE_INT, {.i64 = HLMEDIACODEC_DEC_OU_SET_TIMEOUT_TIMES}, HLMEDIACODEC_MIN_TIMEOUT_TIMES, HLMEDIACODEC_MAX_TIMEOUT_TIMES, VD },
{ NULL }
};
#define DECLARE_HLMEDIACODEC_VCLASS(short_name) \
static const AVClass ff_##short_name##_hlmediacodec_dec_class = { \
.class_name = #short_name "_hlmediacodec", \
.item_name = av_default_item_name, \
.option = ff_hlmediacodec_dec_options, \
.version = LIBAVUTIL_VERSION_INT, \
};
#define DECLARE_HLMEDIACODEC_DEC(short_name, full_name, codec_id, codec_type, bsf) \
DECLARE_HLMEDIACODEC_VCLASS(short_name) \
AVCodec ff_##short_name##_hlmediacodec_decoder = { \
.name = #short_name "_hlmediacodec", \
.long_name = full_name " (Ffmpeg MediaCodec NDK)", \
.type = codec_type, \
.id = codec_id, \
.priv_class = &ff_##short_name##_hlmediacodec_dec_class, \
.priv_data_size = sizeof(HLMediaCodecDecContext), \
.init = hlmediacodec_decode_init, \
.receive_frame = hlmediacodec_receive_frame, \
.flush = hlmediacodec_decode_flush, \
.close = hlmediacodec_decode_close, \
.capabilities = AV_CODEC_CAP_DELAY | FF_CODEC_CAP_INIT_CLEANUP | AV_CODEC_CAP_AVOID_PROBING | AV_CODEC_CAP_HARDWARE, \
.caps_internal = FF_CODEC_CAP_SETS_PKT_DTS, \
.bsfs = bsf, \
.hw_configs = hlmediacodec_hw_configs, \
.wrapper_name = "hlmediacodec", \
}; \
#ifdef CONFIG_H264_HLMEDIACODEC_DECODER
DECLARE_HLMEDIACODEC_DEC(h264, "H.264", AV_CODEC_ID_H264, AVMEDIA_TYPE_VIDEO, "h264_mp4toannexb")
#endif
#ifdef CONFIG_HEVC_HLMEDIACODEC_DECODER
DECLARE_HLMEDIACODEC_DEC(hevc, "H.265", AV_CODEC_ID_HEVC, AVMEDIA_TYPE_VIDEO, "hevc_mp4toannexb")
#endif
#ifdef CONFIG_MPEG4_HLMEDIACODEC_DECODER
DECLARE_HLMEDIACODEC_DEC(mpeg4, "MPEG-4", AV_CODEC_ID_MPEG4, AVMEDIA_TYPE_VIDEO, NULL)
#endif
#ifdef CONFIG_VP8_HLMEDIACODEC_DECODER
DECLARE_HLMEDIACODEC_DEC(vp8, "VP8", AV_CODEC_ID_VP8, AVMEDIA_TYPE_VIDEO, NULL)
#endif
#ifdef CONFIG_VP9_HLMEDIACODEC_DECODER
DECLARE_HLMEDIACODEC_DEC(vp9, "VP9", AV_CODEC_ID_VP9, AVMEDIA_TYPE_VIDEO, NULL)
#endif
#ifdef CONFIG_AAC_HLMEDIACODEC_DECODER
DECLARE_HLMEDIACODEC_DEC(aac, "AAC", AV_CODEC_ID_AAC, AVMEDIA_TYPE_AUDIO, "aac_adtstoasc")
#endif
#ifdef CONFIG_MP3_HLMEDIACODEC_DECODER
DECLARE_HLMEDIACODEC_DEC(mp3, "MP3", AV_CODEC_ID_MP3, AVMEDIA_TYPE_AUDIO, NULL)
#endif
3、修改 libavcodec Makefile,在文件末尾加入下面的内容
HI_CODEC := $(SRC_PATH)/../hilive/libavcodec
OBJS-$(CONFIG_HLMEDIACODEC) += $(HI_CODEC)/hlmediacodec.o $(HI_CODEC)/hlmediacodec_codec.o
OBJS-$(CONFIG_AAC_HLMEDIACODEC_DECODER) += $(HI_CODEC)/hlmediacodec_dec.o
OBJS-$(CONFIG_MP3_HLMEDIACODEC_DECODER) += $(HI_CODEC)/hlmediacodec_dec.o
OBJS-$(CONFIG_H264_HLMEDIACODEC_DECODER) += $(HI_CODEC)/hlmediacodec_dec.o
OBJS-$(CONFIG_H264_HLMEDIACODEC_ENCODER) += $(HI_CODEC)/hlmediacodec_enc.o
OBJS-$(CONFIG_HEVC_HLMEDIACODEC_DECODER) += $(HI_CODEC)/hlmediacodec_dec.o
OBJS-$(CONFIG_HEVC_HLMEDIACODEC_ENCODER) += $(HI_CODEC)/hlmediacodec_enc.o
OBJS-$(CONFIG_MPEG4_HLMEDIACODEC_DECODER) += $(HI_CODEC)/hlmediacodec_dec.o
OBJS-$(CONFIG_VP8_HLMEDIACODEC_DECODER) += $(HI_CODEC)/hlmediacodec_dec.o
OBJS-$(CONFIG_VP9_HLMEDIACODEC_DECODER) += $(HI_CODEC)/hlmediacodec_dec.o
SKIPHEADERS-$(CONFIG_HLMEDIACODEC) += $(HI_CODEC)/hlmediacodec.h $(HI_CODEC)/hlmediacodec_codec.h
代码&编译脚本
ffmpeg 库下载
ffmpeg 编译好的库可以直接加载使用 https://github.com/hilive/ffmpeg-hardcode/tree/master/output
使用
encode(编码)
hevc
avcodec_find_encoder_by_name("hevc_hlmediacodec")
h264
avcodec_find_encoder_by_name("h264_hlmediacodec")
decode(解码)
hevc
avcodec_find_decoder_by_name("h264_hlmediacodec")
h264
avcodec_find_decoder_by_name("h264_hlmediacodec")