mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-11 19:06:33 +00:00
107553843c
According to spec, the JPEG_MARKER_DRI(Restart interval definition) marker can come before the SOS marker. So we should not check the SOS state when parsing the DRI marker. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5053>
522 lines
16 KiB
C
522 lines
16 KiB
C
/* GStreamer
|
|
* Copyright (C) 2022 Víctor Jáquez <vjaquez@igalia.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <gst/base/base.h>
|
|
#include "gstjpegdecoder.h"
|
|
|
|
#define MAX_SAMPLE_FACTOR 4 /* JPEG limit on sampling factors */
|
|
#define DCT_SIZE 8 /* The basic DCT block is 8x8 samples */
|
|
|
|
typedef enum
|
|
{
|
|
GST_JPEG_DECODER_STATE_GOT_SOI = 1 << 0,
|
|
GST_JPEG_DECODER_STATE_GOT_SOF = 1 << 1,
|
|
GST_JPEG_DECODER_STATE_GOT_SOS = 1 << 2,
|
|
GST_JPEG_DECODER_STATE_GOT_HUF_TABLE = 1 << 3,
|
|
GST_JPEG_DECODER_STATE_GOT_IQ_TABLE = 1 << 4,
|
|
|
|
GST_JPEG_DECODER_STATE_VALID_PICTURE = (GST_JPEG_DECODER_STATE_GOT_SOI |
|
|
GST_JPEG_DECODER_STATE_GOT_SOF | GST_JPEG_DECODER_STATE_GOT_SOS),
|
|
} GstJpegDecoderState;
|
|
|
|
struct _GstJpegDecoderPrivate
|
|
{
|
|
guint state;
|
|
guint restart_interval;
|
|
GstJpegHuffmanTables huf_tables;
|
|
GstJpegQuantTables quant_tables;
|
|
GstJpegFrameHdr frame_hdr;
|
|
|
|
guint8 max_h, max_v;
|
|
gboolean lossless;
|
|
};
|
|
|
|
GST_DEBUG_CATEGORY (gst_jpeg_decoder_debug);
|
|
#define GST_CAT_DEFAULT gst_jpeg_decoder_debug
|
|
|
|
/**
|
|
* GstJpegDecoder:
|
|
*
|
|
* Base class to implement statelesss JPEG decoders
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
#define parent_class gst_jpeg_decoder_parent_clas
|
|
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstJpegDecoder, gst_jpeg_decoder,
|
|
GST_TYPE_VIDEO_DECODER, G_ADD_PRIVATE (GstJpegDecoder);
|
|
GST_DEBUG_CATEGORY_INIT (gst_jpeg_decoder_debug, "jpegdecoder", 0,
|
|
"JPEG Image Decoder"));
|
|
|
|
static gboolean gst_jpeg_decoder_set_format (GstVideoDecoder * decoder,
|
|
GstVideoCodecState * state);
|
|
static GstFlowReturn gst_jpeg_decoder_handle_frame (GstVideoDecoder * decoder,
|
|
GstVideoCodecFrame * frame);
|
|
static gboolean gst_jpeg_decoder_stop (GstVideoDecoder * decoder);
|
|
|
|
static void
|
|
gst_jpeg_decoder_class_init (GstJpegDecoderClass * klass)
|
|
{
|
|
GstVideoDecoderClass *decoder_class = GST_VIDEO_DECODER_CLASS (klass);
|
|
|
|
decoder_class->stop = GST_DEBUG_FUNCPTR (gst_jpeg_decoder_stop);
|
|
decoder_class->set_format = GST_DEBUG_FUNCPTR (gst_jpeg_decoder_set_format);
|
|
decoder_class->handle_frame =
|
|
GST_DEBUG_FUNCPTR (gst_jpeg_decoder_handle_frame);
|
|
|
|
gst_type_mark_as_plugin_api (GST_TYPE_JPEG_DECODER, 0);
|
|
}
|
|
|
|
static void
|
|
gst_jpeg_decoder_init (GstJpegDecoder * self)
|
|
{
|
|
gst_video_decoder_set_packetized (GST_VIDEO_DECODER (self), TRUE);
|
|
gst_video_decoder_set_needs_format (GST_VIDEO_DECODER (self), TRUE);
|
|
|
|
self->priv = gst_jpeg_decoder_get_instance_private (self);
|
|
}
|
|
|
|
static gboolean
|
|
gst_jpeg_decoder_stop (GstVideoDecoder * decoder)
|
|
{
|
|
GstJpegDecoder *self = GST_JPEG_DECODER (decoder);
|
|
|
|
g_clear_pointer (&self->input_state, gst_video_codec_state_unref);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_jpeg_decoder_set_format (GstVideoDecoder * decoder,
|
|
GstVideoCodecState * state)
|
|
{
|
|
GstJpegDecoder *self = GST_JPEG_DECODER (decoder);
|
|
|
|
GST_DEBUG_OBJECT (decoder, "Set format");
|
|
|
|
if (self->input_state)
|
|
gst_video_codec_state_unref (self->input_state);
|
|
|
|
self->input_state = gst_video_codec_state_ref (state);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static inline gboolean
|
|
valid_state (guint state, guint ref_state)
|
|
{
|
|
return (state & ref_state) == ref_state;
|
|
}
|
|
|
|
static gboolean
|
|
decode_huffman_table (GstJpegDecoder * self, GstJpegSegment * seg)
|
|
{
|
|
GstJpegDecoderPrivate *priv = self->priv;
|
|
|
|
if (!gst_jpeg_segment_parse_huffman_table (seg, &priv->huf_tables)) {
|
|
GST_ERROR_OBJECT (self, "failed to parse Huffman table");
|
|
return FALSE;
|
|
}
|
|
|
|
priv->state |= GST_JPEG_DECODER_STATE_GOT_HUF_TABLE;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
decode_quant_table (GstJpegDecoder * self, GstJpegSegment * seg)
|
|
{
|
|
GstJpegDecoderPrivate *priv = self->priv;
|
|
|
|
if (!gst_jpeg_segment_parse_quantization_table (seg, &priv->quant_tables)) {
|
|
GST_ERROR_OBJECT (self, "failed to parse quantization table");
|
|
return FALSE;
|
|
}
|
|
|
|
priv->state |= GST_JPEG_DECODER_STATE_GOT_IQ_TABLE;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
decode_restart_interval (GstJpegDecoder * self, GstJpegSegment * seg)
|
|
{
|
|
GstJpegDecoderPrivate *priv = self->priv;
|
|
|
|
if (!gst_jpeg_segment_parse_restart_interval (seg, &priv->restart_interval)) {
|
|
GST_ERROR_OBJECT (self, "failed to parse restart interval");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
decode_frame (GstJpegDecoder * self, GstJpegSegment * seg,
|
|
GstVideoCodecFrame * frame)
|
|
{
|
|
GstJpegDecoderPrivate *priv = self->priv;
|
|
GstJpegDecoderClass *klass = GST_JPEG_DECODER_GET_CLASS (self);
|
|
GstJpegFrameHdr *frame_hdr = &self->priv->frame_hdr;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
guint i;
|
|
|
|
if (!gst_jpeg_segment_parse_frame_header (seg, frame_hdr)) {
|
|
GST_ERROR_OBJECT (self, "failed to parse frame header");
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
/* A.1.1 Dimensions and sampling factors */
|
|
priv->max_h = priv->max_v = 0;
|
|
for (i = 0; i < frame_hdr->num_components; i++) {
|
|
if (frame_hdr->components[i].horizontal_factor >= MAX_SAMPLE_FACTOR
|
|
|| frame_hdr->components[i].vertical_factor >= MAX_SAMPLE_FACTOR) {
|
|
ret = GST_FLOW_ERROR;
|
|
GST_ERROR_OBJECT (self, "frame header with bad sampling factor");
|
|
goto beach;
|
|
}
|
|
|
|
priv->max_h = MAX (priv->max_h, frame_hdr->components[i].horizontal_factor);
|
|
priv->max_v = MAX (priv->max_v, frame_hdr->components[i].vertical_factor);
|
|
}
|
|
|
|
if (priv->max_h == 0 || priv->max_v == 0) {
|
|
ret = GST_FLOW_ERROR;
|
|
GST_ERROR_OBJECT (self, "frame header with bad sampling factor");
|
|
goto beach;
|
|
}
|
|
|
|
priv->lossless = seg->marker == GST_JPEG_MARKER_SOF3;
|
|
|
|
g_assert (klass->new_picture);
|
|
ret = klass->new_picture (self, frame, seg->marker, &priv->frame_hdr);
|
|
if (ret != GST_FLOW_OK) {
|
|
GST_WARNING_OBJECT (self, "subclass failed to handle new picture");
|
|
goto beach;
|
|
}
|
|
|
|
priv->state |= GST_JPEG_DECODER_STATE_GOT_SOF;
|
|
|
|
beach:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static const GstJpegFrameComponent *
|
|
get_component (const GstJpegFrameHdr * frame_hdr, guint selector)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < frame_hdr->num_components; i++) {
|
|
const GstJpegFrameComponent *fcp = &frame_hdr->components[i];
|
|
if (fcp->identifier == selector)
|
|
return fcp;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
decode_scan (GstJpegDecoder * self, GstJpegSegment * seg)
|
|
{
|
|
GstJpegDecoderPrivate *priv = self->priv;
|
|
GstJpegDecoderClass *klass = GST_JPEG_DECODER_GET_CLASS (self);
|
|
GstJpegDecoderScan scan;
|
|
GstJpegScanHdr scan_hdr;
|
|
GstFlowReturn ret;
|
|
const guint8 *data;
|
|
guint size, scan_hdr_size;
|
|
guint64 mcus_per_row, mcu_rows_in_scan;
|
|
guint blocksize = priv->lossless ? 1 : DCT_SIZE;
|
|
|
|
/* E.2.3 Control procedure for decoding a scan */
|
|
if (!valid_state (priv->state, GST_JPEG_DECODER_STATE_GOT_SOF))
|
|
return GST_FLOW_OK; /* ignore segment */
|
|
|
|
if (!gst_jpeg_segment_parse_scan_header (seg, &scan_hdr)) {
|
|
GST_ERROR_OBJECT (self, "failed to parse scan header");
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
if (!valid_state (priv->state, GST_JPEG_DECODER_STATE_GOT_HUF_TABLE))
|
|
gst_jpeg_get_default_huffman_tables (&priv->huf_tables);
|
|
|
|
if (!valid_state (priv->state, GST_JPEG_DECODER_STATE_GOT_IQ_TABLE))
|
|
gst_jpeg_get_default_quantization_tables (&priv->quant_tables);
|
|
|
|
/* Non-interleaved */
|
|
if (scan_hdr.num_components == 1) {
|
|
const guint cs = scan_hdr.components[0].component_selector;
|
|
const GstJpegFrameComponent *fc = get_component (&priv->frame_hdr, cs);
|
|
|
|
if (!fc || fc->horizontal_factor == 0 || fc->vertical_factor == 0) {
|
|
GST_ERROR_OBJECT (self, "failed to validate frame component %u", cs);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
mcus_per_row = gst_util_uint64_scale_int_ceil (priv->frame_hdr.width,
|
|
fc->horizontal_factor, priv->max_h * blocksize);
|
|
mcu_rows_in_scan = gst_util_uint64_scale_int_ceil (priv->frame_hdr.height,
|
|
fc->vertical_factor, priv->max_v * blocksize);
|
|
} else {
|
|
mcus_per_row = gst_util_uint64_scale_int_ceil (priv->frame_hdr.width, 1,
|
|
priv->max_h * blocksize);
|
|
mcu_rows_in_scan =
|
|
gst_util_uint64_scale_int_ceil (priv->frame_hdr.height, 1,
|
|
priv->max_v * blocksize);
|
|
}
|
|
|
|
scan_hdr_size = (seg->data[seg->offset] << 8) | seg->data[seg->offset + 1];
|
|
size = seg->size - scan_hdr_size;
|
|
data = seg->data + seg->offset + scan_hdr_size;
|
|
|
|
if (size <= 0)
|
|
return GST_FLOW_ERROR;
|
|
|
|
/* *INDENT-OFF* */
|
|
scan = (GstJpegDecoderScan) {
|
|
.scan_hdr = &scan_hdr,
|
|
.huffman_tables = &priv->huf_tables,
|
|
.quantization_tables = &priv->quant_tables,
|
|
.restart_interval = priv->restart_interval,
|
|
.mcus_per_row = mcus_per_row,
|
|
.mcu_rows_in_scan = mcu_rows_in_scan,
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
g_assert (klass->decode_scan);
|
|
ret = klass->decode_scan (self, &scan, data, size);
|
|
|
|
if (ret == GST_FLOW_OK)
|
|
priv->state |= GST_JPEG_DECODER_STATE_GOT_SOS;
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
static const char *
|
|
_get_marker_name (guint marker)
|
|
{
|
|
#define MARKERS(V) \
|
|
V (SOF0) \
|
|
V (SOF1) \
|
|
V (SOF2) \
|
|
V (SOF3) \
|
|
V (SOF5) \
|
|
V (SOF6) \
|
|
V (SOF7) \
|
|
V (SOF9) \
|
|
V (SOF10) \
|
|
V (SOF11) \
|
|
V (SOF13) \
|
|
V (SOF14) \
|
|
V (SOF15) \
|
|
V (DHT) \
|
|
V (DAC) \
|
|
V (RST0) \
|
|
V (RST1) \
|
|
V (RST2) \
|
|
V (RST3) \
|
|
V (RST4) \
|
|
V (RST5) \
|
|
V (RST6) \
|
|
V (RST7) \
|
|
V (SOI) \
|
|
V (EOI) \
|
|
V (SOS) \
|
|
V (DQT) \
|
|
V (DNL) \
|
|
V (DRI) \
|
|
V (APP0) \
|
|
V (APP1) \
|
|
V (APP2) \
|
|
V (APP3) \
|
|
V (APP4) \
|
|
V (APP5) \
|
|
V (APP6) \
|
|
V (APP7) \
|
|
V (APP8) \
|
|
V (APP9) \
|
|
V (APP10) \
|
|
V (APP11) \
|
|
V (APP12) \
|
|
V (APP13) \
|
|
V (APP14) \
|
|
V (APP15) \
|
|
V (COM)
|
|
#define CASE(marker) case G_PASTE(GST_JPEG_MARKER_, marker): return G_STRINGIFY (marker);
|
|
switch (marker) {
|
|
MARKERS (CASE)
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
#undef CASE
|
|
#undef MARKERS
|
|
}
|
|
#endif
|
|
|
|
static GstFlowReturn
|
|
gst_jpeg_decoder_handle_frame (GstVideoDecoder * decoder,
|
|
GstVideoCodecFrame * frame)
|
|
{
|
|
GstJpegDecoder *self = GST_JPEG_DECODER (decoder);
|
|
GstJpegDecoderPrivate *priv = self->priv;
|
|
GstJpegDecoderClass *klass = GST_JPEG_DECODER_GET_CLASS (self);
|
|
GstBuffer *in_buf = frame->input_buffer;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
GstMapInfo map;
|
|
GstJpegMarker marker;
|
|
GstJpegSegment seg;
|
|
guint offset = 0;
|
|
|
|
GST_LOG_OBJECT (self, "handle frame %" GST_PTR_FORMAT, in_buf);
|
|
|
|
if (!gst_buffer_map (in_buf, &map, GST_MAP_READ)) {
|
|
GST_ERROR_OBJECT (self, "Cannot map input buffer");
|
|
ret = GST_FLOW_ERROR;
|
|
goto error;
|
|
}
|
|
|
|
priv->state = 0;
|
|
|
|
/* E.2.1 Control procedure for decoding compressed image data */
|
|
while (offset < map.size) {
|
|
if (!gst_jpeg_parse (&seg, map.data, map.size, offset))
|
|
goto unmap_and_error;
|
|
|
|
offset = seg.offset + seg.size;
|
|
marker = seg.marker;
|
|
|
|
if (!valid_state (priv->state, GST_JPEG_DECODER_STATE_GOT_SOI)
|
|
&& marker != GST_JPEG_MARKER_SOI)
|
|
goto unmap_and_error;
|
|
|
|
GST_LOG_OBJECT (self, "marker %s: %" G_GSIZE_FORMAT,
|
|
_get_marker_name (marker), seg.size);
|
|
|
|
switch (marker) {
|
|
|
|
/* Start of Image */
|
|
case GST_JPEG_MARKER_SOI:
|
|
priv->state |= GST_JPEG_DECODER_STATE_GOT_SOI;
|
|
priv->restart_interval = 0;
|
|
break;
|
|
|
|
/* End of Image */
|
|
case GST_JPEG_MARKER_EOI:
|
|
if (!valid_state (priv->state, GST_JPEG_DECODER_STATE_VALID_PICTURE))
|
|
goto unmap_and_error;
|
|
|
|
g_assert (klass->end_picture);
|
|
ret = klass->end_picture (self);
|
|
if (ret != GST_FLOW_OK)
|
|
goto unmap_and_error;
|
|
|
|
priv->state = 0;
|
|
|
|
gst_buffer_unmap (in_buf, &map);
|
|
|
|
GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
|
|
|
|
g_assert (klass->output_picture);
|
|
return klass->output_picture (self, frame);
|
|
|
|
/* Start of Scan */
|
|
case GST_JPEG_MARKER_SOS:{
|
|
/* get whole scan + ECSs, with RSTi */
|
|
GstJpegSegment seg_scan;
|
|
|
|
for (;;) {
|
|
if (!gst_jpeg_parse (&seg_scan, map.data, map.size, offset))
|
|
goto unmap_and_error;
|
|
|
|
if (seg_scan.marker < GST_JPEG_MARKER_RST_MIN
|
|
|| seg_scan.marker > GST_JPEG_MARKER_RST_MAX)
|
|
break;
|
|
|
|
offset = seg_scan.offset + seg_scan.size;
|
|
}
|
|
|
|
offset = seg_scan.offset - 2;
|
|
seg.size = offset - seg.offset;
|
|
|
|
ret = decode_scan (self, &seg);
|
|
if (ret != GST_FLOW_OK)
|
|
goto unmap_and_error;
|
|
|
|
break;
|
|
}
|
|
|
|
/* Interpret markers */
|
|
case GST_JPEG_MARKER_DAC:
|
|
/* FIXME: Annex D - Arithmetic coding */
|
|
GST_FIXME_OBJECT (self, "Arithmetic coding mode unsupported");
|
|
goto unmap_and_error;
|
|
case GST_JPEG_MARKER_DHT:
|
|
if (!decode_huffman_table (self, &seg)) {
|
|
ret = GST_FLOW_ERROR;
|
|
goto unmap_and_error;
|
|
}
|
|
break;
|
|
case GST_JPEG_MARKER_DQT:
|
|
if (!decode_quant_table (self, &seg)) {
|
|
ret = GST_FLOW_ERROR;
|
|
goto unmap_and_error;
|
|
}
|
|
break;
|
|
|
|
case GST_JPEG_MARKER_DRI:
|
|
if (!decode_restart_interval (self, &seg)) {
|
|
GST_WARNING_OBJECT (self, "Fail to decode restart interval");
|
|
goto unmap_and_error;
|
|
}
|
|
break;
|
|
case GST_JPEG_MARKER_DNL:
|
|
break;
|
|
default:
|
|
/* SOFn (Start Of Frame) */
|
|
if (marker >= GST_JPEG_MARKER_SOF_MIN &&
|
|
marker <= GST_JPEG_MARKER_SOF_MAX) {
|
|
ret = decode_frame (self, &seg, frame);
|
|
if (ret != GST_FLOW_OK)
|
|
goto unmap_and_error;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
ret = GST_FLOW_ERROR;
|
|
|
|
unmap_and_error:
|
|
{
|
|
gst_buffer_unmap (in_buf, &map);
|
|
goto error;
|
|
}
|
|
|
|
error:
|
|
{
|
|
if (ret == GST_FLOW_ERROR) {
|
|
GST_VIDEO_DECODER_ERROR (self, 1, STREAM, DECODE,
|
|
("Failed to decode data"), (NULL), ret);
|
|
}
|
|
|
|
gst_video_decoder_drop_frame (decoder, frame);
|
|
|
|
return ret;
|
|
}
|
|
}
|