#include "libavutil/pixdesc.h"
#include "avcodec.h"
#include "internal.h"
+#include "packet_internal.h"
typedef struct libx265Context {
const AVClass *class;
const x265_api *api;
float crf;
+ int cqp;
int forced_idr;
char *preset;
char *tune;
char *profile;
- char *x265_opts;
+ AVDictionary *x265_opts;
+
+ /**
+ * If the encoder does not support ROI then warn the first time we
+ * encounter a frame with ROI side data.
+ */
+ int roi_warned;
} libx265Context;
static int is_keyframe(NalUnitType naltype)
return 0;
}
+static av_cold int libx265_param_parse_float(AVCodecContext *avctx,
+ const char *key, float value)
+{
+ libx265Context *ctx = avctx->priv_data;
+ char buf[256];
+
+ snprintf(buf, sizeof(buf), "%2.2f", value);
+ if (ctx->api->param_parse(ctx->params, key, buf) == X265_PARAM_BAD_VALUE) {
+ av_log(avctx, AV_LOG_ERROR, "Invalid value %2.2f for param \"%s\".\n", value, key);
+ return AVERROR(EINVAL);
+ }
+
+ return 0;
+}
+
+static av_cold int libx265_param_parse_int(AVCodecContext *avctx,
+ const char *key, int value)
+{
+ libx265Context *ctx = avctx->priv_data;
+ char buf[256];
+
+ snprintf(buf, sizeof(buf), "%d", value);
+ if (ctx->api->param_parse(ctx->params, key, buf) == X265_PARAM_BAD_VALUE) {
+ av_log(avctx, AV_LOG_ERROR, "Invalid value %d for param \"%s\".\n", value, key);
+ return AVERROR(EINVAL);
+ }
+
+ return 0;
+}
+
static av_cold int libx265_encode_init(AVCodecContext *avctx)
{
libx265Context *ctx = avctx->priv_data;
+ AVCPBProperties *cpb_props = NULL;
+ int ret;
ctx->api = x265_api_get(av_pix_fmt_desc_get(avctx->pix_fmt)->comp[0].depth);
if (!ctx->api)
}
ctx->params->frameNumThreads = avctx->thread_count;
- ctx->params->fpsNum = avctx->time_base.den;
- ctx->params->fpsDenom = avctx->time_base.num * avctx->ticks_per_frame;
+ if (avctx->framerate.num > 0 && avctx->framerate.den > 0) {
+ ctx->params->fpsNum = avctx->framerate.num;
+ ctx->params->fpsDenom = avctx->framerate.den;
+ } else {
+ ctx->params->fpsNum = avctx->time_base.den;
+ ctx->params->fpsDenom = avctx->time_base.num * avctx->ticks_per_frame;
+ }
ctx->params->sourceWidth = avctx->width;
ctx->params->sourceHeight = avctx->height;
ctx->params->bEnablePsnr = !!(avctx->flags & AV_CODEC_FLAG_PSNR);
return AVERROR(EINVAL);
}
+
+ ctx->params->vui.bEnableVideoSignalTypePresentFlag = 1;
+
+ ctx->params->vui.bEnableVideoFullRangeFlag = avctx->pix_fmt == AV_PIX_FMT_YUVJ420P ||
+ avctx->pix_fmt == AV_PIX_FMT_YUVJ422P ||
+ avctx->pix_fmt == AV_PIX_FMT_YUVJ444P ||
+ avctx->color_range == AVCOL_RANGE_JPEG;
+
if ((avctx->color_primaries <= AVCOL_PRI_SMPTE432 &&
avctx->color_primaries != AVCOL_PRI_UNSPECIFIED) ||
(avctx->color_trc <= AVCOL_TRC_ARIB_STD_B67 &&
(avctx->colorspace <= AVCOL_SPC_ICTCP &&
avctx->colorspace != AVCOL_SPC_UNSPECIFIED)) {
- ctx->params->vui.bEnableVideoSignalTypePresentFlag = 1;
ctx->params->vui.bEnableColorDescriptionPresentFlag = 1;
// x265 validates the parameters internally
ctx->params->vui.colorPrimaries = avctx->color_primaries;
ctx->params->vui.transferCharacteristics = avctx->color_trc;
+#if X265_BUILD >= 159
+ if (avctx->color_trc == AVCOL_TRC_ARIB_STD_B67)
+ ctx->params->preferredTransferCharacteristics = ctx->params->vui.transferCharacteristics;
+#endif
ctx->params->vui.matrixCoeffs = avctx->colorspace;
}
} else if (avctx->bit_rate > 0) {
ctx->params->rc.bitrate = avctx->bit_rate / 1000;
ctx->params->rc.rateControlMode = X265_RC_ABR;
+ } else if (ctx->cqp >= 0) {
+ ret = libx265_param_parse_int(avctx, "qp", ctx->cqp);
+ if (ret < 0)
+ return ret;
+ }
+
+#if X265_BUILD >= 89
+ if (avctx->qmin >= 0) {
+ ret = libx265_param_parse_int(avctx, "qpmin", avctx->qmin);
+ if (ret < 0)
+ return ret;
+ }
+ if (avctx->qmax >= 0) {
+ ret = libx265_param_parse_int(avctx, "qpmax", avctx->qmax);
+ if (ret < 0)
+ return ret;
+ }
+#endif
+ if (avctx->max_qdiff >= 0) {
+ ret = libx265_param_parse_int(avctx, "qpstep", avctx->max_qdiff);
+ if (ret < 0)
+ return ret;
+ }
+ if (avctx->qblur >= 0) {
+ ret = libx265_param_parse_float(avctx, "qblur", avctx->qblur);
+ if (ret < 0)
+ return ret;
+ }
+ if (avctx->qcompress >= 0) {
+ ret = libx265_param_parse_float(avctx, "qcomp", avctx->qcompress);
+ if (ret < 0)
+ return ret;
+ }
+ if (avctx->i_quant_factor >= 0) {
+ ret = libx265_param_parse_float(avctx, "ipratio", avctx->i_quant_factor);
+ if (ret < 0)
+ return ret;
+ }
+ if (avctx->b_quant_factor >= 0) {
+ ret = libx265_param_parse_float(avctx, "pbratio", avctx->b_quant_factor);
+ if (ret < 0)
+ return ret;
}
ctx->params->rc.vbvBufferSize = avctx->rc_buffer_size / 1000;
ctx->params->rc.vbvMaxBitrate = avctx->rc_max_rate / 1000;
+ cpb_props = ff_add_cpb_side_data(avctx);
+ if (!cpb_props)
+ return AVERROR(ENOMEM);
+ cpb_props->buffer_size = ctx->params->rc.vbvBufferSize * 1000;
+ cpb_props->max_bitrate = ctx->params->rc.vbvMaxBitrate * 1000LL;
+ cpb_props->avg_bitrate = ctx->params->rc.bitrate * 1000LL;
+
if (!(avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER))
ctx->params->bRepeatHeaders = 1;
- if (ctx->x265_opts) {
- AVDictionary *dict = NULL;
- AVDictionaryEntry *en = NULL;
+ if (avctx->gop_size >= 0) {
+ ret = libx265_param_parse_int(avctx, "keyint", avctx->gop_size);
+ if (ret < 0)
+ return ret;
+ }
+ if (avctx->keyint_min > 0) {
+ ret = libx265_param_parse_int(avctx, "min-keyint", avctx->keyint_min);
+ if (ret < 0)
+ return ret;
+ }
+ if (avctx->max_b_frames >= 0) {
+ ret = libx265_param_parse_int(avctx, "bframes", avctx->max_b_frames);
+ if (ret < 0)
+ return ret;
+ }
+ if (avctx->refs >= 0) {
+ ret = libx265_param_parse_int(avctx, "ref", avctx->refs);
+ if (ret < 0)
+ return ret;
+ }
- if (!av_dict_parse_string(&dict, ctx->x265_opts, "=", ":", 0)) {
- while ((en = av_dict_get(dict, "", en, AV_DICT_IGNORE_SUFFIX))) {
- int parse_ret = ctx->api->param_parse(ctx->params, en->key, en->value);
-
- switch (parse_ret) {
- case X265_PARAM_BAD_NAME:
- av_log(avctx, AV_LOG_WARNING,
- "Unknown option: %s.\n", en->key);
- break;
- case X265_PARAM_BAD_VALUE:
- av_log(avctx, AV_LOG_WARNING,
- "Invalid value for %s: %s.\n", en->key, en->value);
- break;
- default:
- break;
- }
+ {
+ AVDictionaryEntry *en = NULL;
+ while ((en = av_dict_get(ctx->x265_opts, "", en, AV_DICT_IGNORE_SUFFIX))) {
+ int parse_ret = ctx->api->param_parse(ctx->params, en->key, en->value);
+
+ switch (parse_ret) {
+ case X265_PARAM_BAD_NAME:
+ av_log(avctx, AV_LOG_WARNING,
+ "Unknown option: %s.\n", en->key);
+ break;
+ case X265_PARAM_BAD_VALUE:
+ av_log(avctx, AV_LOG_WARNING,
+ "Invalid value for %s: %s.\n", en->key, en->value);
+ break;
+ default:
+ break;
}
- av_dict_free(&dict);
}
}
}
memcpy(avctx->extradata, nal[0].payload, avctx->extradata_size);
+ memset(avctx->extradata + avctx->extradata_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
}
return 0;
}
+static av_cold int libx265_encode_set_roi(libx265Context *ctx, const AVFrame *frame, x265_picture* pic)
+{
+ AVFrameSideData *sd = av_frame_get_side_data(frame, AV_FRAME_DATA_REGIONS_OF_INTEREST);
+ if (sd) {
+ if (ctx->params->rc.aqMode == X265_AQ_NONE) {
+ if (!ctx->roi_warned) {
+ ctx->roi_warned = 1;
+ av_log(ctx, AV_LOG_WARNING, "Adaptive quantization must be enabled to use ROI encoding, skipping ROI.\n");
+ }
+ } else {
+ /* 8x8 block when qg-size is 8, 16*16 block otherwise. */
+ int mb_size = (ctx->params->rc.qgSize == 8) ? 8 : 16;
+ int mbx = (frame->width + mb_size - 1) / mb_size;
+ int mby = (frame->height + mb_size - 1) / mb_size;
+ int qp_range = 51 + 6 * (pic->bitDepth - 8);
+ int nb_rois;
+ const AVRegionOfInterest *roi;
+ uint32_t roi_size;
+ float *qoffsets; /* will be freed after encode is called. */
+
+ roi = (const AVRegionOfInterest*)sd->data;
+ roi_size = roi->self_size;
+ if (!roi_size || sd->size % roi_size != 0) {
+ av_log(ctx, AV_LOG_ERROR, "Invalid AVRegionOfInterest.self_size.\n");
+ return AVERROR(EINVAL);
+ }
+ nb_rois = sd->size / roi_size;
+
+ qoffsets = av_mallocz_array(mbx * mby, sizeof(*qoffsets));
+ if (!qoffsets)
+ return AVERROR(ENOMEM);
+
+ // This list must be iterated in reverse because the first
+ // region in the list applies when regions overlap.
+ for (int i = nb_rois - 1; i >= 0; i--) {
+ int startx, endx, starty, endy;
+ float qoffset;
+
+ roi = (const AVRegionOfInterest*)(sd->data + roi_size * i);
+
+ starty = FFMIN(mby, roi->top / mb_size);
+ endy = FFMIN(mby, (roi->bottom + mb_size - 1)/ mb_size);
+ startx = FFMIN(mbx, roi->left / mb_size);
+ endx = FFMIN(mbx, (roi->right + mb_size - 1)/ mb_size);
+
+ if (roi->qoffset.den == 0) {
+ av_free(qoffsets);
+ av_log(ctx, AV_LOG_ERROR, "AVRegionOfInterest.qoffset.den must not be zero.\n");
+ return AVERROR(EINVAL);
+ }
+ qoffset = roi->qoffset.num * 1.0f / roi->qoffset.den;
+ qoffset = av_clipf(qoffset * qp_range, -qp_range, +qp_range);
+
+ for (int y = starty; y < endy; y++)
+ for (int x = startx; x < endx; x++)
+ qoffsets[x + y*mbx] = qoffset;
+ }
+
+ pic->quantOffsets = qoffsets;
+ }
+ }
+ return 0;
+}
+
static int libx265_encode_frame(AVCodecContext *avctx, AVPacket *pkt,
const AVFrame *pic, int *got_packet)
{
x265_picture x265pic_out = { 0 };
x265_nal *nal;
uint8_t *dst;
+ int pict_type;
int payload = 0;
int nnal;
int ret;
pic->pict_type == AV_PICTURE_TYPE_P ? X265_TYPE_P :
pic->pict_type == AV_PICTURE_TYPE_B ? X265_TYPE_B :
X265_TYPE_AUTO;
+
+ ret = libx265_encode_set_roi(ctx, pic, &x265pic);
+ if (ret < 0)
+ return ret;
+
+ if (pic->reordered_opaque) {
+ x265pic.userData = av_malloc(sizeof(pic->reordered_opaque));
+ if (!x265pic.userData) {
+ av_freep(&x265pic.quantOffsets);
+ return AVERROR(ENOMEM);
+ }
+
+ memcpy(x265pic.userData, &pic->reordered_opaque, sizeof(pic->reordered_opaque));
+ }
}
ret = ctx->api->encoder_encode(ctx->encoder, &nal, &nnal,
pic ? &x265pic : NULL, &x265pic_out);
+
+ av_freep(&x265pic.quantOffsets);
+
if (ret < 0)
return AVERROR_EXTERNAL;
pkt->pts = x265pic_out.pts;
pkt->dts = x265pic_out.dts;
-#if FF_API_CODED_FRAME
-FF_DISABLE_DEPRECATION_WARNINGS
switch (x265pic_out.sliceType) {
case X265_TYPE_IDR:
case X265_TYPE_I:
- avctx->coded_frame->pict_type = AV_PICTURE_TYPE_I;
+ pict_type = AV_PICTURE_TYPE_I;
break;
case X265_TYPE_P:
- avctx->coded_frame->pict_type = AV_PICTURE_TYPE_P;
+ pict_type = AV_PICTURE_TYPE_P;
break;
case X265_TYPE_B:
- avctx->coded_frame->pict_type = AV_PICTURE_TYPE_B;
+ case X265_TYPE_BREF:
+ pict_type = AV_PICTURE_TYPE_B;
break;
+ default:
+ av_log(avctx, AV_LOG_ERROR, "Unknown picture type encountered.\n");
+ return AVERROR_EXTERNAL;
}
-FF_ENABLE_DEPRECATION_WARNINGS
-#endif
#if X265_BUILD >= 130
if (x265pic_out.sliceType == X265_TYPE_B)
#endif
pkt->flags |= AV_PKT_FLAG_DISPOSABLE;
+ ff_side_data_set_encoder_stats(pkt, x265pic_out.frameData.qp * FF_QP2LAMBDA, NULL, 0, pict_type);
+
+ if (x265pic_out.userData) {
+ memcpy(&avctx->reordered_opaque, x265pic_out.userData, sizeof(avctx->reordered_opaque));
+ av_freep(&x265pic_out.userData);
+ } else
+ avctx->reordered_opaque = 0;
+
*got_packet = 1;
return 0;
}
static const enum AVPixelFormat x265_csp_eight[] = {
AV_PIX_FMT_YUV420P,
+ AV_PIX_FMT_YUVJ420P,
AV_PIX_FMT_YUV422P,
+ AV_PIX_FMT_YUVJ422P,
AV_PIX_FMT_YUV444P,
+ AV_PIX_FMT_YUVJ444P,
AV_PIX_FMT_GBRP,
AV_PIX_FMT_GRAY8,
AV_PIX_FMT_NONE
static const enum AVPixelFormat x265_csp_ten[] = {
AV_PIX_FMT_YUV420P,
+ AV_PIX_FMT_YUVJ420P,
AV_PIX_FMT_YUV422P,
+ AV_PIX_FMT_YUVJ422P,
AV_PIX_FMT_YUV444P,
+ AV_PIX_FMT_YUVJ444P,
AV_PIX_FMT_GBRP,
AV_PIX_FMT_YUV420P10,
AV_PIX_FMT_YUV422P10,
static const enum AVPixelFormat x265_csp_twelve[] = {
AV_PIX_FMT_YUV420P,
+ AV_PIX_FMT_YUVJ420P,
AV_PIX_FMT_YUV422P,
+ AV_PIX_FMT_YUVJ422P,
AV_PIX_FMT_YUV444P,
+ AV_PIX_FMT_YUVJ444P,
AV_PIX_FMT_GBRP,
AV_PIX_FMT_YUV420P10,
AV_PIX_FMT_YUV422P10,
#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options[] = {
{ "crf", "set the x265 crf", OFFSET(crf), AV_OPT_TYPE_FLOAT, { .dbl = -1 }, -1, FLT_MAX, VE },
+ { "qp", "set the x265 qp", OFFSET(cqp), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, VE },
{ "forced-idr", "if forcing keyframes, force them as IDR frames", OFFSET(forced_idr),AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE },
{ "preset", "set the x265 preset", OFFSET(preset), AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE },
{ "tune", "set the x265 tune parameter", OFFSET(tune), AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE },
{ "profile", "set the x265 profile", OFFSET(profile), AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE },
- { "x265-params", "set the x265 configuration using a :-separated list of key=value parameters", OFFSET(x265_opts), AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE },
+ { "x265-params", "set the x265 configuration using a :-separated list of key=value parameters", OFFSET(x265_opts), AV_OPT_TYPE_DICT, { 0 }, 0, 0, VE },
{ NULL }
};
static const AVCodecDefault x265_defaults[] = {
{ "b", "0" },
+ { "bf", "-1" },
+ { "g", "-1" },
+ { "keyint_min", "-1" },
+ { "refs", "-1" },
+ { "qmin", "-1" },
+ { "qmax", "-1" },
+ { "qdiff", "-1" },
+ { "qblur", "-1" },
+ { "qcomp", "-1" },
+ { "i_qfactor", "-1" },
+ { "b_qfactor", "-1" },
{ NULL },
};
.priv_data_size = sizeof(libx265Context),
.priv_class = &class,
.defaults = x265_defaults,
- .capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_AUTO_THREADS,
+ .capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_OTHER_THREADS |
+ AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE,
+ .caps_internal = FF_CODEC_CAP_AUTO_THREADS,
.wrapper_name = "libx265",
};