forked from FFmpeg/FFmpeg
avcodec/libjxl: add animated JPEG XL encoder
libjxl supports animated encoding, so we add a wrapper to the library using the receive_packet callback method. This code was based largely on a patch sent by Zsolt Vadász, although it was updated to use more recent coding practices and many of the leaks and issues were fixed. Reviewed-by: Marth64 <marth64@proxyid.net> Co-authored-by: Zsolt Vadász <zsolt_vadasz@protonmail.com> Signed-off-by: Leo Izen <leo.izen@gmail.com>
This commit is contained in:
parent
060464105b
commit
f3c4082645
10 changed files with 335 additions and 94 deletions
|
@ -9,6 +9,7 @@ version <next>:
|
||||||
- libx265 alpha layer encoding
|
- libx265 alpha layer encoding
|
||||||
- ADPCM IMA Xbox decoder
|
- ADPCM IMA Xbox decoder
|
||||||
- Enhanced FLV v2: Multitrack audio/video, modern codec support
|
- Enhanced FLV v2: Multitrack audio/video, modern codec support
|
||||||
|
- Animated JPEG XL encoding (via libjxl)
|
||||||
|
|
||||||
version 7.1:
|
version 7.1:
|
||||||
- Raw Captions with Time (RCWT) closed caption demuxer
|
- Raw Captions with Time (RCWT) closed caption demuxer
|
||||||
|
|
2
configure
vendored
2
configure
vendored
|
@ -3561,6 +3561,8 @@ libgsm_ms_decoder_deps="libgsm"
|
||||||
libgsm_ms_encoder_deps="libgsm"
|
libgsm_ms_encoder_deps="libgsm"
|
||||||
libilbc_decoder_deps="libilbc"
|
libilbc_decoder_deps="libilbc"
|
||||||
libilbc_encoder_deps="libilbc"
|
libilbc_encoder_deps="libilbc"
|
||||||
|
libjxl_anim_decoder_deps="libjxl libjxl_threads"
|
||||||
|
libjxl_anim_encoder_deps="libjxl libjxl_threads"
|
||||||
libjxl_decoder_deps="libjxl libjxl_threads"
|
libjxl_decoder_deps="libjxl libjxl_threads"
|
||||||
libjxl_encoder_deps="libjxl libjxl_threads"
|
libjxl_encoder_deps="libjxl libjxl_threads"
|
||||||
libkvazaar_encoder_deps="libkvazaar"
|
libkvazaar_encoder_deps="libkvazaar"
|
||||||
|
|
|
@ -2,6 +2,9 @@ The last version increases of all libraries were on 2024-03-07
|
||||||
|
|
||||||
API changes, most recent first:
|
API changes, most recent first:
|
||||||
|
|
||||||
|
2025-01-03 - xxxxxxxxxx - lavc 61.29.100 - codec_id.h
|
||||||
|
Add AV_CODEC_ID_JPEGXL_ANIM.
|
||||||
|
|
||||||
2025-01-xx - xxxxxxxxxx - lavu 59.54.100 - frame.h
|
2025-01-xx - xxxxxxxxxx - lavu 59.54.100 - frame.h
|
||||||
Add AV_CH_LAYOUT_5POINT1POINT2 and AV_CHANNEL_LAYOUT_5POINT1POINT2.
|
Add AV_CH_LAYOUT_5POINT1POINT2 and AV_CHANNEL_LAYOUT_5POINT1POINT2.
|
||||||
|
|
||||||
|
|
|
@ -778,6 +778,8 @@ extern const FFCodec ff_libgsm_ms_encoder;
|
||||||
extern const FFCodec ff_libgsm_ms_decoder;
|
extern const FFCodec ff_libgsm_ms_decoder;
|
||||||
extern const FFCodec ff_libilbc_encoder;
|
extern const FFCodec ff_libilbc_encoder;
|
||||||
extern const FFCodec ff_libilbc_decoder;
|
extern const FFCodec ff_libilbc_decoder;
|
||||||
|
extern const FFCodec ff_libjxl_anim_decoder;
|
||||||
|
extern const FFCodec ff_libjxl_anim_encoder;
|
||||||
extern const FFCodec ff_libjxl_decoder;
|
extern const FFCodec ff_libjxl_decoder;
|
||||||
extern const FFCodec ff_libjxl_encoder;
|
extern const FFCodec ff_libjxl_encoder;
|
||||||
extern const FFCodec ff_liblc3_encoder;
|
extern const FFCodec ff_liblc3_encoder;
|
||||||
|
|
|
@ -1977,6 +1977,14 @@ static const AVCodecDescriptor codec_descriptors[] = {
|
||||||
.long_name = NULL_IF_CONFIG_SMALL("RealVideo 6.0"),
|
.long_name = NULL_IF_CONFIG_SMALL("RealVideo 6.0"),
|
||||||
.props = AV_CODEC_PROP_LOSSY | AV_CODEC_PROP_REORDER,
|
.props = AV_CODEC_PROP_LOSSY | AV_CODEC_PROP_REORDER,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.id = AV_CODEC_ID_JPEGXL_ANIM,
|
||||||
|
.type = AVMEDIA_TYPE_VIDEO,
|
||||||
|
.name = "jpegxl_anim",
|
||||||
|
.long_name = NULL_IF_CONFIG_SMALL("JPEG XL animated"),
|
||||||
|
.props = AV_CODEC_PROP_LOSSY | AV_CODEC_PROP_LOSSLESS,
|
||||||
|
.mime_types= MT("image/jxl"),
|
||||||
|
},
|
||||||
|
|
||||||
/* various PCM "codecs" */
|
/* various PCM "codecs" */
|
||||||
{
|
{
|
||||||
|
|
|
@ -328,6 +328,7 @@ enum AVCodecID {
|
||||||
AV_CODEC_ID_LEAD,
|
AV_CODEC_ID_LEAD,
|
||||||
AV_CODEC_ID_DNXUC,
|
AV_CODEC_ID_DNXUC,
|
||||||
AV_CODEC_ID_RV60,
|
AV_CODEC_ID_RV60,
|
||||||
|
AV_CODEC_ID_JPEGXL_ANIM,
|
||||||
|
|
||||||
/* various PCM "codecs" */
|
/* various PCM "codecs" */
|
||||||
AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs
|
AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs
|
||||||
|
|
|
@ -1534,7 +1534,7 @@ flush:
|
||||||
}
|
}
|
||||||
|
|
||||||
const AVCodecParser ff_jpegxl_parser = {
|
const AVCodecParser ff_jpegxl_parser = {
|
||||||
.codec_ids = { AV_CODEC_ID_JPEGXL },
|
.codec_ids = { AV_CODEC_ID_JPEGXL, AV_CODEC_ID_JPEGXL_ANIM },
|
||||||
.priv_data_size = sizeof(JXLParseContext),
|
.priv_data_size = sizeof(JXLParseContext),
|
||||||
.parser_parse = jpegxl_parse,
|
.parser_parse = jpegxl_parse,
|
||||||
.parser_close = ff_parse_close,
|
.parser_close = ff_parse_close,
|
||||||
|
|
|
@ -549,3 +549,19 @@ const FFCodec ff_libjxl_decoder = {
|
||||||
FF_CODEC_CAP_ICC_PROFILES,
|
FF_CODEC_CAP_ICC_PROFILES,
|
||||||
.p.wrapper_name = "libjxl",
|
.p.wrapper_name = "libjxl",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FFCodec ff_libjxl_anim_decoder = {
|
||||||
|
.p.name = "libjxl_anim",
|
||||||
|
CODEC_LONG_NAME("libjxl JPEG XL animated"),
|
||||||
|
.p.type = AVMEDIA_TYPE_VIDEO,
|
||||||
|
.p.id = AV_CODEC_ID_JPEGXL_ANIM,
|
||||||
|
.priv_data_size = sizeof(LibJxlDecodeContext),
|
||||||
|
.init = libjxl_decode_init,
|
||||||
|
FF_CODEC_RECEIVE_FRAME_CB(libjxl_receive_frame),
|
||||||
|
.close = libjxl_decode_close,
|
||||||
|
.p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_OTHER_THREADS,
|
||||||
|
.caps_internal = FF_CODEC_CAP_NOT_INIT_THREADSAFE |
|
||||||
|
FF_CODEC_CAP_AUTO_THREADS | FF_CODEC_CAP_INIT_CLEANUP |
|
||||||
|
FF_CODEC_CAP_ICC_PROFILES,
|
||||||
|
.p.wrapper_name = "libjxl",
|
||||||
|
};
|
||||||
|
|
|
@ -56,6 +56,12 @@ typedef struct LibJxlEncodeContext {
|
||||||
int xyb;
|
int xyb;
|
||||||
uint8_t *buffer;
|
uint8_t *buffer;
|
||||||
size_t buffer_size;
|
size_t buffer_size;
|
||||||
|
JxlPixelFormat jxl_fmt;
|
||||||
|
|
||||||
|
/* animation stuff */
|
||||||
|
AVFrame *frame;
|
||||||
|
AVFrame *prev;
|
||||||
|
int64_t duration;
|
||||||
} LibJxlEncodeContext;
|
} LibJxlEncodeContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -87,9 +93,9 @@ static float quality_to_distance(float quality)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initalize the encoder on a per-frame basis. All of these need to be set
|
* Initalize the encoder on a per-file basis. All of these need to be set
|
||||||
* once each time the encoder is reset, which it must be each frame to make
|
* once each time the encoder is reset, which is each frame for still
|
||||||
* the image2 muxer work.
|
* images, to make the image2 muxer work. For animation this is run once.
|
||||||
*
|
*
|
||||||
* @return 0 upon success, negative on failure.
|
* @return 0 upon success, negative on failure.
|
||||||
*/
|
*/
|
||||||
|
@ -100,12 +106,6 @@ static int libjxl_init_jxl_encoder(AVCodecContext *avctx)
|
||||||
/* reset the encoder every frame for image2 muxer */
|
/* reset the encoder every frame for image2 muxer */
|
||||||
JxlEncoderReset(ctx->encoder);
|
JxlEncoderReset(ctx->encoder);
|
||||||
|
|
||||||
ctx->options = JxlEncoderFrameSettingsCreate(ctx->encoder, NULL);
|
|
||||||
if (!ctx->options) {
|
|
||||||
av_log(avctx, AV_LOG_ERROR, "Failed to create JxlEncoderOptions\n");
|
|
||||||
return AVERROR_EXTERNAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This needs to be set each time the encoder is reset */
|
/* This needs to be set each time the encoder is reset */
|
||||||
if (JxlEncoderSetParallelRunner(ctx->encoder, JxlThreadParallelRunner, ctx->runner)
|
if (JxlEncoderSetParallelRunner(ctx->encoder, JxlThreadParallelRunner, ctx->runner)
|
||||||
!= JXL_ENC_SUCCESS) {
|
!= JXL_ENC_SUCCESS) {
|
||||||
|
@ -113,42 +113,9 @@ static int libjxl_init_jxl_encoder(AVCodecContext *avctx)
|
||||||
return AVERROR_EXTERNAL;
|
return AVERROR_EXTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* these shouldn't fail, libjxl bug notwithstanding */
|
ctx->options = JxlEncoderFrameSettingsCreate(ctx->encoder, NULL);
|
||||||
if (JxlEncoderFrameSettingsSetOption(ctx->options, JXL_ENC_FRAME_SETTING_EFFORT, ctx->effort)
|
if (!ctx->options) {
|
||||||
!= JXL_ENC_SUCCESS) {
|
av_log(avctx, AV_LOG_ERROR, "Failed to create JxlEncoderOptions\n");
|
||||||
av_log(avctx, AV_LOG_ERROR, "Failed to set effort to: %d\n", ctx->effort);
|
|
||||||
return AVERROR_EXTERNAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check for negative, our default */
|
|
||||||
if (ctx->distance < 0.0) {
|
|
||||||
/* use ffmpeg.c -q option if passed */
|
|
||||||
if (avctx->flags & AV_CODEC_FLAG_QSCALE)
|
|
||||||
ctx->distance = quality_to_distance((float)avctx->global_quality / FF_QP2LAMBDA);
|
|
||||||
else
|
|
||||||
/* default 1.0 matches cjxl */
|
|
||||||
ctx->distance = 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 0.01 is the minimum distance accepted for lossy
|
|
||||||
* interpreting any positive value less than this as minimum
|
|
||||||
*/
|
|
||||||
if (ctx->distance > 0.0 && ctx->distance < 0.01)
|
|
||||||
ctx->distance = 0.01;
|
|
||||||
if (JxlEncoderSetFrameDistance(ctx->options, ctx->distance) != JXL_ENC_SUCCESS) {
|
|
||||||
av_log(avctx, AV_LOG_ERROR, "Failed to set distance: %f\n", ctx->distance);
|
|
||||||
return AVERROR_EXTERNAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* In theory the library should automatically enable modular if necessary,
|
|
||||||
* but it appears it won't at the moment due to a bug. This will still
|
|
||||||
* work even if that is patched.
|
|
||||||
*/
|
|
||||||
if (JxlEncoderFrameSettingsSetOption(ctx->options, JXL_ENC_FRAME_SETTING_MODULAR,
|
|
||||||
ctx->modular || ctx->distance <= 0.0 ? 1 : -1) != JXL_ENC_SUCCESS) {
|
|
||||||
av_log(avctx, AV_LOG_ERROR, "Failed to set modular\n");
|
|
||||||
return AVERROR_EXTERNAL;
|
return AVERROR_EXTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,6 +152,47 @@ static av_cold int libjxl_encode_init(AVCodecContext *avctx)
|
||||||
return AVERROR(ENOMEM);
|
return AVERROR(ENOMEM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* check for negative, our default */
|
||||||
|
if (ctx->distance < 0.0) {
|
||||||
|
/* use ffmpeg.c -q option if passed */
|
||||||
|
if (avctx->flags & AV_CODEC_FLAG_QSCALE)
|
||||||
|
ctx->distance = quality_to_distance((float)avctx->global_quality / FF_QP2LAMBDA);
|
||||||
|
else
|
||||||
|
/* default 1.0 matches cjxl */
|
||||||
|
ctx->distance = 1.0;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* 0.01 is the minimum distance accepted for lossy
|
||||||
|
* interpreting any positive value less than this as minimum
|
||||||
|
*/
|
||||||
|
if (ctx->distance > 0.0 && ctx->distance < 0.01)
|
||||||
|
ctx->distance = 0.01;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializer for the animation encoder. This calls the other initializers
|
||||||
|
* to prevent code duplication and also allocates the prev-frame used in the
|
||||||
|
* encoder.
|
||||||
|
*/
|
||||||
|
static av_cold int libjxl_anim_encode_init(AVCodecContext *avctx)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
LibJxlEncodeContext *ctx = avctx->priv_data;
|
||||||
|
|
||||||
|
ret = libjxl_encode_init(avctx);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = libjxl_init_jxl_encoder(avctx);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ctx->frame = av_frame_alloc();
|
||||||
|
if (!ctx->frame)
|
||||||
|
return AVERROR(ENOMEM);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,54 +248,42 @@ static int libjxl_populate_primaries(void *avctx, JxlColorEncoding *jxl_color, e
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode an entire frame. Currently animation, is not supported by
|
* Sends metadata to libjxl based on the first frame of the stream, such as pixel format,
|
||||||
* this encoder, so this will always reinitialize a new still image
|
* orientation, bit depth, and that sort of thing.
|
||||||
* and encode a one-frame image (for image2 and image2pipe).
|
|
||||||
*/
|
*/
|
||||||
static int libjxl_encode_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFrame *frame, int *got_packet)
|
static int libjxl_preprocess_stream(AVCodecContext *avctx, const AVFrame *frame, int animated)
|
||||||
{
|
{
|
||||||
LibJxlEncodeContext *ctx = avctx->priv_data;
|
LibJxlEncodeContext *ctx = avctx->priv_data;
|
||||||
|
int ret;
|
||||||
AVFrameSideData *sd;
|
AVFrameSideData *sd;
|
||||||
const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(frame->format);
|
const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(frame->format);
|
||||||
JxlBasicInfo info;
|
JxlBasicInfo info;
|
||||||
JxlColorEncoding jxl_color;
|
JxlColorEncoding jxl_color;
|
||||||
JxlPixelFormat jxl_fmt;
|
JxlPixelFormat *jxl_fmt = &ctx->jxl_fmt;
|
||||||
int bits_per_sample;
|
int bits_per_sample;
|
||||||
#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 8, 0)
|
#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 8, 0)
|
||||||
JxlBitDepth jxl_bit_depth;
|
JxlBitDepth jxl_bit_depth;
|
||||||
#endif
|
#endif
|
||||||
JxlEncoderStatus jret;
|
|
||||||
int ret;
|
|
||||||
size_t available = ctx->buffer_size;
|
|
||||||
size_t bytes_written = 0;
|
|
||||||
uint8_t *next_out = ctx->buffer;
|
|
||||||
const uint8_t *data;
|
|
||||||
|
|
||||||
ret = libjxl_init_jxl_encoder(avctx);
|
|
||||||
if (ret) {
|
|
||||||
av_log(avctx, AV_LOG_ERROR, "Error frame-initializing JxlEncoder\n");
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* populate the basic info settings */
|
/* populate the basic info settings */
|
||||||
JxlEncoderInitBasicInfo(&info);
|
JxlEncoderInitBasicInfo(&info);
|
||||||
jxl_fmt.num_channels = pix_desc->nb_components;
|
jxl_fmt->num_channels = pix_desc->nb_components;
|
||||||
info.xsize = frame->width;
|
info.xsize = frame->width;
|
||||||
info.ysize = frame->height;
|
info.ysize = frame->height;
|
||||||
info.num_extra_channels = (jxl_fmt.num_channels + 1) % 2;
|
info.num_extra_channels = (jxl_fmt->num_channels + 1) & 0x1;
|
||||||
info.num_color_channels = jxl_fmt.num_channels - info.num_extra_channels;
|
info.num_color_channels = jxl_fmt->num_channels - info.num_extra_channels;
|
||||||
bits_per_sample = av_get_bits_per_pixel(pix_desc) / jxl_fmt.num_channels;
|
bits_per_sample = av_get_bits_per_pixel(pix_desc) / jxl_fmt->num_channels;
|
||||||
info.bits_per_sample = avctx->bits_per_raw_sample > 0 && !(pix_desc->flags & AV_PIX_FMT_FLAG_FLOAT)
|
info.bits_per_sample = avctx->bits_per_raw_sample > 0 && !(pix_desc->flags & AV_PIX_FMT_FLAG_FLOAT)
|
||||||
? avctx->bits_per_raw_sample : bits_per_sample;
|
? avctx->bits_per_raw_sample : bits_per_sample;
|
||||||
info.alpha_bits = (info.num_extra_channels > 0) * info.bits_per_sample;
|
info.alpha_bits = (info.num_extra_channels > 0) * info.bits_per_sample;
|
||||||
if (pix_desc->flags & AV_PIX_FMT_FLAG_FLOAT) {
|
if (pix_desc->flags & AV_PIX_FMT_FLAG_FLOAT) {
|
||||||
info.exponent_bits_per_sample = info.bits_per_sample > 16 ? 8 : 5;
|
info.exponent_bits_per_sample = info.bits_per_sample > 16 ? 8 : 5;
|
||||||
info.alpha_exponent_bits = info.alpha_bits ? info.exponent_bits_per_sample : 0;
|
info.alpha_exponent_bits = info.alpha_bits ? info.exponent_bits_per_sample : 0;
|
||||||
jxl_fmt.data_type = info.bits_per_sample > 16 ? JXL_TYPE_FLOAT : JXL_TYPE_FLOAT16;
|
jxl_fmt->data_type = info.bits_per_sample > 16 ? JXL_TYPE_FLOAT : JXL_TYPE_FLOAT16;
|
||||||
} else {
|
} else {
|
||||||
info.exponent_bits_per_sample = 0;
|
info.exponent_bits_per_sample = 0;
|
||||||
info.alpha_exponent_bits = 0;
|
info.alpha_exponent_bits = 0;
|
||||||
jxl_fmt.data_type = info.bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16;
|
jxl_fmt->data_type = info.bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 8, 0)
|
#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 8, 0)
|
||||||
|
@ -306,8 +302,19 @@ static int libjxl_encode_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFra
|
||||||
|
|
||||||
/* bitexact lossless requires there to be no XYB transform */
|
/* bitexact lossless requires there to be no XYB transform */
|
||||||
info.uses_original_profile = ctx->distance == 0.0 || !ctx->xyb;
|
info.uses_original_profile = ctx->distance == 0.0 || !ctx->xyb;
|
||||||
|
|
||||||
|
/* libjxl doesn't support negative linesizes so we use orientation to work around this */
|
||||||
info.orientation = frame->linesize[0] >= 0 ? JXL_ORIENT_IDENTITY : JXL_ORIENT_FLIP_VERTICAL;
|
info.orientation = frame->linesize[0] >= 0 ? JXL_ORIENT_IDENTITY : JXL_ORIENT_FLIP_VERTICAL;
|
||||||
|
|
||||||
|
if (animated) {
|
||||||
|
info.have_animation = 1;
|
||||||
|
info.animation.have_timecodes = 0;
|
||||||
|
info.animation.num_loops = 0;
|
||||||
|
/* avctx->timebase is in seconds per tick, so we take the reciprocol */
|
||||||
|
info.animation.tps_numerator = avctx->time_base.den;
|
||||||
|
info.animation.tps_denominator = avctx->time_base.num;
|
||||||
|
}
|
||||||
|
|
||||||
if (JxlEncoderSetBasicInfo(ctx->encoder, &info) != JXL_ENC_SUCCESS) {
|
if (JxlEncoderSetBasicInfo(ctx->encoder, &info) != JXL_ENC_SUCCESS) {
|
||||||
av_log(avctx, AV_LOG_ERROR, "Failed to set JxlBasicInfo\n");
|
av_log(avctx, AV_LOG_ERROR, "Failed to set JxlBasicInfo\n");
|
||||||
return AVERROR_EXTERNAL;
|
return AVERROR_EXTERNAL;
|
||||||
|
@ -386,25 +393,70 @@ static int libjxl_encode_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFra
|
||||||
av_log(avctx, AV_LOG_WARNING, "Could not increase codestream level\n");
|
av_log(avctx, AV_LOG_WARNING, "Could not increase codestream level\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
jxl_fmt.endianness = JXL_NATIVE_ENDIAN;
|
return 0;
|
||||||
if (frame->linesize[0] >= 0) {
|
}
|
||||||
jxl_fmt.align = frame->linesize[0];
|
|
||||||
data = frame->data[0];
|
/**
|
||||||
} else {
|
* Sends frame information to libjxl on a per-frame basis. If this is a still image,
|
||||||
jxl_fmt.align = -frame->linesize[0];
|
* this is evaluated once per output file. If this is an animated JPEG XL encode, it
|
||||||
data = frame->data[0] + frame->linesize[0] * (info.ysize - 1);
|
* is called once per frame.
|
||||||
|
*
|
||||||
|
* This returns a buffer to the data that should be passed to libjxl (via the
|
||||||
|
* argument **data). If the linesize is nonnegative, this will be frame->data[0],
|
||||||
|
* although if the linesize is negative, it will be the start of the buffer
|
||||||
|
* instead. *data is just a pointer to a location in frame->data so it should not be
|
||||||
|
* freed directly.
|
||||||
|
*/
|
||||||
|
static int libjxl_preprocess_frame(AVCodecContext *avctx, const AVFrame *frame, const uint8_t **data)
|
||||||
|
{
|
||||||
|
LibJxlEncodeContext *ctx = avctx->priv_data;
|
||||||
|
JxlPixelFormat *jxl_fmt = &ctx->jxl_fmt;
|
||||||
|
|
||||||
|
/* these shouldn't fail, libjxl bug notwithstanding */
|
||||||
|
if (JxlEncoderFrameSettingsSetOption(ctx->options, JXL_ENC_FRAME_SETTING_EFFORT, ctx->effort)
|
||||||
|
!= JXL_ENC_SUCCESS) {
|
||||||
|
av_log(avctx, AV_LOG_ERROR, "Failed to set effort to: %d\n", ctx->effort);
|
||||||
|
return AVERROR_EXTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JxlEncoderAddImageFrame(ctx->options, &jxl_fmt, data, jxl_fmt.align * info.ysize) != JXL_ENC_SUCCESS) {
|
if (JxlEncoderSetFrameDistance(ctx->options, ctx->distance) != JXL_ENC_SUCCESS) {
|
||||||
av_log(avctx, AV_LOG_ERROR, "Failed to add Image Frame\n");
|
av_log(avctx, AV_LOG_ERROR, "Failed to set distance: %f\n", ctx->distance);
|
||||||
return AVERROR_EXTERNAL;
|
return AVERROR_EXTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Run this after the last frame in the image has been passed.
|
* In theory the library should automatically enable modular if necessary,
|
||||||
* TODO support animation
|
* but it appears it won't at the moment due to a bug. This will still
|
||||||
|
* work even if that is patched.
|
||||||
*/
|
*/
|
||||||
JxlEncoderCloseInput(ctx->encoder);
|
if (JxlEncoderFrameSettingsSetOption(ctx->options, JXL_ENC_FRAME_SETTING_MODULAR,
|
||||||
|
ctx->modular || ctx->distance <= 0.0 ? 1 : -1) != JXL_ENC_SUCCESS) {
|
||||||
|
av_log(avctx, AV_LOG_ERROR, "Failed to set modular\n");
|
||||||
|
return AVERROR_EXTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
jxl_fmt->endianness = JXL_NATIVE_ENDIAN;
|
||||||
|
if (frame->linesize[0] >= 0) {
|
||||||
|
jxl_fmt->align = frame->linesize[0];
|
||||||
|
*data = frame->data[0];
|
||||||
|
} else {
|
||||||
|
jxl_fmt->align = -frame->linesize[0];
|
||||||
|
*data = frame->data[0] + frame->linesize[0] * (frame->height - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run libjxl's output processing loop, reallocating the packet as necessary
|
||||||
|
* if libjxl needs more space to work with.
|
||||||
|
*/
|
||||||
|
static int libjxl_process_output(AVCodecContext *avctx, size_t *bytes_written)
|
||||||
|
{
|
||||||
|
LibJxlEncodeContext *ctx = avctx->priv_data;
|
||||||
|
JxlEncoderStatus jret;
|
||||||
|
size_t available = ctx->buffer_size;
|
||||||
|
uint8_t *next_out = ctx->buffer;
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
jret = JxlEncoderProcessOutput(ctx->encoder, &next_out, &available);
|
jret = JxlEncoderProcessOutput(ctx->encoder, &next_out, &available);
|
||||||
|
@ -412,7 +464,7 @@ static int libjxl_encode_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFra
|
||||||
av_log(avctx, AV_LOG_ERROR, "Unspecified libjxl error occurred\n");
|
av_log(avctx, AV_LOG_ERROR, "Unspecified libjxl error occurred\n");
|
||||||
return AVERROR_EXTERNAL;
|
return AVERROR_EXTERNAL;
|
||||||
}
|
}
|
||||||
bytes_written = ctx->buffer_size - available;
|
*bytes_written = ctx->buffer_size - available;
|
||||||
/* all data passed has been encoded */
|
/* all data passed has been encoded */
|
||||||
if (jret == JXL_ENC_SUCCESS)
|
if (jret == JXL_ENC_SUCCESS)
|
||||||
break;
|
break;
|
||||||
|
@ -429,14 +481,58 @@ static int libjxl_encode_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFra
|
||||||
return AVERROR(ENOMEM);
|
return AVERROR(ENOMEM);
|
||||||
ctx->buffer = temp;
|
ctx->buffer = temp;
|
||||||
ctx->buffer_size = new_size;
|
ctx->buffer_size = new_size;
|
||||||
next_out = ctx->buffer + bytes_written;
|
next_out = ctx->buffer + *bytes_written;
|
||||||
available = new_size - bytes_written;
|
available = new_size - *bytes_written;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
av_log(avctx, AV_LOG_ERROR, "Bad libjxl event: %d\n", jret);
|
av_log(avctx, AV_LOG_ERROR, "Bad libjxl event: %d\n", jret);
|
||||||
return AVERROR_EXTERNAL;
|
return AVERROR_EXTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode an entire frame. This will always reinitialize a new still image
|
||||||
|
* and encode a one-frame image (for image2 and image2pipe).
|
||||||
|
*/
|
||||||
|
static int libjxl_encode_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFrame *frame, int *got_packet)
|
||||||
|
{
|
||||||
|
|
||||||
|
LibJxlEncodeContext *ctx = avctx->priv_data;
|
||||||
|
int ret;
|
||||||
|
size_t bytes_written = 0;
|
||||||
|
const uint8_t *data;
|
||||||
|
|
||||||
|
ret = libjxl_init_jxl_encoder(avctx);
|
||||||
|
if (ret < 0) {
|
||||||
|
av_log(avctx, AV_LOG_ERROR, "Error frame-initializing JxlEncoder\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = libjxl_preprocess_stream(avctx, frame, 0);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = libjxl_preprocess_frame(avctx, frame, &data);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (JxlEncoderAddImageFrame(ctx->options, &ctx->jxl_fmt, data, ctx->jxl_fmt.align * frame->height)
|
||||||
|
!= JXL_ENC_SUCCESS) {
|
||||||
|
av_log(avctx, AV_LOG_ERROR, "Failed to add Image Frame\n");
|
||||||
|
return AVERROR_EXTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Run this after the last frame in the image has been passed.
|
||||||
|
*/
|
||||||
|
JxlEncoderCloseInput(ctx->encoder);
|
||||||
|
|
||||||
|
ret = libjxl_process_output(avctx, &bytes_written);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
ret = ff_get_encode_buffer(avctx, pkt, bytes_written, 0);
|
ret = ff_get_encode_buffer(avctx, pkt, bytes_written, 0);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -447,6 +543,94 @@ static int libjxl_encode_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFra
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode one frame of the animation. libjxl requires us to set duration of the frame
|
||||||
|
* but we're only promised the PTS, not the duration. As a result we have to buffer
|
||||||
|
* a frame and subtract the PTS from the last PTS. The last frame uses the previous
|
||||||
|
* frame's calculated duration as a fallback if its duration field is unset.
|
||||||
|
*
|
||||||
|
* We also need to tell libjxl if our frame is the last one, which we won't know upon
|
||||||
|
* receiving a single frame, so we have to buffer a frame as well and send the "last frame"
|
||||||
|
* upon receiving the special EOF frame.
|
||||||
|
*/
|
||||||
|
static int libjxl_anim_encode_frame(AVCodecContext *avctx, AVPacket *pkt)
|
||||||
|
{
|
||||||
|
LibJxlEncodeContext *ctx = avctx->priv_data;
|
||||||
|
int ret = 0;
|
||||||
|
JxlFrameHeader frame_header;
|
||||||
|
size_t bytes_written = 0;
|
||||||
|
const uint8_t *data;
|
||||||
|
|
||||||
|
if (!ctx->prev) {
|
||||||
|
ctx->prev = av_frame_alloc();
|
||||||
|
if (!ctx->prev)
|
||||||
|
return AVERROR(ENOMEM);
|
||||||
|
ret = ff_encode_get_frame(avctx, ctx->prev);
|
||||||
|
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||||
|
return ret;
|
||||||
|
ret = libjxl_preprocess_stream(avctx, ctx->prev, 1);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ff_encode_get_frame(avctx, ctx->frame);
|
||||||
|
if (ret == AVERROR_EOF) {
|
||||||
|
av_frame_free(&ctx->frame);
|
||||||
|
ret = 0;
|
||||||
|
}
|
||||||
|
if (ret == AVERROR(EAGAIN))
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
JxlEncoderInitFrameHeader(&frame_header);
|
||||||
|
|
||||||
|
ctx->duration = ctx->prev->duration ? ctx->prev->duration :
|
||||||
|
ctx->frame ? ctx->frame->pts - ctx->prev->pts :
|
||||||
|
ctx->duration ? ctx->duration :
|
||||||
|
1;
|
||||||
|
|
||||||
|
frame_header.duration = ctx->duration;
|
||||||
|
pkt->duration = ctx->duration;
|
||||||
|
pkt->pts = ctx->prev->pts;
|
||||||
|
pkt->dts = pkt->pts;
|
||||||
|
|
||||||
|
if (JxlEncoderSetFrameHeader(ctx->options, &frame_header) != JXL_ENC_SUCCESS) {
|
||||||
|
av_log(avctx, AV_LOG_ERROR, "Failed to set JxlFrameHeader\n");
|
||||||
|
return AVERROR_EXTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = libjxl_preprocess_frame(avctx, ctx->prev, &data);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (JxlEncoderAddImageFrame(ctx->options, &ctx->jxl_fmt, data, ctx->jxl_fmt.align * ctx->prev->height)
|
||||||
|
!= JXL_ENC_SUCCESS) {
|
||||||
|
av_log(avctx, AV_LOG_ERROR, "Failed to add Image Frame\n");
|
||||||
|
return AVERROR_EXTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx->frame)
|
||||||
|
JxlEncoderCloseInput(ctx->encoder);
|
||||||
|
|
||||||
|
ret = libjxl_process_output(avctx, &bytes_written);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = ff_get_encode_buffer(avctx, pkt, bytes_written, 0);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
memcpy(pkt->data, ctx->buffer, bytes_written);
|
||||||
|
|
||||||
|
if (ctx->frame) {
|
||||||
|
av_frame_unref(ctx->prev);
|
||||||
|
av_frame_move_ref(ctx->prev, ctx->frame);
|
||||||
|
} else {
|
||||||
|
av_frame_free(&ctx->prev);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static av_cold int libjxl_encode_close(AVCodecContext *avctx)
|
static av_cold int libjxl_encode_close(AVCodecContext *avctx)
|
||||||
{
|
{
|
||||||
LibJxlEncodeContext *ctx = avctx->priv_data;
|
LibJxlEncodeContext *ctx = avctx->priv_data;
|
||||||
|
@ -464,6 +648,8 @@ static av_cold int libjxl_encode_close(AVCodecContext *avctx)
|
||||||
ctx->encoder = NULL;
|
ctx->encoder = NULL;
|
||||||
|
|
||||||
av_freep(&ctx->buffer);
|
av_freep(&ctx->buffer);
|
||||||
|
av_frame_free(&ctx->prev);
|
||||||
|
av_frame_free(&ctx->frame);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -488,6 +674,16 @@ static const AVClass libjxl_encode_class = {
|
||||||
.version = LIBAVUTIL_VERSION_INT,
|
.version = LIBAVUTIL_VERSION_INT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const enum AVPixelFormat libjxl_supported_pixfmts[] = {
|
||||||
|
AV_PIX_FMT_RGB24, AV_PIX_FMT_RGBA,
|
||||||
|
AV_PIX_FMT_RGB48, AV_PIX_FMT_RGBA64,
|
||||||
|
AV_PIX_FMT_RGBF32, AV_PIX_FMT_RGBAF32,
|
||||||
|
AV_PIX_FMT_GRAY8, AV_PIX_FMT_YA8,
|
||||||
|
AV_PIX_FMT_GRAY16, AV_PIX_FMT_YA16,
|
||||||
|
AV_PIX_FMT_GRAYF32,
|
||||||
|
AV_PIX_FMT_NONE,
|
||||||
|
};
|
||||||
|
|
||||||
const FFCodec ff_libjxl_encoder = {
|
const FFCodec ff_libjxl_encoder = {
|
||||||
.p.name = "libjxl",
|
.p.name = "libjxl",
|
||||||
CODEC_LONG_NAME("libjxl JPEG XL"),
|
CODEC_LONG_NAME("libjxl JPEG XL"),
|
||||||
|
@ -503,15 +699,27 @@ const FFCodec ff_libjxl_encoder = {
|
||||||
.caps_internal = FF_CODEC_CAP_NOT_INIT_THREADSAFE |
|
.caps_internal = FF_CODEC_CAP_NOT_INIT_THREADSAFE |
|
||||||
FF_CODEC_CAP_AUTO_THREADS | FF_CODEC_CAP_INIT_CLEANUP |
|
FF_CODEC_CAP_AUTO_THREADS | FF_CODEC_CAP_INIT_CLEANUP |
|
||||||
FF_CODEC_CAP_ICC_PROFILES,
|
FF_CODEC_CAP_ICC_PROFILES,
|
||||||
.p.pix_fmts = (const enum AVPixelFormat[]) {
|
.p.pix_fmts = libjxl_supported_pixfmts,
|
||||||
AV_PIX_FMT_RGB24, AV_PIX_FMT_RGBA,
|
.p.priv_class = &libjxl_encode_class,
|
||||||
AV_PIX_FMT_RGB48, AV_PIX_FMT_RGBA64,
|
.p.wrapper_name = "libjxl",
|
||||||
AV_PIX_FMT_RGBF32, AV_PIX_FMT_RGBAF32,
|
};
|
||||||
AV_PIX_FMT_GRAY8, AV_PIX_FMT_YA8,
|
|
||||||
AV_PIX_FMT_GRAY16, AV_PIX_FMT_YA16,
|
const FFCodec ff_libjxl_anim_encoder = {
|
||||||
AV_PIX_FMT_GRAYF32,
|
.p.name = "libjxl_anim",
|
||||||
AV_PIX_FMT_NONE
|
CODEC_LONG_NAME("libjxl JPEG XL animated"),
|
||||||
},
|
.p.type = AVMEDIA_TYPE_VIDEO,
|
||||||
|
.p.id = AV_CODEC_ID_JPEGXL_ANIM,
|
||||||
|
.priv_data_size = sizeof(LibJxlEncodeContext),
|
||||||
|
.init = libjxl_anim_encode_init,
|
||||||
|
FF_CODEC_RECEIVE_PACKET_CB(libjxl_anim_encode_frame),
|
||||||
|
.close = libjxl_encode_close,
|
||||||
|
.p.capabilities = AV_CODEC_CAP_OTHER_THREADS |
|
||||||
|
AV_CODEC_CAP_DR1 |
|
||||||
|
AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE,
|
||||||
|
.caps_internal = FF_CODEC_CAP_NOT_INIT_THREADSAFE |
|
||||||
|
FF_CODEC_CAP_AUTO_THREADS | FF_CODEC_CAP_INIT_CLEANUP |
|
||||||
|
FF_CODEC_CAP_ICC_PROFILES,
|
||||||
|
.p.pix_fmts = libjxl_supported_pixfmts,
|
||||||
.p.priv_class = &libjxl_encode_class,
|
.p.priv_class = &libjxl_encode_class,
|
||||||
.p.wrapper_name = "libjxl",
|
.p.wrapper_name = "libjxl",
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
#include "version_major.h"
|
#include "version_major.h"
|
||||||
|
|
||||||
#define LIBAVCODEC_VERSION_MINOR 28
|
#define LIBAVCODEC_VERSION_MINOR 29
|
||||||
#define LIBAVCODEC_VERSION_MICRO 100
|
#define LIBAVCODEC_VERSION_MICRO 100
|
||||||
|
|
||||||
#define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
|
#define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
|
||||||
|
|
Loading…
Add table
Reference in a new issue