/*
 * Android MediaCodec encoders
 *
 * Copyright (c) 2022 Zhao Zhili <zhilizhao@tencent.com>
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "config_components.h"

#include "libavutil/avassert.h"
#include "libavutil/fifo.h"
#include "libavutil/avstring.h"
#include "libavutil/hwcontext_mediacodec.h"
#include "libavutil/imgutils.h"
#include "libavutil/mem.h"
#include "libavutil/opt.h"
#include "libavutil/thread.h"

#include "avcodec.h"
#include "bsf.h"
#include "codec_internal.h"
#include "encode.h"
#include "hwconfig.h"
#include "jni.h"
#include "mediacodec.h"
#include "mediacodec_wrapper.h"
#include "mediacodecdec_common.h"
#include "profiles.h"

#define INPUT_DEQUEUE_TIMEOUT_US 8000
#define OUTPUT_DEQUEUE_TIMEOUT_US 8000

enum BitrateMode {
    /* Constant quality mode */
    BITRATE_MODE_CQ = 0,
    /* Variable bitrate mode */
    BITRATE_MODE_VBR = 1,
    /* Constant bitrate mode */
    BITRATE_MODE_CBR = 2,
    /* Constant bitrate mode with frame drops */
    BITRATE_MODE_CBR_FD = 3,
};

typedef struct MediaCodecAsyncOutput {
    int32_t index;
    FFAMediaCodecBufferInfo buf_info;
} MediaCodecAsyncOutput;

typedef struct MediaCodecEncContext {
    AVClass *avclass;
    FFAMediaCodec *codec;
    int use_ndk_codec;
    const char *name;
    FFANativeWindow *window;

    int fps;
    int width;
    int height;

    uint8_t *extradata;
    int extradata_size;
    int eof_sent;

    AVFrame *frame;
    AVBSFContext *bsf;

    int bitrate_mode;
    int level;
    int pts_as_dts;
    int extract_extradata;
    // Ref. MediaFormat KEY_OPERATING_RATE
    int operating_rate;
    int async_mode;

    AVMutex input_mutex;
    AVCond input_cond;
    AVFifo *input_index;

    AVMutex output_mutex;
    AVCond output_cond;
    int encode_status;
    AVFifo *async_output;

    int qp_i_min;
    int qp_p_min;
    int qp_b_min;
    int qp_i_max;
    int qp_p_max;
    int qp_b_max;
} MediaCodecEncContext;

enum {
    COLOR_FormatYUV420Planar                              = 0x13,
    COLOR_FormatYUV420SemiPlanar                          = 0x15,
    COLOR_FormatSurface                                   = 0x7F000789,
};

static const struct {
    int color_format;
    enum AVPixelFormat pix_fmt;
} color_formats[] = {
    { COLOR_FormatYUV420Planar,         AV_PIX_FMT_YUV420P },
    { COLOR_FormatYUV420SemiPlanar,     AV_PIX_FMT_NV12    },
    { COLOR_FormatSurface,              AV_PIX_FMT_MEDIACODEC },
};

static const enum AVPixelFormat avc_pix_fmts[] = {
    AV_PIX_FMT_MEDIACODEC,
    AV_PIX_FMT_YUV420P,
    AV_PIX_FMT_NV12,
    AV_PIX_FMT_NONE
};

static void mediacodec_dump_format(AVCodecContext *avctx,
        FFAMediaFormat *out_format)
{
    MediaCodecEncContext *s = avctx->priv_data;
    const char *name = s->name;
    char *str = ff_AMediaFormat_toString(out_format);

    av_log(avctx, AV_LOG_DEBUG, "MediaCodec encoder %s output format %s\n",
           name ? name : "unknown", str);
    av_free(str);
}

static void mediacodec_output_format(AVCodecContext *avctx)
{
    MediaCodecEncContext *s = avctx->priv_data;
    FFAMediaFormat *out_format = ff_AMediaCodec_getOutputFormat(s->codec);

    if (!s->name)
        s->name = ff_AMediaCodec_getName(s->codec);
    mediacodec_dump_format(avctx, out_format);
    ff_AMediaFormat_delete(out_format);
}

static int extract_extradata_support(AVCodecContext *avctx)
{
    const AVBitStreamFilter *bsf = av_bsf_get_by_name("extract_extradata");

    if (!bsf) {
        av_log(avctx, AV_LOG_WARNING, "extract_extradata bsf not found\n");
        return 0;
    }

    for (int i = 0; bsf->codec_ids[i] != AV_CODEC_ID_NONE; i++) {
        if (bsf->codec_ids[i] == avctx->codec_id)
            return 1;
    }

    return 0;
}

static int mediacodec_init_bsf(AVCodecContext *avctx)
{
    MediaCodecEncContext *s = avctx->priv_data;
    char str[128] = {0};
    int ret;
    int crop_right = s->width - avctx->width;
    int crop_bottom = s->height - avctx->height;

    /* Nothing can be done for this format now */
    if (avctx->pix_fmt == AV_PIX_FMT_MEDIACODEC)
        return 0;

    s->extract_extradata = (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) &&
                           extract_extradata_support(avctx);
    if (!crop_right && !crop_bottom && !s->extract_extradata)
        return 0;

    ret = 0;
    if (crop_right || crop_bottom) {
        if (avctx->codec_id == AV_CODEC_ID_H264)
            ret = snprintf(str, sizeof(str), "h264_metadata=crop_right=%d:crop_bottom=%d",
                           crop_right, crop_bottom);
        else if (avctx->codec_id == AV_CODEC_ID_HEVC)
            /* Encoder can use CTU size larger than 16x16, so the real crop
             * margin can be larger than crop_right/crop_bottom. Let bsf figure
             * out the real crop margin.
             */
            ret = snprintf(str, sizeof(str), "hevc_metadata=width=%d:height=%d",
                           avctx->width, avctx->height);
        if (ret >= sizeof(str))
            return AVERROR_BUFFER_TOO_SMALL;
    }

    if (s->extract_extradata) {
        ret = av_strlcatf(str, sizeof(str), "%sextract_extradata", ret ? "," : "");
        if (ret >= sizeof(str))
            return AVERROR_BUFFER_TOO_SMALL;
    }

    ret = av_bsf_list_parse_str(str, &s->bsf);
    if (ret < 0)
        return ret;

    ret = avcodec_parameters_from_context(s->bsf->par_in, avctx);
    if (ret < 0)
        return ret;
    s->bsf->time_base_in = avctx->time_base;
    ret = av_bsf_init(s->bsf);

    return ret;
}

static void copy_frame_to_buffer(AVCodecContext *avctx, const AVFrame *frame,
                                 uint8_t *dst, size_t size)
{
    MediaCodecEncContext *s = avctx->priv_data;
    uint8_t *dst_data[4] = {};
    int dst_linesize[4] = {};

    if (avctx->pix_fmt == AV_PIX_FMT_YUV420P) {
        dst_data[0] = dst;
        dst_data[1] = dst + s->width * s->height;
        dst_data[2] = dst_data[1] + s->width * s->height / 4;

        dst_linesize[0] = s->width;
        dst_linesize[1] = dst_linesize[2] = s->width / 2;
    } else if (avctx->pix_fmt == AV_PIX_FMT_NV12) {
        dst_data[0] = dst;
        dst_data[1] = dst + s->width * s->height;

        dst_linesize[0] = s->width;
        dst_linesize[1] = s->width;
    } else {
        av_assert0(0);
    }

    av_image_copy2(dst_data, dst_linesize, frame->data, frame->linesize,
                   avctx->pix_fmt, avctx->width, avctx->height);
}


static void on_error(FFAMediaCodec *codec, void *userdata, int error,
                     const char *detail)
{
    AVCodecContext *avctx = userdata;
    MediaCodecEncContext *s = avctx->priv_data;

    if (error == AVERROR(EAGAIN))
        return;

    av_log(avctx, AV_LOG_ERROR, "On error, %s, %s\n", av_err2str(error), detail);

    ff_mutex_lock(&s->input_mutex);
    ff_mutex_lock(&s->output_mutex);
    s->encode_status = error;
    ff_mutex_unlock(&s->output_mutex);
    ff_mutex_unlock(&s->input_mutex);

    ff_cond_signal(&s->output_cond);
    ff_cond_signal(&s->input_cond);
}

static void on_input_available(FFAMediaCodec *codec, void *userdata,
                               int32_t index)
{
    AVCodecContext *avctx = userdata;
    MediaCodecEncContext *s = avctx->priv_data;
    int ret;

    ff_mutex_lock(&s->input_mutex);
    ret = av_fifo_write(s->input_index, &index, 1);
    if (ret >= 0)
        ff_cond_signal(&s->input_cond);
    ff_mutex_unlock(&s->input_mutex);

    if (ret < 0)
        on_error(codec, userdata, ret, "av_fifo_write failed");
}

static void on_output_available(FFAMediaCodec *codec, void *userdata,
                               int32_t index,
                               FFAMediaCodecBufferInfo *out_info)
{
    AVCodecContext *avctx = userdata;
    MediaCodecEncContext *s = avctx->priv_data;
    MediaCodecAsyncOutput output = {
        .index = index,
        .buf_info = *out_info,
    };
    int ret;

    ff_mutex_lock(&s->output_mutex);
    ret = av_fifo_write(s->async_output, &output, 1);
    if (ret >= 0)
        ff_cond_signal(&s->output_cond);
    ff_mutex_unlock(&s->output_mutex);

    if (ret < 0)
        on_error(codec, userdata, ret, "av_fifo_write failed");
}

static void on_format_changed(FFAMediaCodec *codec, void *userdata,
                              FFAMediaFormat *format)
{
    mediacodec_dump_format(userdata, format);
}

static int mediacodec_init_async_state(AVCodecContext *avctx)
{
    MediaCodecEncContext *s = avctx->priv_data;
    size_t fifo_size = 16;

    if (!s->async_mode)
        return 0;

    ff_mutex_init(&s->input_mutex, NULL);
    ff_cond_init(&s->input_cond, NULL);

    ff_mutex_init(&s->output_mutex, NULL);
    ff_cond_init(&s->output_cond, NULL);

    s->input_index = av_fifo_alloc2(fifo_size, sizeof(int32_t), AV_FIFO_FLAG_AUTO_GROW);
    s->async_output = av_fifo_alloc2(fifo_size, sizeof(MediaCodecAsyncOutput),
                                     AV_FIFO_FLAG_AUTO_GROW);

    if (!s->input_index || !s->async_output)
        return AVERROR(ENOMEM);

    return 0;
}

static void mediacodec_uninit_async_state(AVCodecContext *avctx)
{
    MediaCodecEncContext *s = avctx->priv_data;

    if (!s->async_mode)
        return;

    ff_mutex_destroy(&s->input_mutex);
    ff_cond_destroy(&s->input_cond);

    ff_mutex_destroy(&s->output_mutex);
    ff_cond_destroy(&s->output_cond);

    av_fifo_freep2(&s->input_index);
    av_fifo_freep2(&s->async_output);

    s->async_mode = 0;
}

static int mediacodec_generate_extradata(AVCodecContext *avctx);

static void mediacodec_set_qp_range(AVCodecContext *avctx,
                                    FFAMediaFormat *format)
{
    MediaCodecEncContext *s = avctx->priv_data;

    // Handle common options in AVCodecContext first.
    if (avctx->qmin >= 0) {
        ff_AMediaFormat_setInt32(format, "video-qp-i-min", avctx->qmin);
        ff_AMediaFormat_setInt32(format, "video-qp-p-min", avctx->qmin);
        ff_AMediaFormat_setInt32(format, "video-qp-b-min", avctx->qmin);
    }

    if (avctx->qmax >= 0) {
        ff_AMediaFormat_setInt32(format, "video-qp-i-max", avctx->qmax);
        ff_AMediaFormat_setInt32(format, "video-qp-p-max", avctx->qmax);
        ff_AMediaFormat_setInt32(format, "video-qp-b-max", avctx->qmax);
    }

    if (s->qp_i_min >= 0)
        ff_AMediaFormat_setInt32(format, "video-qp-i-min", s->qp_i_min);
    if (s->qp_p_min >= 0)
        ff_AMediaFormat_setInt32(format, "video-qp-p-min", s->qp_p_min);
    if (s->qp_b_min >= 0)
        ff_AMediaFormat_setInt32(format, "video-qp-b-min", s->qp_b_min);

    if (s->qp_i_max >= 0)
        ff_AMediaFormat_setInt32(format, "video-qp-i-max", s->qp_i_max);
    if (s->qp_p_max >= 0)
        ff_AMediaFormat_setInt32(format, "video-qp-p-max", s->qp_p_max);
    if (s->qp_b_max >= 0)
        ff_AMediaFormat_setInt32(format, "video-qp-b-max", s->qp_b_max);
}

static av_cold int mediacodec_init(AVCodecContext *avctx)
{
    const char *codec_mime = NULL;
    MediaCodecEncContext *s = avctx->priv_data;
    FFAMediaFormat *format = NULL;
    int ret;
    int gop;

    // Init async state first, so we can do cleanup safely on error path.
    ret = mediacodec_init_async_state(avctx);
    if (ret < 0)
        return ret;

    if (s->use_ndk_codec < 0)
        s->use_ndk_codec = !av_jni_get_java_vm(avctx);

    switch (avctx->codec_id) {
    case AV_CODEC_ID_H264:
        codec_mime = "video/avc";
        break;
    case AV_CODEC_ID_HEVC:
        codec_mime = "video/hevc";
        break;
    case AV_CODEC_ID_VP8:
        codec_mime = "video/x-vnd.on2.vp8";
        break;
    case AV_CODEC_ID_VP9:
        codec_mime = "video/x-vnd.on2.vp9";
        break;
    case AV_CODEC_ID_MPEG4:
        codec_mime = "video/mp4v-es";
        break;
    case AV_CODEC_ID_AV1:
        codec_mime = "video/av01";
        break;
    default:
        av_assert0(0);
    }

    if (s->name)
        s->codec = ff_AMediaCodec_createCodecByName(s->name, s->use_ndk_codec);
    else
        s->codec = ff_AMediaCodec_createEncoderByType(codec_mime, s->use_ndk_codec);
    if (!s->codec) {
        av_log(avctx, AV_LOG_ERROR, "Failed to create encoder for type %s\n",
               codec_mime);
        return AVERROR_EXTERNAL;
    }

    format = ff_AMediaFormat_new(s->use_ndk_codec);
    if (!format) {
        av_log(avctx, AV_LOG_ERROR, "Failed to create media format\n");
        return AVERROR_EXTERNAL;
    }

    ff_AMediaFormat_setString(format, "mime", codec_mime);
    // Workaround the alignment requirement of mediacodec. We can't do it
    // silently for AV_PIX_FMT_MEDIACODEC.
    if (avctx->pix_fmt != AV_PIX_FMT_MEDIACODEC &&
        (avctx->codec_id == AV_CODEC_ID_H264 ||
         avctx->codec_id == AV_CODEC_ID_HEVC)) {
        s->width = FFALIGN(avctx->width, 16);
        s->height = FFALIGN(avctx->height, 16);
    } else {
        s->width = avctx->width;
        s->height = avctx->height;
        if (s->width % 16 || s->height % 16)
            av_log(avctx, AV_LOG_WARNING,
                    "Video size %dx%d isn't align to 16, it may have device compatibility issue\n",
                    s->width, s->height);
    }
    ff_AMediaFormat_setInt32(format, "width", s->width);
    ff_AMediaFormat_setInt32(format, "height", s->height);

    if (avctx->pix_fmt == AV_PIX_FMT_MEDIACODEC) {
        AVMediaCodecContext *user_ctx = avctx->hwaccel_context;
        if (avctx->hw_device_ctx) {
            AVHWDeviceContext *device_ctx = (AVHWDeviceContext*)(avctx->hw_device_ctx->data);
            AVMediaCodecDeviceContext *dev_ctx;

            if (device_ctx->type != AV_HWDEVICE_TYPE_MEDIACODEC || !device_ctx->hwctx) {
                ret = AVERROR(EINVAL);
                goto bailout;
            }
            dev_ctx = device_ctx->hwctx;
            s->window = ff_mediacodec_surface_ref(dev_ctx->surface, dev_ctx->native_window, avctx);
        }

        if (!s->window && user_ctx && user_ctx->surface)
            s->window = ff_mediacodec_surface_ref(user_ctx->surface, NULL, avctx);

        if (!s->window) {
            ret = AVERROR(EINVAL);
            av_log(avctx, AV_LOG_ERROR, "Missing hw_device_ctx or hwaccel_context for AV_PIX_FMT_MEDIACODEC\n");
            goto bailout;
        }
        /* Although there is a method ANativeWindow_toSurface() introduced in
         * API level 26, it's easier and safe to always require a Surface for
         * Java MediaCodec.
         */
        if (!s->use_ndk_codec && !s->window->surface) {
            ret = AVERROR(EINVAL);
            av_log(avctx, AV_LOG_ERROR, "Missing jobject Surface for AV_PIX_FMT_MEDIACODEC. "
                    "Please note that Java MediaCodec doesn't work with ANativeWindow.\n");
            goto bailout;
        }
    }

    for (int i = 0; i < FF_ARRAY_ELEMS(color_formats); i++) {
        if (avctx->pix_fmt == color_formats[i].pix_fmt) {
            ff_AMediaFormat_setInt32(format, "color-format",
                                     color_formats[i].color_format);
            break;
        }
    }

    ret = ff_AMediaFormatColorRange_from_AVColorRange(avctx->color_range);
    if (ret != COLOR_RANGE_UNSPECIFIED)
        ff_AMediaFormat_setInt32(format, "color-range", ret);
    ret = ff_AMediaFormatColorStandard_from_AVColorSpace(avctx->colorspace);
    if (ret != COLOR_STANDARD_UNSPECIFIED)
        ff_AMediaFormat_setInt32(format, "color-standard", ret);
    ret = ff_AMediaFormatColorTransfer_from_AVColorTransfer(avctx->color_trc);
    if (ret != COLOR_TRANSFER_UNSPECIFIED)
        ff_AMediaFormat_setInt32(format, "color-transfer", ret);

    if (avctx->bit_rate)
        ff_AMediaFormat_setInt32(format, "bitrate", avctx->bit_rate);
    if (s->bitrate_mode >= 0) {
        ff_AMediaFormat_setInt32(format, "bitrate-mode", s->bitrate_mode);
        if (s->bitrate_mode == BITRATE_MODE_CQ && avctx->global_quality > 0)
            ff_AMediaFormat_setInt32(format, "quality", avctx->global_quality);
    }
    mediacodec_set_qp_range(avctx, format);

    // frame-rate and i-frame-interval are required to configure codec
    if (avctx->framerate.num >= avctx->framerate.den && avctx->framerate.den > 0) {
        s->fps = avctx->framerate.num / avctx->framerate.den;
    } else {
        s->fps = 30;
        av_log(avctx, AV_LOG_INFO, "Use %d as the default MediaFormat frame-rate\n", s->fps);
    }
    gop = round(avctx->gop_size / s->fps);
    if (gop == 0) {
        gop = 1;
        av_log(avctx, AV_LOG_INFO,
                "Use %d as the default MediaFormat i-frame-interval, "
                "please set gop_size properly (>= fps)\n", gop);
    } else {
        av_log(avctx, AV_LOG_DEBUG, "Set i-frame-interval to %d\n", gop);
    }

    ff_AMediaFormat_setInt32(format, "frame-rate", s->fps);
    ff_AMediaFormat_setInt32(format, "i-frame-interval", gop);

    ret = ff_AMediaCodecProfile_getProfileFromAVCodecContext(avctx);
    if (ret > 0) {
        av_log(avctx, AV_LOG_DEBUG, "set profile to 0x%x\n", ret);
        ff_AMediaFormat_setInt32(format, "profile", ret);
    }
    if (s->level > 0) {
        av_log(avctx, AV_LOG_DEBUG, "set level to 0x%x\n", s->level);
        ff_AMediaFormat_setInt32(format, "level", s->level);
    }
    if (avctx->max_b_frames > 0) {
        if (avctx->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) {
            av_log(avctx, AV_LOG_ERROR,
                    "Enabling B frames will produce packets with no DTS. "
                    "Use -strict experimental to use it anyway.\n");
            ret = AVERROR(EINVAL);
            goto bailout;
        }
        ff_AMediaFormat_setInt32(format, "max-bframes", avctx->max_b_frames);
    }
    if (s->pts_as_dts == -1)
        s->pts_as_dts = avctx->max_b_frames <= 0;
    if (s->operating_rate > 0)
        ff_AMediaFormat_setInt32(format, "operating-rate", s->operating_rate);

    ret = ff_AMediaCodec_getConfigureFlagEncode(s->codec);
    ret = ff_AMediaCodec_configure(s->codec, format, s->window, NULL, ret);
    if (ret) {
        av_log(avctx, AV_LOG_ERROR, "MediaCodec configure failed, %s\n", av_err2str(ret));
        if (avctx->pix_fmt == AV_PIX_FMT_YUV420P)
            av_log(avctx, AV_LOG_ERROR, "Please try -pix_fmt nv12, some devices don't "
                                        "support yuv420p as encoder input format.\n");
        goto bailout;
    }

    if (s->async_mode) {
        FFAMediaCodecOnAsyncNotifyCallback cb = {
                .onAsyncInputAvailable = on_input_available,
                .onAsyncOutputAvailable = on_output_available,
                .onAsyncFormatChanged = on_format_changed,
                .onAsyncError = on_error,
        };

        ret = ff_AMediaCodec_setAsyncNotifyCallback(s->codec, &cb, avctx);
        if (ret < 0) {
            av_log(avctx, AV_LOG_WARNING,
                   "Try MediaCodec async mode failed, %s, switch to sync mode\n",
                   av_err2str(ret));
            mediacodec_uninit_async_state(avctx);
        }
    }

    ret = mediacodec_init_bsf(avctx);
    if (ret)
        goto bailout;

    mediacodec_output_format(avctx);

    s->frame = av_frame_alloc();
    if (!s->frame) {
        ret = AVERROR(ENOMEM);
        goto bailout;
    }

    ret = ff_AMediaCodec_start(s->codec);
    if (ret) {
        av_log(avctx, AV_LOG_ERROR, "MediaCodec failed to start, %s\n",
               av_err2str(ret));
        goto bailout;
    }

    ret = mediacodec_generate_extradata(avctx);

bailout:
    if (format)
        ff_AMediaFormat_delete(format);
    return ret;
}

static int mediacodec_get_output_index(AVCodecContext *avctx, ssize_t *index,
                                       FFAMediaCodecBufferInfo *out_info)
{
    MediaCodecEncContext *s = avctx->priv_data;
    FFAMediaCodec *codec = s->codec;
    int64_t timeout_us = s->eof_sent ? OUTPUT_DEQUEUE_TIMEOUT_US : 0;
    MediaCodecAsyncOutput output = { .index = -1 };
    int ret;

    if (!s->async_mode) {
        *index = ff_AMediaCodec_dequeueOutputBuffer(codec, out_info, timeout_us);
        return 0;
    }

    ff_mutex_lock(&s->output_mutex);

    while (!s->encode_status) {
        if (av_fifo_read(s->async_output, &output, 1) >= 0)
            break;

        // Only wait after signalEndOfInputStream
        if (s->eof_sent && !s->encode_status)
            ff_cond_wait(&s->output_cond, &s->output_mutex);
        else
            break;
    }

    ret = s->encode_status;
    ff_mutex_unlock(&s->output_mutex);

    // Get output index success
    if (output.index >= 0) {
        *index = output.index;
        *out_info = output.buf_info;
        return 0;
    }

    return ret ? ret : AVERROR(EAGAIN);
}

static int mediacodec_receive(AVCodecContext *avctx, AVPacket *pkt)
{
    MediaCodecEncContext *s = avctx->priv_data;
    FFAMediaCodec *codec = s->codec;
    ssize_t index;
    FFAMediaCodecBufferInfo out_info = {0};
    uint8_t *out_buf;
    size_t out_size = 0;
    int ret;
    int extradata_size = 0;

    ret = mediacodec_get_output_index(avctx, &index, &out_info);
    if (ret < 0)
        return ret;

    if (ff_AMediaCodec_infoTryAgainLater(codec, index))
        return AVERROR(EAGAIN);

    if (ff_AMediaCodec_infoOutputFormatChanged(codec, index)) {
        mediacodec_output_format(avctx);
        return AVERROR(EAGAIN);
    }

    if (ff_AMediaCodec_infoOutputBuffersChanged(codec, index)) {
        ff_AMediaCodec_cleanOutputBuffers(codec);
        return AVERROR(EAGAIN);
    }

    if (index < 0)
        return AVERROR_EXTERNAL;

    if (out_info.flags & ff_AMediaCodec_getBufferFlagEndOfStream(codec))
        return AVERROR_EOF;

    out_buf = ff_AMediaCodec_getOutputBuffer(codec, index, &out_size);
    if (!out_buf) {
        ret = AVERROR_EXTERNAL;
        goto bailout;
    }

    if (out_info.flags & ff_AMediaCodec_getBufferFlagCodecConfig(codec)) {
        if (avctx->codec_id == AV_CODEC_ID_AV1) {
            // Skip AV1CodecConfigurationRecord without configOBUs
            if (out_info.size <= 4) {
                ff_AMediaCodec_releaseOutputBuffer(codec, index, false);
                return mediacodec_receive(avctx, pkt);
            }
            out_info.size -= 4;
            out_info.offset += 4;
        }

        ret = av_reallocp(&s->extradata, out_info.size);
        if (ret)
            goto bailout;

        s->extradata_size = out_info.size;
        memcpy(s->extradata, out_buf + out_info.offset, out_info.size);
        ff_AMediaCodec_releaseOutputBuffer(codec, index, false);
        // try immediately
        return mediacodec_receive(avctx, pkt);
    }

    ret = ff_get_encode_buffer(avctx, pkt, out_info.size + s->extradata_size, 0);
    if (ret < 0)
      goto bailout;

    if (s->extradata_size) {
        extradata_size = s->extradata_size;
        s->extradata_size = 0;
        memcpy(pkt->data, s->extradata, extradata_size);
    }
    memcpy(pkt->data + extradata_size, out_buf + out_info.offset, out_info.size);
    pkt->pts = av_rescale_q(out_info.presentationTimeUs, AV_TIME_BASE_Q, avctx->time_base);
    if (s->pts_as_dts)
        pkt->dts = pkt->pts;
    if (out_info.flags & ff_AMediaCodec_getBufferFlagKeyFrame(codec))
        pkt->flags |= AV_PKT_FLAG_KEY;
    ret = 0;

    av_log(avctx, AV_LOG_TRACE, "receive packet pts %" PRId64 " dts %" PRId64
           " flags %d extradata %d\n",
           pkt->pts, pkt->dts, pkt->flags, extradata_size);

bailout:
    ff_AMediaCodec_releaseOutputBuffer(codec, index, false);
    return ret;
}

static int mediacodec_get_input_index(AVCodecContext *avctx, ssize_t *index)
{
    MediaCodecEncContext *s = avctx->priv_data;
    FFAMediaCodec *codec = s->codec;
    int ret = 0;
    int32_t n;

    if (!s->async_mode) {
        *index = ff_AMediaCodec_dequeueInputBuffer(codec, INPUT_DEQUEUE_TIMEOUT_US);
        return 0;
    }

    ff_mutex_lock(&s->input_mutex);

    n = -1;
    while (n < 0 && !s->encode_status) {
        if (av_fifo_can_read(s->input_index) > 0) {
            av_fifo_read(s->input_index, &n, 1);
            break;
        }

        if (n < 0 && !s->encode_status)
            ff_cond_wait(&s->input_cond, &s->input_mutex);
    }

    ret = s->encode_status;
    *index = n;
    ff_mutex_unlock(&s->input_mutex);

    return ret;
}


static int mediacodec_send(AVCodecContext *avctx,
                           const AVFrame *frame) {
    MediaCodecEncContext *s = avctx->priv_data;
    FFAMediaCodec *codec = s->codec;
    ssize_t index;
    uint8_t *input_buf = NULL;
    size_t input_size = 0;
    int64_t pts = 0;
    uint32_t flags = 0;
    int ret;

    if (s->eof_sent)
        return 0;

    if (s->window) {
        if (!frame) {
            s->eof_sent = 1;
            return ff_AMediaCodec_signalEndOfInputStream(codec);
        }

        if (frame->data[3])
            av_mediacodec_release_buffer((AVMediaCodecBuffer *)frame->data[3], 1);
        return 0;
    }

    ret = mediacodec_get_input_index(avctx, &index);
    if (ret < 0)
        return ret;

    if (ff_AMediaCodec_infoTryAgainLater(codec, index))
        return AVERROR(EAGAIN);

    if (index < 0) {
        av_log(avctx, AV_LOG_ERROR, "dequeue input buffer failed, %zd", index);
        return AVERROR_EXTERNAL;
    }

    if (frame) {
        input_buf = ff_AMediaCodec_getInputBuffer(codec, index, &input_size);
        copy_frame_to_buffer(avctx, frame, input_buf, input_size);

        pts = av_rescale_q(frame->pts, avctx->time_base, AV_TIME_BASE_Q);
    } else {
        flags |= ff_AMediaCodec_getBufferFlagEndOfStream(codec);
        s->eof_sent = 1;
    }

    ff_AMediaCodec_queueInputBuffer(codec, index, 0, input_size, pts, flags);
    return 0;
}

static int mediacodec_encode(AVCodecContext *avctx, AVPacket *pkt)
{
    MediaCodecEncContext *s = avctx->priv_data;
    int ret;

    // Return on three case:
    // 1. Serious error
    // 2. Got a packet success
    // 3. No AVFrame is available yet (don't return if get_frame return EOF)
    while (1) {
        if (s->bsf) {
            ret = av_bsf_receive_packet(s->bsf, pkt);
            if (!ret)
                return 0;
            if (ret != AVERROR(EAGAIN))
                return ret;
        }

        ret = mediacodec_receive(avctx, pkt);
        if (s->bsf) {
            if (!ret || ret == AVERROR_EOF)
                ret = av_bsf_send_packet(s->bsf, pkt);
        } else {
            if (!ret)
                return 0;
        }

        if (ret < 0 && ret != AVERROR(EAGAIN))
            return ret;

        if (!s->frame->buf[0]) {
            ret = ff_encode_get_frame(avctx, s->frame);
            if (ret && ret != AVERROR_EOF)
                return ret;
        }

        ret = mediacodec_send(avctx, s->frame->buf[0] ? s->frame : NULL);
        if (!ret)
            av_frame_unref(s->frame);
        else if (ret != AVERROR(EAGAIN))
            return ret;
    }

    return 0;
}

static int mediacodec_send_dummy_frame(AVCodecContext *avctx)
{
    MediaCodecEncContext *s = avctx->priv_data;
    int ret;

    s->frame->width = avctx->width;
    s->frame->height = avctx->height;
    s->frame->format = avctx->pix_fmt;
    s->frame->pts = 0;

    ret = av_frame_get_buffer(s->frame, 0);
    if (ret < 0)
        return ret;

    do {
        ret = mediacodec_send(avctx, s->frame);
    } while (ret == AVERROR(EAGAIN));
    av_frame_unref(s->frame);

    if (ret < 0)
        return ret;

    ret = mediacodec_send(avctx, NULL);
    if (ret < 0) {
        av_log(avctx, AV_LOG_ERROR, "Flush failed: %s\n", av_err2str(ret));
        return ret;
    }

    return 0;
}

static int mediacodec_receive_dummy_pkt(AVCodecContext *avctx, AVPacket *pkt)
{
    MediaCodecEncContext *s = avctx->priv_data;
    int ret;

    do {
        ret = mediacodec_receive(avctx, pkt);
    } while (ret == AVERROR(EAGAIN));

    if (ret < 0)
        return ret;

    do {
        ret = av_bsf_send_packet(s->bsf, pkt);
        if (ret < 0)
            return ret;
        ret = av_bsf_receive_packet(s->bsf, pkt);
    } while (ret == AVERROR(EAGAIN));

    return ret;
}

static int mediacodec_generate_extradata(AVCodecContext *avctx)
{
    MediaCodecEncContext *s = avctx->priv_data;
    AVPacket *pkt = NULL;
    int ret;
    size_t side_size;
    uint8_t *side;

    if (!(avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER))
        return 0;

    // Send dummy frame and receive a packet doesn't work in async mode
    if (s->async_mode || !s->extract_extradata) {
        av_log(avctx, AV_LOG_WARNING,
               "Mediacodec encoder doesn't support AV_CODEC_FLAG_GLOBAL_HEADER. "
               "Use extract_extradata bsf when necessary.\n");
        return 0;
    }

    pkt = av_packet_alloc();
    if (!pkt)
        return AVERROR(ENOMEM);

    ret = mediacodec_send_dummy_frame(avctx);
    if (ret < 0)
        goto bailout;
    ret = mediacodec_receive_dummy_pkt(avctx, pkt);
    if (ret < 0)
        goto bailout;

    side = av_packet_get_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, &side_size);
    if (side && side_size > 0) {
        avctx->extradata = av_mallocz(side_size + AV_INPUT_BUFFER_PADDING_SIZE);
        if (!avctx->extradata) {
            ret = AVERROR(ENOMEM);
            goto bailout;
        }

        memcpy(avctx->extradata, side, side_size);
        avctx->extradata_size = side_size;
    }

bailout:
    if (s->eof_sent) {
        s->eof_sent = 0;
        ff_AMediaCodec_flush(s->codec);
    }
    av_bsf_flush(s->bsf);
    av_packet_free(&pkt);
    return ret;
}

static av_cold int mediacodec_close(AVCodecContext *avctx)
{
    MediaCodecEncContext *s = avctx->priv_data;
    if (s->codec) {
        ff_AMediaCodec_stop(s->codec);
        ff_AMediaCodec_delete(s->codec);
        s->codec = NULL;
    }

    if (s->window) {
        ff_mediacodec_surface_unref(s->window, avctx);
        s->window = NULL;
    }

    av_bsf_free(&s->bsf);
    av_frame_free(&s->frame);

    mediacodec_uninit_async_state(avctx);

    return 0;
}

static av_cold void mediacodec_flush(AVCodecContext *avctx)
{
    MediaCodecEncContext *s = avctx->priv_data;
    if (s->bsf)
        av_bsf_flush(s->bsf);
    av_frame_unref(s->frame);
    ff_AMediaCodec_flush(s->codec);
}

static const AVCodecHWConfigInternal *const mediacodec_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
};

static const FFCodecDefault mediacodec_defaults[] = {
    {"qmin", "-1"},
    {"qmax", "-1"},
    {NULL},
};

#define OFFSET(x) offsetof(MediaCodecEncContext, x)
#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
#define COMMON_OPTION                                                                                       \
    { "ndk_codec", "Use MediaCodec from NDK",                                                               \
                    OFFSET(use_ndk_codec), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE },                      \
    { "ndk_async", "Try NDK MediaCodec in async mode",                                                      \
                    OFFSET(async_mode), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, VE },                           \
    { "codec_name", "Select codec by name",                                                                 \
                    OFFSET(name), AV_OPT_TYPE_STRING, {0}, 0, 0, VE },                                      \
    { "bitrate_mode", "Bitrate control method",                                                             \
                    OFFSET(bitrate_mode), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, VE, .unit = "bitrate_mode" },  \
    { "cq", "Constant quality mode",                                                                                \
                    0, AV_OPT_TYPE_CONST, {.i64 = BITRATE_MODE_CQ}, 0, 0, VE, .unit = "bitrate_mode" },             \
    { "vbr", "Variable bitrate mode",                                                                               \
                    0, AV_OPT_TYPE_CONST, {.i64 = BITRATE_MODE_VBR}, 0, 0, VE, .unit = "bitrate_mode" },            \
    { "cbr", "Constant bitrate mode",                                                                               \
                    0, AV_OPT_TYPE_CONST, {.i64 = BITRATE_MODE_CBR}, 0, 0, VE, .unit = "bitrate_mode" },            \
    { "cbr_fd", "Constant bitrate mode with frame drops",                                                           \
                    0, AV_OPT_TYPE_CONST, {.i64 = BITRATE_MODE_CBR_FD}, 0, 0, VE, .unit = "bitrate_mode" },         \
    { "pts_as_dts", "Use PTS as DTS. It is enabled automatically if avctx max_b_frames <= 0, "              \
                    "since most of Android devices don't output B frames by default.",                      \
                    OFFSET(pts_as_dts), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE },                         \
    { "operating_rate", "The desired operating rate that the codec will need to operate at, zero for unspecified",    \
                    OFFSET(operating_rate), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, VE },                  \
    { "qp_i_min", "minimum quantization parameter for I frame",                                             \
                    OFFSET(qp_i_min), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, VE },                      \
    { "qp_p_min", "minimum quantization parameter for P frame",                                             \
                    OFFSET(qp_p_min), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, VE },                      \
    { "qp_b_min", "minimum quantization parameter for B frame",                                             \
                    OFFSET(qp_b_min), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, VE },                      \
    { "qp_i_max", "maximum quantization parameter for I frame",                                             \
                    OFFSET(qp_i_max), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, VE },                      \
    { "qp_p_max", "maximum quantization parameter for P frame",                                             \
                    OFFSET(qp_p_max), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, VE },                      \
    { "qp_b_max", "maximum quantization parameter for B frame",                                             \
                    OFFSET(qp_b_max), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, VE },                      \


#define MEDIACODEC_ENCODER_CLASS(name)              \
static const AVClass name ## _mediacodec_class = {  \
    .class_name = #name "_mediacodec",              \
    .item_name  = av_default_item_name,             \
    .option     = name ## _options,                 \
    .version    = LIBAVUTIL_VERSION_INT,            \
};                                                  \

#define DECLARE_MEDIACODEC_ENCODER(short_name, long_name, codec_id)     \
MEDIACODEC_ENCODER_CLASS(short_name)                                    \
const FFCodec ff_ ## short_name ## _mediacodec_encoder = {              \
    .p.name           = #short_name "_mediacodec",                      \
    CODEC_LONG_NAME(long_name " Android MediaCodec encoder"),           \
    .p.type           = AVMEDIA_TYPE_VIDEO,                             \
    .p.id             = codec_id,                                       \
    .p.capabilities   = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY |         \
                        AV_CODEC_CAP_HARDWARE |                         \
                        AV_CODEC_CAP_ENCODER_FLUSH,                     \
    .priv_data_size   = sizeof(MediaCodecEncContext),                   \
    CODEC_PIXFMTS_ARRAY(avc_pix_fmts),                                  \
    .color_ranges   = AVCOL_RANGE_MPEG | AVCOL_RANGE_JPEG,              \
    .defaults         = mediacodec_defaults,                            \
    .init             = mediacodec_init,                                \
    FF_CODEC_RECEIVE_PACKET_CB(mediacodec_encode),                      \
    .close            = mediacodec_close,                               \
    .flush            = mediacodec_flush,                               \
    .p.priv_class     = &short_name ## _mediacodec_class,               \
    .caps_internal    = FF_CODEC_CAP_INIT_CLEANUP,                      \
    .p.wrapper_name = "mediacodec",                                     \
    .hw_configs     = mediacodec_hw_configs,                            \
};                                                                      \

#if CONFIG_H264_MEDIACODEC_ENCODER

enum MediaCodecAvcLevel {
    AVCLevel1       = 0x01,
    AVCLevel1b      = 0x02,
    AVCLevel11      = 0x04,
    AVCLevel12      = 0x08,
    AVCLevel13      = 0x10,
    AVCLevel2       = 0x20,
    AVCLevel21      = 0x40,
    AVCLevel22      = 0x80,
    AVCLevel3       = 0x100,
    AVCLevel31      = 0x200,
    AVCLevel32      = 0x400,
    AVCLevel4       = 0x800,
    AVCLevel41      = 0x1000,
    AVCLevel42      = 0x2000,
    AVCLevel5       = 0x4000,
    AVCLevel51      = 0x8000,
    AVCLevel52      = 0x10000,
    AVCLevel6       = 0x20000,
    AVCLevel61      = 0x40000,
    AVCLevel62      = 0x80000,
};

static const AVOption h264_options[] = {
    COMMON_OPTION

    FF_AVCTX_PROFILE_OPTION("baseline",             NULL, VIDEO, AV_PROFILE_H264_BASELINE)
    FF_AVCTX_PROFILE_OPTION("constrained_baseline", NULL, VIDEO, AV_PROFILE_H264_CONSTRAINED_BASELINE)
    FF_AVCTX_PROFILE_OPTION("main",                 NULL, VIDEO, AV_PROFILE_H264_MAIN)
    FF_AVCTX_PROFILE_OPTION("extended",             NULL, VIDEO, AV_PROFILE_H264_EXTENDED)
    FF_AVCTX_PROFILE_OPTION("high",                 NULL, VIDEO, AV_PROFILE_H264_HIGH)
    FF_AVCTX_PROFILE_OPTION("high10",               NULL, VIDEO, AV_PROFILE_H264_HIGH_10)
    FF_AVCTX_PROFILE_OPTION("high422",              NULL, VIDEO, AV_PROFILE_H264_HIGH_422)
    FF_AVCTX_PROFILE_OPTION("high444",              NULL, VIDEO, AV_PROFILE_H264_HIGH_444)

    { "level", "Specify level",
                OFFSET(level), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, VE, .unit = "level" },
    { "1",      "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel1  }, 0, 0, VE, .unit = "level" },
    { "1b",     "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel1b }, 0, 0, VE, .unit = "level" },
    { "1.1",    "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel11 }, 0, 0, VE, .unit = "level" },
    { "1.2",    "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel12 }, 0, 0, VE, .unit = "level" },
    { "1.3",    "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel13 }, 0, 0, VE, .unit = "level" },
    { "2",      "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel2  }, 0, 0, VE, .unit = "level" },
    { "2.1",    "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel21 }, 0, 0, VE, .unit = "level" },
    { "2.2",    "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel22 }, 0, 0, VE, .unit = "level" },
    { "3",      "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel3  }, 0, 0, VE, .unit = "level" },
    { "3.1",    "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel31 }, 0, 0, VE, .unit = "level" },
    { "3.2",    "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel32 }, 0, 0, VE, .unit = "level" },
    { "4",      "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel4  }, 0, 0, VE, .unit = "level" },
    { "4.1",    "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel41 }, 0, 0, VE, .unit = "level" },
    { "4.2",    "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel42 }, 0, 0, VE, .unit = "level" },
    { "5",      "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel5  }, 0, 0, VE, .unit = "level" },
    { "5.1",    "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel51 }, 0, 0, VE, .unit = "level" },
    { "5.2",    "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel52 }, 0, 0, VE, .unit = "level" },
    { "6.0",    "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel6  }, 0, 0, VE, .unit = "level" },
    { "6.1",    "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel61 }, 0, 0, VE, .unit = "level" },
    { "6.2",    "", 0, AV_OPT_TYPE_CONST, { .i64 = AVCLevel62 }, 0, 0, VE, .unit = "level" },
    { NULL, }
};

DECLARE_MEDIACODEC_ENCODER(h264, "H.264", AV_CODEC_ID_H264)

#endif  // CONFIG_H264_MEDIACODEC_ENCODER

#if CONFIG_HEVC_MEDIACODEC_ENCODER

enum MediaCodecHevcLevel {
    HEVCMainTierLevel1  = 0x1,
    HEVCHighTierLevel1  = 0x2,
    HEVCMainTierLevel2  = 0x4,
    HEVCHighTierLevel2  = 0x8,
    HEVCMainTierLevel21 = 0x10,
    HEVCHighTierLevel21 = 0x20,
    HEVCMainTierLevel3  = 0x40,
    HEVCHighTierLevel3  = 0x80,
    HEVCMainTierLevel31 = 0x100,
    HEVCHighTierLevel31 = 0x200,
    HEVCMainTierLevel4  = 0x400,
    HEVCHighTierLevel4  = 0x800,
    HEVCMainTierLevel41 = 0x1000,
    HEVCHighTierLevel41 = 0x2000,
    HEVCMainTierLevel5  = 0x4000,
    HEVCHighTierLevel5  = 0x8000,
    HEVCMainTierLevel51 = 0x10000,
    HEVCHighTierLevel51 = 0x20000,
    HEVCMainTierLevel52 = 0x40000,
    HEVCHighTierLevel52 = 0x80000,
    HEVCMainTierLevel6  = 0x100000,
    HEVCHighTierLevel6  = 0x200000,
    HEVCMainTierLevel61 = 0x400000,
    HEVCHighTierLevel61 = 0x800000,
    HEVCMainTierLevel62 = 0x1000000,
    HEVCHighTierLevel62 = 0x2000000,
};

static const AVOption hevc_options[] = {
    COMMON_OPTION

    FF_AVCTX_PROFILE_OPTION("main",   NULL, VIDEO, AV_PROFILE_HEVC_MAIN)
    FF_AVCTX_PROFILE_OPTION("main10", NULL, VIDEO, AV_PROFILE_HEVC_MAIN_10)

    { "level", "Specify tier and level",
                OFFSET(level), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, VE, .unit = "level" },
    { "m1",    "Main tier level 1",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCMainTierLevel1  },  0, 0, VE,  .unit = "level" },
    { "h1",    "High tier level 1",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCHighTierLevel1  },  0, 0, VE,  .unit = "level" },
    { "m2",    "Main tier level 2",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCMainTierLevel2  },  0, 0, VE,  .unit = "level" },
    { "h2",    "High tier level 2",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCHighTierLevel2  },  0, 0, VE,  .unit = "level" },
    { "m2.1",  "Main tier level 2.1",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCMainTierLevel21 },  0, 0, VE,  .unit = "level" },
    { "h2.1",  "High tier level 2.1",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCHighTierLevel21 },  0, 0, VE,  .unit = "level" },
    { "m3",    "Main tier level 3",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCMainTierLevel3  },  0, 0, VE,  .unit = "level" },
    { "h3",    "High tier level 3",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCHighTierLevel3  },  0, 0, VE,  .unit = "level" },
    { "m3.1",  "Main tier level 3.1",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCMainTierLevel31 },  0, 0, VE,  .unit = "level" },
    { "h3.1",  "High tier level 3.1",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCHighTierLevel31 },  0, 0, VE,  .unit = "level" },
    { "m4",    "Main tier level 4",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCMainTierLevel4  },  0, 0, VE,  .unit = "level" },
    { "h4",    "High tier level 4",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCHighTierLevel4  },  0, 0, VE,  .unit = "level" },
    { "m4.1",  "Main tier level 4.1",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCMainTierLevel41 },  0, 0, VE,  .unit = "level" },
    { "h4.1",  "High tier level 4.1",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCHighTierLevel41 },  0, 0, VE,  .unit = "level" },
    { "m5",    "Main tier level 5",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCMainTierLevel5  },  0, 0, VE,  .unit = "level" },
    { "h5",    "High tier level 5",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCHighTierLevel5  },  0, 0, VE,  .unit = "level" },
    { "m5.1",  "Main tier level 5.1",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCMainTierLevel51 },  0, 0, VE,  .unit = "level" },
    { "h5.1",  "High tier level 5.1",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCHighTierLevel51 },  0, 0, VE,  .unit = "level" },
    { "m5.2",  "Main tier level 5.2",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCMainTierLevel52 },  0, 0, VE,  .unit = "level" },
    { "h5.2",  "High tier level 5.2",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCHighTierLevel52 },  0, 0, VE,  .unit = "level" },
    { "m6",    "Main tier level 6",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCMainTierLevel6  },  0, 0, VE,  .unit = "level" },
    { "h6",    "High tier level 6",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCHighTierLevel6  },  0, 0, VE,  .unit = "level" },
    { "m6.1",  "Main tier level 6.1",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCMainTierLevel61 },  0, 0, VE,  .unit = "level" },
    { "h6.1",  "High tier level 6.1",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCHighTierLevel61 },  0, 0, VE,  .unit = "level" },
    { "m6.2",  "Main tier level 6.2",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCMainTierLevel62 },  0, 0, VE,  .unit = "level" },
    { "h6.2",  "High tier level 6.2",
                0, AV_OPT_TYPE_CONST, { .i64 = HEVCHighTierLevel62 },  0, 0, VE,  .unit = "level" },
    { NULL, }
};

DECLARE_MEDIACODEC_ENCODER(hevc, "H.265", AV_CODEC_ID_HEVC)

#endif  // CONFIG_HEVC_MEDIACODEC_ENCODER

#if CONFIG_VP8_MEDIACODEC_ENCODER

enum MediaCodecVP8Level {
    VP8Level_Version0 = 0x01,
    VP8Level_Version1 = 0x02,
    VP8Level_Version2 = 0x04,
    VP8Level_Version3 = 0x08,
};

static const AVOption vp8_options[] = {
    COMMON_OPTION
    { "level", "Specify tier and level",
                OFFSET(level), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, VE, .unit = "level" },
    { "V0",    "Level Version 0",
                0, AV_OPT_TYPE_CONST, { .i64 = VP8Level_Version0 },  0, 0, VE,  .unit = "level" },
    { "V1",    "Level Version 1",
                0, AV_OPT_TYPE_CONST, { .i64 = VP8Level_Version1 },  0, 0, VE,  .unit = "level" },
    { "V2",    "Level Version 2",
                0, AV_OPT_TYPE_CONST, { .i64 = VP8Level_Version2 },  0, 0, VE,  .unit = "level" },
    { "V3",    "Level Version 3",
                0, AV_OPT_TYPE_CONST, { .i64 = VP8Level_Version3 },  0, 0, VE,  .unit = "level" },
    { NULL, }
};

DECLARE_MEDIACODEC_ENCODER(vp8, "VP8", AV_CODEC_ID_VP8)

#endif  // CONFIG_VP8_MEDIACODEC_ENCODER

#if CONFIG_VP9_MEDIACODEC_ENCODER

enum MediaCodecVP9Level {
    VP9Level1  = 0x1,
    VP9Level11  = 0x2,
    VP9Level2  = 0x4,
    VP9Level21  = 0x8,
    VP9Level3 = 0x10,
    VP9Level31 = 0x20,
    VP9Level4  = 0x40,
    VP9Level41  = 0x80,
    VP9Level5 = 0x100,
    VP9Level51 = 0x200,
    VP9Level52  = 0x400,
    VP9Level6  = 0x800,
    VP9Level61 = 0x1000,
    VP9Level62 = 0x2000,
};

static const AVOption vp9_options[] = {
    COMMON_OPTION

    FF_AVCTX_PROFILE_OPTION("profile0",   NULL, VIDEO, AV_PROFILE_VP9_0)
    FF_AVCTX_PROFILE_OPTION("profile1",   NULL, VIDEO, AV_PROFILE_VP9_1)
    FF_AVCTX_PROFILE_OPTION("profile2",   NULL, VIDEO, AV_PROFILE_VP9_2)
    FF_AVCTX_PROFILE_OPTION("profile3",   NULL, VIDEO, AV_PROFILE_VP9_3)

    { "level", "Specify tier and level",
                OFFSET(level), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, VE, .unit = "level" },
    { "1",     "Level 1",
                0, AV_OPT_TYPE_CONST, { .i64 = VP9Level1  },  0, 0, VE,  .unit = "level" },
    { "1.1",   "Level 1.1",
                0, AV_OPT_TYPE_CONST, { .i64 = VP9Level11 },  0, 0, VE,  .unit = "level" },
    { "2",     "Level 2",
                0, AV_OPT_TYPE_CONST, { .i64 = VP9Level2  },  0, 0, VE,  .unit = "level" },
    { "2.1",   "Level 2.1",
                0, AV_OPT_TYPE_CONST, { .i64 = VP9Level21 },  0, 0, VE,  .unit = "level" },
    { "3",     "Level 3",
                0, AV_OPT_TYPE_CONST, { .i64 = VP9Level3  },  0, 0, VE,  .unit = "level" },
    { "3.1",   "Level 3.1",
                0, AV_OPT_TYPE_CONST, { .i64 = VP9Level31 },  0, 0, VE,  .unit = "level" },
    { "4",     "Level 4",
                0, AV_OPT_TYPE_CONST, { .i64 = VP9Level4  },  0, 0, VE,  .unit = "level" },
    { "4.1",   "Level 4.1",
                0, AV_OPT_TYPE_CONST, { .i64 = VP9Level41 },  0, 0, VE,  .unit = "level" },
    { "5",     "Level 5",
                0, AV_OPT_TYPE_CONST, { .i64 = VP9Level5  },  0, 0, VE,  .unit = "level" },
    { "5.1",   "Level 5.1",
                0, AV_OPT_TYPE_CONST, { .i64 = VP9Level51 },  0, 0, VE,  .unit = "level" },
    { "5.2",   "Level 5.2",
                0, AV_OPT_TYPE_CONST, { .i64 = VP9Level52 },  0, 0, VE,  .unit = "level" },
    { "6",     "Level 6",
                0, AV_OPT_TYPE_CONST, { .i64 = VP9Level6  },  0, 0, VE,  .unit = "level" },
    { "6.1",   "Level 6.1",
                0, AV_OPT_TYPE_CONST, { .i64 = VP9Level61 },  0, 0, VE,  .unit = "level" },
    { "6.2",   "Level 6.2",
                0, AV_OPT_TYPE_CONST, { .i64 = VP9Level62 },  0, 0, VE,  .unit = "level" },
    { NULL, }
};

DECLARE_MEDIACODEC_ENCODER(vp9, "VP9", AV_CODEC_ID_VP9)

#endif  // CONFIG_VP9_MEDIACODEC_ENCODER

#if CONFIG_MPEG4_MEDIACODEC_ENCODER

enum MediaCodecMpeg4Level {
    MPEG4Level0  = 0x01,
    MPEG4Level0b  = 0x02,
    MPEG4Level1 = 0x04,
    MPEG4Level2  = 0x08,
    MPEG4Level3 = 0x10,
    MPEG4Level3b = 0x18,
    MPEG4Level4 = 0x20,
    MPEG4Level4a  = 0x40,
    MPEG4Level5  = 0x80,
    MPEG4Level6 = 0x100,
};

static const AVOption mpeg4_options[] = {
    COMMON_OPTION

    FF_MPEG4_PROFILE_OPTS

    { "level", "Specify tier and level",
                OFFSET(level), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, VE, .unit = "level" },
    { "0",     "Level 0",
                0, AV_OPT_TYPE_CONST, { .i64 = MPEG4Level0  },  0, 0, VE,  .unit = "level" },
    { "0b",    "Level 0b",
                0, AV_OPT_TYPE_CONST, { .i64 = MPEG4Level0b },  0, 0, VE,  .unit = "level" },
    { "1",     "Level 1",
                0, AV_OPT_TYPE_CONST, { .i64 = MPEG4Level1  },  0, 0, VE,  .unit = "level" },
    { "2",     "Level 2",
                0, AV_OPT_TYPE_CONST, { .i64 = MPEG4Level2 },  0, 0, VE,  .unit = "level" },
    { "3",     "Level 3",
                0, AV_OPT_TYPE_CONST, { .i64 = MPEG4Level3  },  0, 0, VE,  .unit = "level" },
    { "3b",    "Level 3b",
                0, AV_OPT_TYPE_CONST, { .i64 = MPEG4Level3b },  0, 0, VE,  .unit = "level" },
    { "4",     "Level 4",
                0, AV_OPT_TYPE_CONST, { .i64 = MPEG4Level4  },  0, 0, VE,  .unit = "level" },
    { "4a",    "Level 4a",
                0, AV_OPT_TYPE_CONST, { .i64 = MPEG4Level4a },  0, 0, VE,  .unit = "level" },
    { "5",     "Level 5",
                0, AV_OPT_TYPE_CONST, { .i64 = MPEG4Level5  },  0, 0, VE,  .unit = "level" },
    { "6",     "Level 6",
                0, AV_OPT_TYPE_CONST, { .i64 = MPEG4Level6  },  0, 0, VE,  .unit = "level" },
    { NULL, }
};

DECLARE_MEDIACODEC_ENCODER(mpeg4, "MPEG-4", AV_CODEC_ID_MPEG4)

#endif  // CONFIG_MPEG4_MEDIACODEC_ENCODER

#if CONFIG_AV1_MEDIACODEC_ENCODER

enum MediaCodecAV1Level {
    AV1Level2  = 0x1,
    AV1Level21 = 0x2,
    AV1Level22 = 0x4,
    AV1Level23 = 0x8,
    AV1Level3  = 0x10,
    AV1Level31 = 0x20,
    AV1Level32 = 0x40,
    AV1Level33 = 0x80,
    AV1Level4  = 0x100,
    AV1Level41 = 0x200,
    AV1Level42 = 0x400,
    AV1Level43 = 0x800,
    AV1Level5  = 0x1000,
    AV1Level51 = 0x2000,
    AV1Level52 = 0x4000,
    AV1Level53 = 0x8000,
    AV1Level6  = 0x10000,
    AV1Level61 = 0x20000,
    AV1Level62 = 0x40000,
    AV1Level63 = 0x80000,
    AV1Level7  = 0x100000,
    AV1Level71 = 0x200000,
    AV1Level72 = 0x400000,
    AV1Level73 = 0x800000,
};

static const AVOption av1_options[] = {
    COMMON_OPTION

    FF_AV1_PROFILE_OPTS

    { "level", "Specify tier and level",
                OFFSET(level), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, VE, .unit = "level" },
    { "2",     "Level 2",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level2  },  0, 0, VE,  .unit = "level" },
    { "2.1",    "Level 2.1",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level21 },  0, 0, VE,  .unit = "level" },
    { "2.2",    "Level 2.2",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level22 },  0, 0, VE,  .unit = "level" },
    { "2.3",    "Level 2.3",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level23 },  0, 0, VE,  .unit = "level" },
    { "3",      "Level 3",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level3  },  0, 0, VE,  .unit = "level" },
    { "3.1",    "Level 3.1",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level31 },  0, 0, VE,  .unit = "level" },
    { "3.2",    "Level 3.2",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level32 },  0, 0, VE,  .unit = "level" },
    { "3.3",    "Level 3.3",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level33 },  0, 0, VE,  .unit = "level" },
    { "4",      "Level 4",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level4  },  0, 0, VE,  .unit = "level" },
    { "4.1",    "Level 4.1",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level41 },  0, 0, VE,  .unit = "level" },
    { "4.2",    "Level 4.2",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level42 },  0, 0, VE,  .unit = "level" },
    { "4.3",    "Level 4.3",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level43 },  0, 0, VE,  .unit = "level" },
    { "5",      "Level 5",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level5  },  0, 0, VE,  .unit = "level" },
    { "5.1",    "Level 5.1",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level51 },  0, 0, VE,  .unit = "level" },
    { "5.2",    "Level 5.2",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level52 },  0, 0, VE,  .unit = "level" },
    { "5.3",    "Level 5.3",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level53 },  0, 0, VE,  .unit = "level" },
    { "6",      "Level 6",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level6  },  0, 0, VE,  .unit = "level" },
    { "6.1",    "Level 6.1",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level61 },  0, 0, VE,  .unit = "level" },
    { "6.2",    "Level 6.2",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level62 },  0, 0, VE,  .unit = "level" },
    { "6.3",    "Level 6.3",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level63 },  0, 0, VE,  .unit = "level" },
    { "7",      "Level 7",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level7  },  0, 0, VE,  .unit = "level" },
    { "7.1",    "Level 7.1",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level71 },  0, 0, VE,  .unit = "level" },
    { "7.2",    "Level 7.2",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level72 },  0, 0, VE,  .unit = "level" },
    { "7.3",    "Level 7.3",
                0, AV_OPT_TYPE_CONST, { .i64 = AV1Level73 },  0, 0, VE,  .unit = "level" },
    { NULL, }
};

DECLARE_MEDIACODEC_ENCODER(av1, "AV1", AV_CODEC_ID_AV1)

#endif  // CONFIG_AV1_MEDIACODEC_ENCODER
