From 0cb829c589ce4f43b4789dccc309ccc045da894c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 27 Dec 2024 20:55:56 +0100 Subject: [PATCH] svtjpegxsenc: add support for interlaced video Part-of: --- .../ext/svtjpegxs/gstsvtjpegxsenc.c | 118 +++++++++++++----- 1 file changed, 87 insertions(+), 31 deletions(-) diff --git a/subprojects/gst-plugins-bad/ext/svtjpegxs/gstsvtjpegxsenc.c b/subprojects/gst-plugins-bad/ext/svtjpegxs/gstsvtjpegxsenc.c index 7b7850b3db..8453d3b244 100644 --- a/subprojects/gst-plugins-bad/ext/svtjpegxs/gstsvtjpegxsenc.c +++ b/subprojects/gst-plugins-bad/ext/svtjpegxs/gstsvtjpegxsenc.c @@ -165,10 +165,17 @@ enum // FIXME: add 4:2:2 and 4:4:4 packed formats // Only handle progressive mode for now static GstStaticPadTemplate sink_pad_template = -GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/x-raw, " "format = { " SUPPORTED_FORMATS " }," "interlace-mode = progressive, " + "width = (int) [16, 16384], " + "height = (int) [16, 16384], " + "framerate = (fraction) [0, MAX]; " + "video/x-raw, " + "format = { " SUPPORTED_FORMATS " }," + "interlace-mode = interleaved, " + "field-order = { top-field-first, bottom-field-first }, " "width = (int) [16, 16384], " "height = (int) [16, 16384], " "framerate = (fraction) [0, MAX]")); @@ -176,6 +183,7 @@ static GstStaticPadTemplate src_pad_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("image/x-jxsc, alignment = frame, " "width = (int) [16, 16384], height = (int) [16, 16384], " + "interlace-mode = { progressive, fields }, " "sampling = { YCbCr-4:4:4, YCbCr-4:2:2, YCbCr-4:2:0 }, " "framerate = (fraction) [0, MAX]")); @@ -437,12 +445,14 @@ gst_svt_jpeg_xs_enc_set_format (GstVideoEncoder * encoder, } } + guint n_fields = !!GST_VIDEO_INFO_IS_INTERLACED (&state->info) + 1; + const char *sampling = NULL; // Fill in video format parameters { enc->source_width = GST_VIDEO_INFO_WIDTH (&state->info); - enc->source_height = GST_VIDEO_INFO_HEIGHT (&state->info); + enc->source_height = GST_VIDEO_INFO_HEIGHT (&state->info) / n_fields; switch (GST_VIDEO_INFO_FORMAT (&state->info)) { case GST_VIDEO_FORMAT_I420: @@ -523,12 +533,12 @@ gst_svt_jpeg_xs_enc_set_format (GstVideoEncoder * encoder, { SvtJxsErrorType_t ret; svt_jpeg_xs_image_config_t img_config; - uint32_t bytes_per_frame = 0; + uint32_t bytes_per_frame_or_field = 0; ret = svt_jpeg_xs_encoder_get_image_config (SVT_JPEGXS_API_VER_MAJOR, - SVT_JPEGXS_API_VER_MINOR, enc, &img_config, &bytes_per_frame); + SVT_JPEGXS_API_VER_MINOR, enc, &img_config, &bytes_per_frame_or_field); - if (ret != SvtJxsErrorNone || bytes_per_frame == 0) { + if (ret != SvtJxsErrorNone || bytes_per_frame_or_field == 0) { GST_ELEMENT_ERROR (encoder, LIBRARY, INIT, (NULL), @@ -536,8 +546,15 @@ gst_svt_jpeg_xs_enc_set_format (GstVideoEncoder * encoder, return FALSE; } - GST_DEBUG_OBJECT (jxsenc, "Encoded frame size: %u bytes", bytes_per_frame); - jxsenc->bytes_per_frame = bytes_per_frame; + if (n_fields == 2) { + GST_DEBUG_OBJECT (jxsenc, "Encoded field size: %u bytes", + bytes_per_frame_or_field); + } + + GST_DEBUG_OBJECT (jxsenc, "Encoded frame size: %u bytes", + bytes_per_frame_or_field * n_fields); + + jxsenc->bytes_per_frame = bytes_per_frame_or_field * n_fields; } GstCaps *src_caps = gst_static_pad_template_get_caps (&src_pad_template); @@ -555,6 +572,12 @@ gst_svt_jpeg_xs_enc_set_format (GstVideoEncoder * encoder, gst_video_encoder_set_output_state (GST_VIDEO_ENCODER (encoder), src_caps, jxsenc->state); + if (n_fields == 2) { + // input will be interleaved, but we output interlace-mode=fields + GST_VIDEO_INFO_INTERLACE_MODE (&output_state->info) = + GST_VIDEO_INTERLACE_MODE_FIELDS; + } + if (!gst_video_encoder_negotiate (encoder)) { gst_video_codec_state_unref (output_state); return FALSE; @@ -568,7 +591,8 @@ gst_svt_jpeg_xs_enc_set_format (GstVideoEncoder * encoder, // The codestream data is either a full progressive image or a single field. static GstFlowReturn -gst_svt_jpeg_xs_enc_encode_codestream (GstSvtJpegXsEnc * jxsenc, GstVideoFrame * video_frame, // +gst_svt_jpeg_xs_enc_encode_codestream (GstSvtJpegXsEnc * jxsenc, + guint field, guint n_fields, GstVideoFrame * video_frame, svt_jpeg_xs_bitstream_buffer_t * bitstream_buffer) { // Encoder input/output frame struct @@ -579,24 +603,34 @@ gst_svt_jpeg_xs_enc_encode_codestream (GstSvtJpegXsEnc * jxsenc, GstVideoFrame * svt_jpeg_xs_image_buffer_t img = { {0,} }; - img.data_yuv[0] = GST_VIDEO_FRAME_PLANE_DATA (video_frame, 0); - img.data_yuv[1] = GST_VIDEO_FRAME_PLANE_DATA (video_frame, 1); - img.data_yuv[2] = GST_VIDEO_FRAME_PLANE_DATA (video_frame, 2); + img.data_yuv[0] = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (video_frame, 0) + + field * GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 0); + img.data_yuv[1] = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (video_frame, 1) + + field * GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 1); + img.data_yuv[2] = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (video_frame, 2) + + field * GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 2); // Note: wants stride in pixels not in bytes (might need tweaks for 10-bit) - img.stride[0] = GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 0) + img.stride[0] = n_fields * GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 0) / GST_VIDEO_FRAME_COMP_PSTRIDE (video_frame, 0); - img.stride[1] = GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 1) + img.stride[1] = n_fields * GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 1) / GST_VIDEO_FRAME_COMP_PSTRIDE (video_frame, 1); - img.stride[2] = GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 2) + img.stride[2] = n_fields * GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 2) / GST_VIDEO_FRAME_COMP_PSTRIDE (video_frame, 2); + // svt-jpegxs returns an error if we specify the size correctly, + // probably because of lazy assumption in some input check. + // See https://github.com/OpenVisualCloud/SVT-JPEG-XS/pull/5 + // Remove once there's a new release with the fix. img.alloc_size[0] = GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 0) * GST_VIDEO_FRAME_COMP_HEIGHT (video_frame, 0); + // - field * GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 0); img.alloc_size[1] = GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 1) * GST_VIDEO_FRAME_COMP_HEIGHT (video_frame, 1); + // - field * GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 1); img.alloc_size[2] = GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 2) * GST_VIDEO_FRAME_COMP_HEIGHT (video_frame, 2); + // - field * GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 2); for (int i = 0; i < 3; ++i) { GST_TRACE_OBJECT (jxsenc, "img stride[%u] = %u, alloc_size[%u]: %u", @@ -636,6 +670,9 @@ gst_svt_jpeg_xs_enc_encode_codestream (GstSvtJpegXsEnc * jxsenc, GstVideoFrame * *bitstream_buffer = encoder_frame.bitstream; + GST_TRACE_OBJECT (jxsenc, "Codestream length: %u (%s)", + encoder_frame.bitstream.used_size, (n_fields == 2) ? "field" : "frame"); + return GST_FLOW_OK; /* Errors */ @@ -687,32 +724,51 @@ gst_svt_jpeg_xs_enc_handle_frame (GstVideoEncoder * vencoder, goto allocate_output_frame_failure; } + guint n_fields = !!GST_VIDEO_FRAME_IS_INTERLACED (&video_frame) + 1; + + // Map output buffer GstMapInfo outbuf_map = GST_MAP_INFO_INIT; - svt_jpeg_xs_bitstream_buffer_t out_buf; - // Set up encoder output buffer struct - { - if (!gst_buffer_map (frame->output_buffer, &outbuf_map, GST_MAP_WRITE)) - goto output_buffer_map_write_failure; + if (!gst_buffer_map (frame->output_buffer, &outbuf_map, GST_MAP_WRITE)) + goto output_buffer_map_write_failure; - out_buf.buffer = outbuf_map.data; - out_buf.allocation_size = outbuf_map.size; + // Encode frame or fields + gsize offset = 0; + + for (guint field = 0; field < n_fields; ++field) { + svt_jpeg_xs_bitstream_buffer_t out_buf; + + if (n_fields == 2) { + GST_TRACE_OBJECT (jxsenc, + "Encoding field %u of 2 @ %zu", field + 1, offset); + } + + // Set up encoder output buffer struct + out_buf.buffer = outbuf_map.data + offset; + out_buf.allocation_size = outbuf_map.size - offset; out_buf.used_size = 0; + + flow = + gst_svt_jpeg_xs_enc_encode_codestream (jxsenc, field, n_fields, + &video_frame, &out_buf); + + if (flow != GST_FLOW_OK) + goto out_unmap; + + offset += out_buf.used_size; } - flow = gst_svt_jpeg_xs_enc_encode_codestream (jxsenc, &video_frame, &out_buf); - - if (flow != GST_FLOW_OK) - goto out_unmap; - - GST_LOG_OBJECT (jxsenc, "Output buffer size: %u, last=%d", - out_buf.used_size, out_buf.last_packet_in_frame); + gst_buffer_unmap (frame->output_buffer, &outbuf_map); // Shouldn't happen, but let's play it safe - if (out_buf.used_size < jxsenc->bytes_per_frame) - gst_buffer_set_size (frame->output_buffer, out_buf.used_size); + if (offset < jxsenc->bytes_per_frame) { + GST_WARNING_OBJECT (jxsenc, "Short encoder output: %zu < %u bytes", + offset, jxsenc->bytes_per_frame); + gst_buffer_set_size (frame->output_buffer, offset); + } - gst_buffer_unmap (frame->output_buffer, &outbuf_map); + GST_LOG_OBJECT (jxsenc, "Output buffer size: %zu bytes, codestreams=%u", + offset, n_fields); // All frames are key frames GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);