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. Co-authored-by: Zsolt Vadász <zsolt_vad...@protonmail.com> Signed-off-by: Leo Izen <leo.i...@gmail.com> --- configure | 2 + libavcodec/allcodecs.c | 2 + libavcodec/codec_desc.c | 8 + libavcodec/codec_id.h | 1 + libavcodec/jpegxl_parser.c | 2 +- libavcodec/libjxldec.c | 16 ++ libavcodec/libjxlenc.c | 392 ++++++++++++++++++++++++++++--------- 7 files changed, 330 insertions(+), 93 deletions(-) diff --git a/configure b/configure index bf55ba67fa..44297285f5 100755 --- a/configure +++ b/configure @@ -3558,6 +3558,8 @@ libgsm_ms_decoder_deps="libgsm" libgsm_ms_encoder_deps="libgsm" libilbc_decoder_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_encoder_deps="libjxl libjxl_threads" libkvazaar_encoder_deps="libkvazaar" diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index 0b559dfc58..4bc41239c7 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -777,6 +777,8 @@ extern const FFCodec ff_libgsm_ms_encoder; extern const FFCodec ff_libgsm_ms_decoder; extern const FFCodec ff_libilbc_encoder; 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_encoder; extern const FFCodec ff_liblc3_encoder; diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c index bc9163bf98..fc075ffd7f 100644 --- a/libavcodec/codec_desc.c +++ b/libavcodec/codec_desc.c @@ -1977,6 +1977,14 @@ static const AVCodecDescriptor codec_descriptors[] = { .long_name = NULL_IF_CONFIG_SMALL("RealVideo 6.0"), .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" */ { diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h index 6bfaa02601..ba0480aa09 100644 --- a/libavcodec/codec_id.h +++ b/libavcodec/codec_id.h @@ -328,6 +328,7 @@ enum AVCodecID { AV_CODEC_ID_LEAD, AV_CODEC_ID_DNXUC, AV_CODEC_ID_RV60, + AV_CODEC_ID_JPEGXL_ANIM, /* various PCM "codecs" */ AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs diff --git a/libavcodec/jpegxl_parser.c b/libavcodec/jpegxl_parser.c index 76122af54a..a888e9ae6e 100644 --- a/libavcodec/jpegxl_parser.c +++ b/libavcodec/jpegxl_parser.c @@ -1534,7 +1534,7 @@ flush: } 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), .parser_parse = jpegxl_parse, .parser_close = ff_parse_close, diff --git a/libavcodec/libjxldec.c b/libavcodec/libjxldec.c index 9dfc261e3d..96c338d1b4 100644 --- a/libavcodec/libjxldec.c +++ b/libavcodec/libjxldec.c @@ -549,3 +549,19 @@ const FFCodec ff_libjxl_decoder = { FF_CODEC_CAP_ICC_PROFILES, .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", +}; diff --git a/libavcodec/libjxlenc.c b/libavcodec/libjxlenc.c index 8b0e60df6f..7e1abe385d 100644 --- a/libavcodec/libjxlenc.c +++ b/libavcodec/libjxlenc.c @@ -56,6 +56,12 @@ typedef struct LibJxlEncodeContext { int xyb; uint8_t *buffer; size_t buffer_size; + JxlPixelFormat jxl_fmt; + + /* animation stuff */ + AVFrame *frame; + AVFrame *prev; + int64_t duration; } 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 - * once each time the encoder is reset, which it must be each frame to make - * the image2 muxer work. + * Initalize the encoder on a per-file basis. All of these need to be set + * once each time the encoder is reset, which is each frame for still + * images, to make the image2 muxer work. For animation this is run once. * * @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 */ 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 */ if (JxlEncoderSetParallelRunner(ctx->encoder, JxlThreadParallelRunner, ctx->runner) != JXL_ENC_SUCCESS) { @@ -113,42 +113,9 @@ static int libjxl_init_jxl_encoder(AVCodecContext *avctx) return AVERROR_EXTERNAL; } - /* 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; - } - - /* 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"); + ctx->options = JxlEncoderFrameSettingsCreate(ctx->encoder, NULL); + if (!ctx->options) { + av_log(avctx, AV_LOG_ERROR, "Failed to create JxlEncoderOptions\n"); return AVERROR_EXTERNAL; } @@ -185,6 +152,47 @@ static av_cold int libjxl_encode_init(AVCodecContext *avctx) 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; } @@ -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 - * this encoder, so this will always reinitialize a new still image - * and encode a one-frame image (for image2 and image2pipe). + * Sends metadata to libjxl based on the first frame of the stream, such as pixel format, + * orientation, bit depth, and that sort of thing. */ -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; + int ret; AVFrameSideData *sd; const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(frame->format); JxlBasicInfo info; JxlColorEncoding jxl_color; - JxlPixelFormat jxl_fmt; + JxlPixelFormat *jxl_fmt = &ctx->jxl_fmt; int bits_per_sample; #if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 8, 0) JxlBitDepth jxl_bit_depth; #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 */ JxlEncoderInitBasicInfo(&info); - jxl_fmt.num_channels = pix_desc->nb_components; + jxl_fmt->num_channels = pix_desc->nb_components; info.xsize = frame->width; info.ysize = frame->height; - info.num_extra_channels = (jxl_fmt.num_channels + 1) % 2; - 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; + info.num_extra_channels = (jxl_fmt->num_channels + 1) & 0x1; + 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; 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; info.alpha_bits = (info.num_extra_channels > 0) * info.bits_per_sample; if (pix_desc->flags & AV_PIX_FMT_FLAG_FLOAT) { 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; - 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 { info.exponent_bits_per_sample = 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) @@ -306,8 +302,19 @@ static int libjxl_encode_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFra /* bitexact lossless requires there to be no XYB transform */ 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; + 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) { av_log(avctx, AV_LOG_ERROR, "Failed to set JxlBasicInfo\n"); 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"); } - 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] * (info.ysize - 1); + return 0; +} + +/** + * Sends frame information to libjxl on a per-frame basis. If this is a still image, + * this is evaluated once per output file. If this is an animated JPEG XL encode, it + * 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) { - av_log(avctx, AV_LOG_ERROR, "Failed to add Image Frame\n"); + 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; } /* - * Run this after the last frame in the image has been passed. - * TODO support animation + * 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. */ - 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) { 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"); return AVERROR_EXTERNAL; } - bytes_written = ctx->buffer_size - available; + *bytes_written = ctx->buffer_size - available; /* all data passed has been encoded */ if (jret == JXL_ENC_SUCCESS) break; @@ -429,14 +481,58 @@ static int libjxl_encode_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFra return AVERROR(ENOMEM); ctx->buffer = temp; ctx->buffer_size = new_size; - next_out = ctx->buffer + bytes_written; - available = new_size - bytes_written; + next_out = ctx->buffer + *bytes_written; + available = new_size - *bytes_written; continue; } av_log(avctx, AV_LOG_ERROR, "Bad libjxl event: %d\n", jret); 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); if (ret < 0) return ret; @@ -447,6 +543,94 @@ static int libjxl_encode_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFra 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) { LibJxlEncodeContext *ctx = avctx->priv_data; @@ -464,6 +648,8 @@ static av_cold int libjxl_encode_close(AVCodecContext *avctx) ctx->encoder = NULL; av_freep(&ctx->buffer); + av_frame_free(&ctx->prev); + av_frame_free(&ctx->frame); return 0; } @@ -488,6 +674,16 @@ static const AVClass libjxl_encode_class = { .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 = { .p.name = "libjxl", CODEC_LONG_NAME("libjxl JPEG XL"), @@ -503,15 +699,27 @@ const FFCodec ff_libjxl_encoder = { .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 = (const enum AVPixelFormat[]) { - 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 - }, + .p.pix_fmts = libjxl_supported_pixfmts, + .p.priv_class = &libjxl_encode_class, + .p.wrapper_name = "libjxl", +}; + +const FFCodec ff_libjxl_anim_encoder = { + .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(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.wrapper_name = "libjxl", }; -- 2.47.1 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".