/* GStreamer SVT JPEG XS decoder
 * Copyright (C) 2024 Tim-Philipp Müller <tim centricular com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

/**
 * SECTION:element-svtjpegxsdec
 *
 * The svtjpegxsdec element does JPEG XS decoding using the Scalable
 * Video Technology for JPEG XS Decoder (SVT JPEG XS Decoder) library.
 *
 * See https://jpeg.org/jpegxs/ for more information about the JPEG XS format.
 *
 * <refsect2>
 * <title>Example launch line</title>
 * |[
 * gst-launch-1.0 -e filesrc location=jxs.ts ! tsdemux ! svtjpegxsdec ! videoconvertscale ! autovideosink
 * ]|
 * Decodes an JPEG-XS video from an MPEG-TS container.
 * </refsect2>
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "gstsvtjpegxsdec.h"

GST_DEBUG_CATEGORY_STATIC (svtjpegxsdec_debug);
#define GST_CAT_DEFAULT svtjpegxsdec_debug

#define GST_JPEG_XS_DEC_FLOW_DROP_FRAME (GST_FLOW_CUSTOM_ERROR - 1000)

static const uint8_t BLOCKING = 1;

typedef struct _GstSvtJpegXsDec
{
  GstVideoDecoder video_decoder;

  // SVT JPEG XS decoder handle
  svt_jpeg_xs_decoder_api_t *jxs_decoder;

  // Valid if decoder has been initialised (i.e. jxs_decoder is set)
  svt_jpeg_xs_image_config_t img_config;

  uint32_t bytes_per_frame;

  // Video decoder base class codec state
  GstVideoCodecState *input_state;
  GstVideoCodecState *output_state;

  // Properties
  int threads;
} GstSvtJpegXsDec;

static void gst_svt_jpeg_xs_dec_set_property (GObject * object,
    guint property_id, const GValue * value, GParamSpec * pspec);
static void gst_svt_jpeg_xs_dec_get_property (GObject * object,
    guint property_id, GValue * value, GParamSpec * pspec);
static void gst_svt_jpeg_xs_dec_finalize (GObject * object);

static gboolean gst_svt_jpeg_xs_dec_start (GstVideoDecoder * decoder);
static gboolean gst_svt_jpeg_xs_dec_stop (GstVideoDecoder * decoder);
static gboolean gst_svt_jpeg_xs_dec_set_format (GstVideoDecoder * decoder,
    GstVideoCodecState * state);
static GstFlowReturn gst_svt_jpeg_xs_dec_handle_frame (GstVideoDecoder *
    decoder, GstVideoCodecFrame * frame);

enum
{
  PROP_THREADS = 1,
};

#define DEFAULT_THREADS 0

#define FORMATS_8_BIT "Y444, Y42B, I420"

#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define FORMATS_10_BIT "Y444_10LE, I422_10LE, I420_10LE"
#define FORMATS_12_BIT "Y444_12LE, I422_12LE, I420_12LE"
#define VIDEO_FORMAT_I420_10 GST_VIDEO_FORMAT_I420_10LE
#define VIDEO_FORMAT_I420_12 GST_VIDEO_FORMAT_I420_12LE
#define VIDEO_FORMAT_I422_10 GST_VIDEO_FORMAT_I422_10LE
#define VIDEO_FORMAT_I422_12 GST_VIDEO_FORMAT_I422_12LE
#define VIDEO_FORMAT_Y444_10 GST_VIDEO_FORMAT_Y444_10LE
#define VIDEO_FORMAT_Y444_12 GST_VIDEO_FORMAT_Y444_12LE
#else
#define FORMATS_10_BIT "Y444_10BE, I422_10BE, I420_10BE"
#define FORMATS_12_BIT "Y444_12BE, I422_12BE, I420_12BE"
#define VIDEO_FORMAT_I420_10 GST_VIDEO_FORMAT_I420_10BE
#define VIDEO_FORMAT_I420_12 GST_VIDEO_FORMAT_I420_12BE
#define VIDEO_FORMAT_I422_10 GST_VIDEO_FORMAT_I422_10BE
#define VIDEO_FORMAT_I422_12 GST_VIDEO_FORMAT_I422_12BE
#define VIDEO_FORMAT_Y444_10 GST_VIDEO_FORMAT_Y444_10BE
#define VIDEO_FORMAT_Y444_12 GST_VIDEO_FORMAT_Y444_12BE
#endif

#define SUPPORTED_FORMATS FORMATS_8_BIT ", " FORMATS_10_BIT ", " FORMATS_12_BIT

static GstStaticPadTemplate sink_pad_template =
GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("image/x-jxsc, alignment = frame, "
        "interlace-mode = { progressive, fields }, "
        "sampling = { YCbCr-4:4:4, YCbCr-4:2:2, YCbCr-4:2:0 }, "
        "depth = { 8, 10, 12 }"));

// FIXME: add 4:2:2 and 4:4:4 packed formats
static GstStaticPadTemplate src_pad_template =
GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/x-raw, "    //
        "format = (string) { " SUPPORTED_FORMATS " }, "
        "interlace-mode = { progressive, interleaved }, "
        "width = (int) [16, 16384], height = (int) [16, 16384], "
        "framerate = (fraction) [0, MAX]"));

#define gst_svt_jpeg_xs_dec_parent_class parent_class

G_DEFINE_TYPE_WITH_CODE (GstSvtJpegXsDec, gst_svt_jpeg_xs_dec,
    GST_TYPE_VIDEO_DECODER, GST_DEBUG_CATEGORY_INIT (svtjpegxsdec_debug,
        "svtjpegxsdec", 0, "SVT JPEG XS decoder element"));

GST_ELEMENT_REGISTER_DEFINE (svtjpegxsdec, "svtjpegxsdec", GST_RANK_SECONDARY,
    gst_svt_jpeg_xs_dec_get_type ());

static void
gst_svt_jpeg_xs_dec_class_init (GstSvtJpegXsDecClass * klass)
{
  GstVideoDecoderClass *video_decoder_class = GST_VIDEO_DECODER_CLASS (klass);
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gst_element_class_add_static_pad_template (element_class, &src_pad_template);

  gst_element_class_add_static_pad_template (element_class, &sink_pad_template);

  gst_element_class_set_static_metadata (element_class,
      "SVT JPEG XS decoder",
      "Codec/Decoder/Video",
      "Scalable Video Technology for JPEG XS Decoder",
      "Tim-Philipp Müller <tim centricular com>");

  gobject_class->set_property = gst_svt_jpeg_xs_dec_set_property;
  gobject_class->get_property = gst_svt_jpeg_xs_dec_get_property;
  gobject_class->finalize = gst_svt_jpeg_xs_dec_finalize;

  video_decoder_class->start = gst_svt_jpeg_xs_dec_start;
  video_decoder_class->stop = gst_svt_jpeg_xs_dec_stop;
  video_decoder_class->set_format = gst_svt_jpeg_xs_dec_set_format;
  video_decoder_class->handle_frame = gst_svt_jpeg_xs_dec_handle_frame;

  g_object_class_install_property (gobject_class,
      PROP_THREADS,
      g_param_spec_int ("threads",
          "Threads",
          "Number of threads to use (0 = automatic)",
          0,
          G_MAXINT,
          DEFAULT_THREADS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

}

static void
gst_svt_jpeg_xs_dec_finalize (GObject * object)
{
  // Nothing to do here yet

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gst_svt_jpeg_xs_dec_init (GstSvtJpegXsDec * jxsdec)
{
  // For clarity, technically not needed
  jxsdec->jxs_decoder = NULL;
  jxsdec->input_state = NULL;
  jxsdec->output_state = NULL;
  jxsdec->bytes_per_frame = 0;

  // Property defaults
  jxsdec->threads = DEFAULT_THREADS;

  // Accept caps strategy
  GstVideoDecoder *vdec = GST_VIDEO_DECODER_CAST (jxsdec);
  gst_video_decoder_set_use_default_pad_acceptcaps (vdec, TRUE);
  GST_PAD_SET_ACCEPT_TEMPLATE (GST_VIDEO_DECODER_SINK_PAD (vdec));
}

static gboolean
gst_svt_jpeg_xs_dec_start (GstVideoDecoder * decoder)
{
  // Nothing to do here yet
  return TRUE;
}

static gboolean
gst_svt_jpeg_xs_dec_stop (GstVideoDecoder * decoder)
{
  GstSvtJpegXsDec *jxsdec = GST_SVT_JPEG_XS_DEC (decoder);

  GST_DEBUG_OBJECT (jxsdec, "Stopping");

  if (jxsdec->input_state) {
    gst_video_codec_state_unref (jxsdec->input_state);
    jxsdec->input_state = NULL;
  }

  if (jxsdec->output_state) {
    gst_video_codec_state_unref (jxsdec->output_state);
    jxsdec->output_state = NULL;
  }

  if (jxsdec->jxs_decoder != NULL) {
    svt_jpeg_xs_decoder_close (jxsdec->jxs_decoder);
    g_free (jxsdec->jxs_decoder);
    jxsdec->jxs_decoder = NULL;
  }
  jxsdec->bytes_per_frame = 0;

  return TRUE;
}

static gboolean
gst_svt_jpeg_xs_dec_set_format (GstVideoDecoder * decoder,
    GstVideoCodecState * state)
{
  GstSvtJpegXsDec *jxsdec = GST_SVT_JPEG_XS_DEC (decoder);

  if (jxsdec->input_state) {
    gst_video_codec_state_unref (jxsdec->input_state);

    // Throw away existing decoder, so it's re-created later based on the
    // new input format, which may or may not have changed (hard to tell).
    gst_svt_jpeg_xs_dec_stop (decoder);
    gst_svt_jpeg_xs_dec_start (decoder);
  }

  jxsdec->input_state = gst_video_codec_state_ref (state);

  // In future we could set the output format right away if we have enough
  // info in the caps, but the decoder needs an actual frame / header
  // to initialise itself, so we will have to delay all of this into
  // the handle_frame function.
  GST_DEBUG_OBJECT (decoder, "New input caps: %" GST_PTR_FORMAT, state->caps);

  return TRUE;
}

static const gchar *
format_to_format_name (ColourFormat_t fmt)
{
  switch (fmt) {
    case COLOUR_FORMAT_INVALID:
      return "invalid";
    case COLOUR_FORMAT_PLANAR_YUV400:
      return "YUV400";
    case COLOUR_FORMAT_PLANAR_YUV420:
      return "YUV420";
    case COLOUR_FORMAT_PLANAR_YUV422:
      return "YUV422";
    case COLOUR_FORMAT_PLANAR_YUV444_OR_RGB:
      return "YUV444";
    case COLOUR_FORMAT_PLANAR_4_COMPONENTS:
      return "Planar4c";
    case COLOUR_FORMAT_GRAY:
      return "GRAY";
    case COLOUR_FORMAT_PACKED_YUV444_OR_RGB:
      return "PACKED_YUV444_OR_RGB";
    case COLOUR_FORMAT_PLANAR_MAX:
    case COLOUR_FORMAT_PACKED_MIN:
    case COLOUR_FORMAT_PACKED_MAX:
    default:
      break;
  }
  return "unknown";
}

static const uint32_t FAST_SEARCH = 1;
static const uint32_t FRAME_BASED = 0;

static GstFlowReturn
gst_svt_jpeg_xs_dec_init_decoder (GstSvtJpegXsDec * jxsdec, guint n_fields,
    const guint8 * data, gsize size)
{
  svt_jpeg_xs_image_config_t img_config;
  SvtJxsErrorType_t dec_ret;
  uint32_t expected_frame_size = 0;

  dec_ret = svt_jpeg_xs_decoder_get_single_frame_size (data, size,
      &img_config, &expected_frame_size, FAST_SEARCH);

  if (dec_ret != SvtJxsErrorNone) {
    GST_ELEMENT_ERROR (jxsdec, STREAM, DECODE, (NULL),
        ("Couldn't probe input frame headers, error code: 0x%08x", dec_ret));
    return GST_FLOW_ERROR;
  }
  // We expect complete frames as input (or two codestreams, one per field)
  if (size < expected_frame_size) {
    GST_ELEMENT_ERROR (jxsdec, STREAM, DECODE, (NULL),
        ("Input frame size smaller than expected size, %zu < %u",
            size, expected_frame_size));
    return GST_FLOW_ERROR;
  }
  // Ensure decoder API struct
  jxsdec->jxs_decoder = g_new0 (svt_jpeg_xs_decoder_api_t, 1);

  // Decode parameters from properties
  {
    GST_OBJECT_LOCK (jxsdec);
    jxsdec->jxs_decoder->threads_num = jxsdec->threads;
    GST_OBJECT_UNLOCK (jxsdec);
  }

  // Hardcoded decode parameters
  {
    jxsdec->jxs_decoder->use_cpu_flags = CPU_FLAGS_ALL;

    // Codestream packetization mode (i.e. buffer = entire JPEG XS picture segment)
    jxsdec->jxs_decoder->packetization_mode = FRAME_BASED;

    // Would be better if there was a callback for the messages from the library.
    // Not sure how to prevent the SvtMalloc spam.
    GstDebugLevel level = gst_debug_category_get_threshold (svtjpegxsdec_debug);
    if (level < GST_LEVEL_WARNING) {
      jxsdec->jxs_decoder->verbose = VERBOSE_ERRORS;
    } else if (level == GST_LEVEL_WARNING) {
      jxsdec->jxs_decoder->verbose = VERBOSE_WARNINGS;
    } else {
      jxsdec->jxs_decoder->verbose = VERBOSE_SYSTEM_INFO;
    }
  }

  dec_ret = svt_jpeg_xs_decoder_init (SVT_JPEGXS_API_VER_MAJOR,
      SVT_JPEGXS_API_VER_MINOR, jxsdec->jxs_decoder, data, size,
      &jxsdec->img_config);

  if (dec_ret != SvtJxsErrorNone) {
    g_free (jxsdec->jxs_decoder);
    jxsdec->jxs_decoder = NULL;
    GST_ELEMENT_ERROR (jxsdec, STREAM, DECODE, (NULL),
        ("Decoder failed to initialise, error code: 0x%08x", dec_ret));
    return GST_FLOW_ERROR;
  }

  svt_jpeg_xs_image_config_t *cfg = &jxsdec->img_config;

  GST_INFO_OBJECT (jxsdec, "Output image configuration (%u fields):", n_fields);
  GST_INFO_OBJECT (jxsdec, "  width: %u", cfg->width);
  GST_INFO_OBJECT (jxsdec, "  height: %u", cfg->height * n_fields);
  GST_INFO_OBJECT (jxsdec, "  depth: %u", cfg->bit_depth);
  GST_INFO_OBJECT (jxsdec, "  format: %s", format_to_format_name (cfg->format));
  GST_INFO_OBJECT (jxsdec, "  components: %u", cfg->components_num);
  for (int i = 0; i < cfg->components_num; ++i) {
    GST_INFO_OBJECT (jxsdec, "  comp width: %u", cfg->components[i].width);
    GST_INFO_OBJECT (jxsdec, "  comp height: %u", cfg->components[i].height);
    GST_INFO_OBJECT (jxsdec, "  comp bsize: %u", cfg->components[i].byte_size);
  }

  // Really shouldn't happen, since we specify allowed depth in our sink template
  if (cfg->bit_depth != 8 && cfg->bit_depth != 10 && cfg->bit_depth != 12) {
    GST_ELEMENT_ERROR (jxsdec, STREAM, FORMAT, (NULL),
        ("Image has bit depth of %u, but only a depth of 8, 10 or 12 is supported.",
            cfg->bit_depth));
    return GST_FLOW_NOT_NEGOTIATED;
  }

  GstVideoFormat fmt = GST_VIDEO_FORMAT_UNKNOWN;

  switch (cfg->format) {
    case COLOUR_FORMAT_PLANAR_YUV420:
      if (cfg->bit_depth == 8) {
        fmt = GST_VIDEO_FORMAT_I420;
      } else if (cfg->bit_depth == 10) {
        fmt = VIDEO_FORMAT_I420_10;
      } else if (cfg->bit_depth == 12) {
        fmt = VIDEO_FORMAT_I420_12;
      }
      break;
    case COLOUR_FORMAT_PLANAR_YUV422:
      if (cfg->bit_depth == 8) {
        fmt = GST_VIDEO_FORMAT_Y42B;
      } else if (cfg->bit_depth == 10) {
        fmt = VIDEO_FORMAT_I422_10;
      } else if (cfg->bit_depth == 12) {
        fmt = VIDEO_FORMAT_I422_12;
      }
      break;
      // We rely on external signalling (caps) to know what's what
    case COLOUR_FORMAT_PLANAR_YUV444_OR_RGB:
      if (cfg->bit_depth == 8) {
        fmt = GST_VIDEO_FORMAT_Y444;
      } else if (cfg->bit_depth == 10) {
        fmt = VIDEO_FORMAT_Y444_10;
      } else if (cfg->bit_depth == 12) {
        fmt = VIDEO_FORMAT_Y444_12;
      }
      break;
      // We rely on external signalling (caps) to know what's what
      // case COLOUR_FORMAT_PACKED_YUV444_OR_RGB:
      // FIXME:  fmt = GST_VIDEO_FORMAT_v308;
      //  break;
    default:
      break;
  }

  if (fmt == GST_VIDEO_FORMAT_UNKNOWN) {
    // Really shouldn't happen, since we specify allowed samplings in our
    // sink template, although outputting packed or planar is a decoder
    // choice I suppose.
    GST_ELEMENT_ERROR (jxsdec, STREAM, FORMAT, (NULL),
        ("Unsupported pixel format %s.", format_to_format_name (cfg->format)));
    return GST_FLOW_NOT_NEGOTIATED;
  }
  // Configure output format
  if (jxsdec->output_state)
    gst_video_codec_state_unref (jxsdec->output_state);

  jxsdec->output_state =
      gst_video_decoder_set_output_state (GST_VIDEO_DECODER (jxsdec), fmt,
      cfg->width, cfg->height * n_fields, jxsdec->input_state);

  // The input caps have interlace-mode=fields since that seems the best
  // mapping, but we will output interlace-mode=interleaved.
  if (n_fields == 2) {
    GST_VIDEO_INFO_INTERLACE_MODE (&jxsdec->output_state->info) =
        GST_VIDEO_INTERLACE_MODE_INTERLEAVED;
  }

  return GST_FLOW_OK;
}

// The codestream data is either a full progressive image or a single field.
static GstFlowReturn
gst_svt_jpeg_xs_dec_decode_codestream (GstSvtJpegXsDec * jxsdec,
    guint field, guint n_fields, GstVideoFrame * video_frame,
    const guint8 * data, gsize data_size)
{
  // Decoder input/output frame struct
  svt_jpeg_xs_frame_t decoder_frame;

  // Set up decoder input buffer struct
  {
    svt_jpeg_xs_bitstream_buffer_t in_buf;

    if (data_size > G_MAXUINT32)
      return GST_FLOW_ERROR;

    in_buf.buffer = (uint8_t *) data;
    in_buf.allocation_size = data_size;
    in_buf.used_size = data_size;

    decoder_frame.bitstream = in_buf;
  }

  // Set up decoder output image struct
  {
    svt_jpeg_xs_image_buffer_t img = { {0,}
    };

    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] = n_fields * GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 0)
        / GST_VIDEO_FRAME_COMP_PSTRIDE (video_frame, 0);
    img.stride[1] = n_fields * GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 1)
        / GST_VIDEO_FRAME_COMP_PSTRIDE (video_frame, 1);
    img.stride[2] = n_fields * GST_VIDEO_FRAME_COMP_STRIDE (video_frame, 2)
        / GST_VIDEO_FRAME_COMP_PSTRIDE (video_frame, 2);

    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 (jxsdec, "img stride[%u] = %u, alloc_size[%u]: %u",
          i, img.stride[i], i, img.alloc_size[i]);
    }

    decoder_frame.image = img;
  }

  decoder_frame.user_prv_ctx_ptr = NULL;

  GST_TRACE_OBJECT (jxsdec, "Sending frame or field to decoder ..");

  SvtJxsErrorType_t dec_ret;

  // Decode!
  {
    dec_ret =
        svt_jpeg_xs_decoder_send_frame (jxsdec->jxs_decoder, &decoder_frame,
        BLOCKING);

    if (dec_ret != SvtJxsErrorNone)
      goto send_packet_error;
  }

  // Will get it back from the decoder
  memset (&decoder_frame, 0, sizeof (svt_jpeg_xs_frame_t));

  // Wait for decoded frame..
  {
    dec_ret =
        svt_jpeg_xs_decoder_get_frame (jxsdec->jxs_decoder, &decoder_frame,
        BLOCKING);

    if (dec_ret != SvtJxsErrorNone)
      goto get_frame_error;
  }

  GST_TRACE_OBJECT (jxsdec, "Decoding finished OK");
  return GST_FLOW_OK;

send_packet_error:
  {
    GST_ELEMENT_ERROR (jxsdec, STREAM, DECODE, (NULL),
        ("Error submitting image for decoding: 0x%08x", dec_ret));
    return GST_FLOW_ERROR;
  }

get_frame_error:
  {
    GstFlowReturn flow = GST_FLOW_ERROR;

    GST_TRACE_OBJECT (jxsdec, "Decoding error 0x%08x", dec_ret);

    GST_VIDEO_DECODER_ERROR (jxsdec, 1, STREAM, DECODE,
        (NULL), ("Error decoding image, error code 0x%08x", dec_ret), flow);

    if (flow == GST_FLOW_OK)
      flow = GST_JPEG_XS_DEC_FLOW_DROP_FRAME;

    return flow;
  }
}

static GstFlowReturn
gst_svt_jpeg_xs_dec_handle_frame (GstVideoDecoder * vdecoder,
    GstVideoCodecFrame * frame)
{
  GstSvtJpegXsDec *jxsdec = GST_SVT_JPEG_XS_DEC (vdecoder);
  GstFlowReturn flow = GST_FLOW_OK;
  guint n_fields;

  GST_LOG_OBJECT (jxsdec, "Frame to decode, size: %zu bytes",
      gst_buffer_get_size (frame->input_buffer));

  if (jxsdec->input_state == NULL) {
    GST_WARNING_OBJECT (jxsdec, "No input caps were set?");
    return GST_FLOW_NOT_NEGOTIATED;
  }

  if (GST_VIDEO_INFO_IS_INTERLACED (&jxsdec->input_state->info)) {
    n_fields = 2;
  } else {
    n_fields = 1;
  }

  GstMapInfo in_map = GST_MAP_INFO_INIT;
  GstVideoFrame video_frame = GST_VIDEO_FRAME_INIT;

  // Map input buffer
  if (!gst_buffer_map (frame->input_buffer, &in_map, GST_MAP_READ))
    goto input_buffer_map_failure;

  if (jxsdec->jxs_decoder == NULL) {
    flow =
        gst_svt_jpeg_xs_dec_init_decoder (jxsdec, n_fields, in_map.data,
        in_map.size);

    if (flow != GST_FLOW_OK)
      goto out_unmap;
  }
  // Allocate output frame
  {
    flow = gst_video_decoder_allocate_output_frame (vdecoder, frame);

    if (flow != GST_FLOW_OK)
      goto allocate_output_frame_failure;
  }

  // Map output frame
  if (!gst_video_frame_map (&video_frame, &jxsdec->output_state->info,
          frame->output_buffer, GST_MAP_WRITE))
    goto output_frame_map_error;

  // Decode frame or fields
  gsize offset = 0;

  for (guint field = 0; field < n_fields; ++field) {
    // Figure out where the second codestream/field starts in the data
    // (should be size/2 unless vbr encoding was used, which is unlikely).
    if (field == 1) {
      svt_jpeg_xs_image_config_t img_config;
      SvtJxsErrorType_t parse_ret;
      uint32_t frame_size = 0;

      parse_ret = svt_jpeg_xs_decoder_get_single_frame_size (in_map.data,
          in_map.size, &img_config, &frame_size, FAST_SEARCH);

      // This shouldn't really happen because at this point we have already
      // decoded the first field without errors. In the unlikely case it does
      // happen, let's check the midpoint for the second codestream header and
      // otherwise just decode the first field again into the second field.
      if (parse_ret != SvtJxsErrorNone) {
        if (in_map.size >= 8 &&
            GST_READ_UINT32_BE (in_map.data + in_map.size / 2) == 0xff10ff50) {
          frame_size = in_map.size / 2;
        } else {
          GST_ELEMENT_WARNING (jxsdec, STREAM, DECODE, (NULL),
              ("Unexpected error 0x%08x getting codestream length of field 1 "
                  "to determine offset of codestream for field 2. Will decode "
                  "field 1 again into field 2", parse_ret));
          frame_size = 0;
        }
      }

      offset = frame_size;
    }

    if (n_fields == 2) {
      GST_TRACE_OBJECT (jxsdec,
          "Decoding field %u of 2 @ %zu", field + 1, offset);
    }

    flow = gst_svt_jpeg_xs_dec_decode_codestream (jxsdec, field, n_fields,
        &video_frame, in_map.data + offset, in_map.size - offset);

    if (flow != GST_FLOW_OK)
      break;
  }

  if (n_fields == 2) {
    GST_BUFFER_FLAG_SET (video_frame.buffer, GST_VIDEO_BUFFER_FLAG_INTERLACED);
    if ((video_frame.flags & GST_VIDEO_FRAME_FLAG_TFF)) {
      GST_BUFFER_FLAG_SET (video_frame.buffer, GST_VIDEO_BUFFER_FLAG_TFF);
    } else {
      GST_BUFFER_FLAG_UNSET (video_frame.buffer, GST_VIDEO_BUFFER_FLAG_TFF);
    }
  } else {
    GST_BUFFER_FLAG_UNSET (video_frame.buffer,
        GST_VIDEO_BUFFER_FLAG_INTERLACED);
    GST_BUFFER_FLAG_UNSET (video_frame.buffer, GST_VIDEO_BUFFER_FLAG_TFF);
  }

  // Unmap output frame and input buffer
  {
    gst_video_frame_unmap (&video_frame);
    video_frame.buffer = NULL;

    gst_buffer_unmap (frame->input_buffer, &in_map);
    in_map.memory = NULL;
  }

  // And output! (or drop)
  if (flow == GST_FLOW_OK) {
    flow = gst_video_decoder_finish_frame (vdecoder, frame);
  } else {
    flow = gst_video_decoder_drop_frame (vdecoder, frame);
  }

  frame = NULL;

out_unmap:

  if (video_frame.buffer != NULL)
    gst_video_frame_unmap (&video_frame);

  if (frame != NULL && in_map.memory != NULL)
    gst_buffer_unmap (frame->input_buffer, &in_map);

out:

  if (frame != NULL)
    gst_video_codec_frame_unref (frame);

  return flow;

/* ERRORS */
input_buffer_map_failure:
  {
    GST_ELEMENT_ERROR (jxsdec, STREAM, DECODE, (NULL),
        ("Couldn't map input buffer"));
    flow = GST_FLOW_ERROR;
    goto out;
  }

allocate_output_frame_failure:
  {
    GST_DEBUG_OBJECT (jxsdec, "Couldn't allocate output frame, flow=%s",
        gst_flow_get_name (flow));
    goto out_unmap;
  }

output_frame_map_error:
  {
    GST_ERROR_OBJECT (jxsdec, "Couldn't map output frame!");
    flow = GST_FLOW_ERROR;
    goto out_unmap;
  }
}

static void
gst_svt_jpeg_xs_dec_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  GstSvtJpegXsDec *jxsdec = GST_SVT_JPEG_XS_DEC (object);

  if (jxsdec->jxs_decoder != NULL) {
    GST_ERROR_OBJECT (jxsdec,
        "Decoder has been configured already, can't change properties now.");
    return;
  }

  GST_LOG_OBJECT (jxsdec, "Setting property %s", pspec->name);

  switch (property_id) {
    case PROP_THREADS:
      GST_OBJECT_LOCK (jxsdec);
      jxsdec->threads = g_value_get_int (value);
      GST_OBJECT_UNLOCK (jxsdec);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

static void
gst_svt_jpeg_xs_dec_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  GstSvtJpegXsDec *jxsdec = GST_SVT_JPEG_XS_DEC (object);

  GST_LOG_OBJECT (jxsdec, "Getting property %s", pspec->name);

  switch (property_id) {
    case PROP_THREADS:
      GST_OBJECT_LOCK (jxsdec);
      g_value_set_int (value, jxsdec->threads);
      GST_OBJECT_UNLOCK (jxsdec);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}
