/* * Copyright (C) 2012 Collabora Ltd. * Author: Sebastian Dröge * Copyright (C) 2013 Sebastian Dröge * * 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 "gstopenjpegdec.h" #include GST_DEBUG_CATEGORY_STATIC (gst_openjpeg_dec_debug); #define GST_CAT_DEFAULT gst_openjpeg_dec_debug enum { PROP_0, PROP_MAX_THREADS, PROP_LAST }; #define GST_OPENJPEG_DEC_DEFAULT_MAX_THREADS 0 static gboolean gst_openjpeg_dec_start (GstVideoDecoder * decoder); static gboolean gst_openjpeg_dec_stop (GstVideoDecoder * decoder); static gboolean gst_openjpeg_dec_set_format (GstVideoDecoder * decoder, GstVideoCodecState * state); static GstFlowReturn gst_openjpeg_dec_handle_frame (GstVideoDecoder * decoder, GstVideoCodecFrame * frame); static gboolean gst_openjpeg_dec_decide_allocation (GstVideoDecoder * decoder, GstQuery * query); static void gst_openjpeg_dec_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_openjpeg_dec_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); #if G_BYTE_ORDER == G_LITTLE_ENDIAN #define GRAY16 "GRAY16_LE" #define YUV10 "Y444_10LE, I422_10LE, I420_10LE" #else #define GRAY16 "GRAY16_BE" #define YUV10 "Y444_10BE, I422_10BE, I420_10BE" #endif static GstStaticPadTemplate gst_openjpeg_dec_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("image/x-j2c, " "alignment= (string){frame,stripe}, " GST_JPEG2000_SAMPLING_LIST "; " "image/x-jpc," "alignment= (string){frame,stripe}, " GST_JPEG2000_SAMPLING_LIST "; " "image/jp2") ); static GstStaticPadTemplate gst_openjpeg_dec_src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ ARGB64, ARGB, xRGB, " "AYUV64, " YUV10 ", " "AYUV, Y444, Y42B, I420, Y41B, YUV9, " "GRAY8, " GRAY16 " }")) ); #define parent_class gst_openjpeg_dec_parent_class G_DEFINE_TYPE (GstOpenJPEGDec, gst_openjpeg_dec, GST_TYPE_VIDEO_DECODER); GST_ELEMENT_REGISTER_DEFINE (openjpegdec, "openjpegdec", GST_RANK_PRIMARY, GST_TYPE_OPENJPEG_DEC); static void gst_openjpeg_dec_class_init (GstOpenJPEGDecClass * klass) { GstElementClass *element_class; GstVideoDecoderClass *video_decoder_class; GObjectClass *gobject_class = G_OBJECT_CLASS (klass); element_class = (GstElementClass *) klass; video_decoder_class = (GstVideoDecoderClass *) klass; gst_element_class_add_static_pad_template (element_class, &gst_openjpeg_dec_src_template); gst_element_class_add_static_pad_template (element_class, &gst_openjpeg_dec_sink_template); gst_element_class_set_static_metadata (element_class, "OpenJPEG JPEG2000 decoder", "Codec/Decoder/Video", "Decode JPEG2000 streams", "Sebastian Dröge "); video_decoder_class->start = GST_DEBUG_FUNCPTR (gst_openjpeg_dec_start); video_decoder_class->stop = GST_DEBUG_FUNCPTR (gst_openjpeg_dec_stop); video_decoder_class->set_format = GST_DEBUG_FUNCPTR (gst_openjpeg_dec_set_format); video_decoder_class->handle_frame = GST_DEBUG_FUNCPTR (gst_openjpeg_dec_handle_frame); video_decoder_class->decide_allocation = gst_openjpeg_dec_decide_allocation; gobject_class->set_property = gst_openjpeg_dec_set_property; gobject_class->get_property = gst_openjpeg_dec_get_property; /** * GstOpenJPEGDec:max-threads: * * Maximum number of worker threads to spawn. (0 = auto) * * Since: 1.18 */ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_MAX_THREADS, g_param_spec_int ("max-threads", "Maximum decode threads", "Maximum number of worker threads to spawn. (0 = auto)", 0, G_MAXINT, GST_OPENJPEG_DEC_DEFAULT_MAX_THREADS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); GST_DEBUG_CATEGORY_INIT (gst_openjpeg_dec_debug, "openjpegdec", 0, "OpenJPEG Decoder"); } static void gst_openjpeg_dec_init (GstOpenJPEGDec * self) { GstVideoDecoder *decoder = (GstVideoDecoder *) self; gst_video_decoder_set_packetized (decoder, TRUE); gst_video_decoder_set_needs_format (decoder, TRUE); gst_video_decoder_set_use_default_pad_acceptcaps (GST_VIDEO_DECODER_CAST (self), TRUE); GST_PAD_SET_ACCEPT_TEMPLATE (GST_VIDEO_DECODER_SINK_PAD (self)); opj_set_default_decoder_parameters (&self->params); self->sampling = GST_JPEG2000_SAMPLING_NONE; self->max_threads = GST_OPENJPEG_DEC_DEFAULT_MAX_THREADS; self->num_procs = g_get_num_processors (); } static gboolean gst_openjpeg_dec_start (GstVideoDecoder * decoder) { GstOpenJPEGDec *self = GST_OPENJPEG_DEC (decoder); GST_DEBUG_OBJECT (self, "Starting"); return TRUE; } static gboolean gst_openjpeg_dec_stop (GstVideoDecoder * video_decoder) { GstOpenJPEGDec *self = GST_OPENJPEG_DEC (video_decoder); GST_DEBUG_OBJECT (self, "Stopping"); if (self->output_state) { gst_video_codec_state_unref (self->output_state); self->output_state = NULL; } if (self->input_state) { gst_video_codec_state_unref (self->input_state); self->input_state = NULL; } if (self->current_frame) { gst_video_codec_frame_unref (self->current_frame); self->current_frame = NULL; } GST_DEBUG_OBJECT (self, "Stopped"); return TRUE; } static void gst_openjpeg_dec_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstOpenJPEGDec *dec = (GstOpenJPEGDec *) object; switch (prop_id) { case PROP_MAX_THREADS: g_atomic_int_set (&dec->max_threads, g_value_get_int (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_openjpeg_dec_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstOpenJPEGDec *dec = (GstOpenJPEGDec *) object; switch (prop_id) { case PROP_MAX_THREADS: g_value_set_int (value, g_atomic_int_get (&dec->max_threads)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gboolean gst_openjpeg_dec_set_format (GstVideoDecoder * decoder, GstVideoCodecState * state) { GstOpenJPEGDec *self = GST_OPENJPEG_DEC (decoder); GstStructure *s; gint caps_int = 0; const gchar *caps_string = NULL; GST_DEBUG_OBJECT (self, "Setting format: %" GST_PTR_FORMAT, state->caps); s = gst_caps_get_structure (state->caps, 0); self->color_space = OPJ_CLRSPC_UNKNOWN; if (gst_structure_has_name (s, "image/jp2")) { self->codec_format = OPJ_CODEC_JP2; self->is_jp2c = FALSE; } else if (gst_structure_has_name (s, "image/x-j2c")) { self->codec_format = OPJ_CODEC_J2K; self->is_jp2c = TRUE; } else if (gst_structure_has_name (s, "image/x-jpc")) { self->codec_format = OPJ_CODEC_J2K; self->is_jp2c = FALSE; } else { g_return_val_if_reached (FALSE); } self->num_stripes = 1; caps_string = gst_structure_get_string (s, "alignment"); gst_structure_get_int (s, "num-stripes", &caps_int); if (caps_int > 1) { self->num_stripes = caps_int; gst_video_decoder_set_subframe_mode (decoder, TRUE); } else { gst_video_decoder_set_subframe_mode (decoder, FALSE); if (g_strcmp0 (caps_string, "stripe") == 0) { GST_ERROR_OBJECT (self, "Alignment is set to stripe but num-stripes is missing"); return FALSE; } } self->sampling = gst_jpeg2000_sampling_from_string (gst_structure_get_string (s, "sampling")); if (gst_jpeg2000_sampling_is_rgb (self->sampling)) self->color_space = OPJ_CLRSPC_SRGB; else if (gst_jpeg2000_sampling_is_mono (self->sampling)) self->color_space = OPJ_CLRSPC_GRAY; else if (gst_jpeg2000_sampling_is_yuv (self->sampling)) self->color_space = OPJ_CLRSPC_SYCC; self->ncomps = 0; gst_structure_get_int (s, "num-components", &self->ncomps); if (self->input_state) gst_video_codec_state_unref (self->input_state); self->input_state = gst_video_codec_state_ref (state); return TRUE; } static gboolean reverse_rgb_channels (GstJPEG2000Sampling sampling) { return sampling == GST_JPEG2000_SAMPLING_BGR || sampling == GST_JPEG2000_SAMPLING_BGRA; } static void fill_frame_packed8_4 (GstOpenJPEGDec * self, GstVideoFrame * frame, opj_image_t * image) { gint x, y, y0, y1, w, c; guint8 *data_out, *tmp; const gint *data_in[4]; gint dstride; gint off[4]; w = GST_VIDEO_FRAME_WIDTH (frame); data_out = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); dstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0); for (c = 0; c < 4; c++) { data_in[c] = image->comps[c].data; off[c] = 0x80 * image->comps[c].sgnd; } /* copy only the stripe content (image) to the full size frame */ y0 = image->y0; y1 = image->y1; data_out += y0 * dstride; for (y = y0; y < y1; y++) { tmp = data_out; for (x = 0; x < w; x++) { /* alpha, from 4'th input channel */ tmp[0] = off[3] + *data_in[3]; /* colour channels */ tmp[1] = off[0] + *data_in[0]; tmp[2] = off[1] + *data_in[1]; tmp[3] = off[2] + *data_in[2]; tmp += 4; data_in[0]++; data_in[1]++; data_in[2]++; data_in[3]++; } data_out += dstride; } } static void fill_frame_packed16_4 (GstOpenJPEGDec * self, GstVideoFrame * frame, opj_image_t * image) { gint x, y, y0, y1, w, c; guint16 *data_out, *tmp; const gint *data_in[4]; gint dstride; gint shift[4], off[4]; w = GST_VIDEO_FRAME_WIDTH (frame); data_out = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); dstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0) / 2; for (c = 0; c < 4; c++) { data_in[c] = image->comps[c].data; off[c] = (1 << (image->comps[c].prec - 1)) * image->comps[c].sgnd; shift[c] = MAX (MIN (GST_VIDEO_FRAME_COMP_DEPTH (frame, c) - image->comps[c].prec, 8), 0); } y0 = image->y0; y1 = image->y1; data_out += y0 * dstride; for (y = y0; y < y1; y++) { tmp = data_out; for (x = 0; x < w; x++) { /* alpha, from 4'th input channel */ tmp[0] = off[3] + (*data_in[3] << shift[3]); /* colour channels */ tmp[1] = off[0] + (*data_in[0] << shift[0]); tmp[2] = off[1] + (*data_in[1] << shift[1]); tmp[3] = off[2] + (*data_in[2] << shift[2]); tmp += 4; data_in[0]++; data_in[1]++; data_in[2]++; data_in[3]++; } data_out += dstride; } } static void fill_frame_packed8_3 (GstOpenJPEGDec * self, GstVideoFrame * frame, opj_image_t * image) { gint x, y, y0, y1, w, c; guint8 *data_out, *tmp; const gint *data_in[3]; gint dstride; gint off[3]; w = GST_VIDEO_FRAME_WIDTH (frame); data_out = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); dstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0); for (c = 0; c < 3; c++) { data_in[c] = image->comps[c].data; off[c] = 0x80 * image->comps[c].sgnd; }; y0 = image->y0; y1 = image->y1; data_out += y0 * dstride; for (y = y0; y < y1; y++) { tmp = data_out; for (x = 0; x < w; x++) { tmp[0] = off[0] + *data_in[0]; tmp[1] = off[1] + *data_in[1]; tmp[2] = off[2] + *data_in[2]; data_in[0]++; data_in[1]++; data_in[2]++; tmp += 3; } data_out += dstride; } } static void fill_frame_packed16_3 (GstOpenJPEGDec * self, GstVideoFrame * frame, opj_image_t * image) { gint x, y, y0, y1, w, c; guint16 *data_out, *tmp; const gint *data_in[3]; gint dstride; gint shift[3], off[3]; w = GST_VIDEO_FRAME_WIDTH (frame); data_out = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); dstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0) / 2; for (c = 0; c < 3; c++) { data_in[c] = image->comps[c].data; off[c] = (1 << (image->comps[c].prec - 1)) * image->comps[c].sgnd; shift[c] = MAX (MIN (GST_VIDEO_FRAME_COMP_DEPTH (frame, c) - image->comps[c].prec, 8), 0); } y0 = image->y0; y1 = image->y1; data_out += y0 * dstride; for (y = y0; y < y1; y++) { tmp = data_out; for (x = 0; x < w; x++) { tmp[1] = off[0] + (*data_in[0] << shift[0]); tmp[2] = off[1] + (*data_in[1] << shift[1]); tmp[3] = off[2] + (*data_in[2] << shift[2]); tmp += 4; data_in[0]++; data_in[1]++; data_in[2]++; } data_out += dstride; } } /* for grayscale with alpha */ static void fill_frame_packed8_2 (GstOpenJPEGDec * self, GstVideoFrame * frame, opj_image_t * image) { gint x, y, y0, y1, w, c; guint8 *data_out, *tmp; const gint *data_in[2]; gint dstride; gint off[2]; w = GST_VIDEO_FRAME_WIDTH (frame); data_out = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); dstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0); for (c = 0; c < 2; c++) { data_in[c] = image->comps[c].data; off[c] = 0x80 * image->comps[c].sgnd; }; y0 = image->y0; y1 = image->y1; data_out += y0 * dstride; for (y = y0; y < y1; y++) { tmp = data_out; for (x = 0; x < w; x++) { /* alpha, from 2nd input channel */ tmp[0] = off[1] + *data_in[1]; /* luminance, from first input channel */ tmp[1] = off[0] + *data_in[0]; tmp[2] = tmp[1]; tmp[3] = tmp[1]; data_in[0]++; data_in[1]++; tmp += 4; } data_out += dstride; } } /* for grayscale with alpha */ static void fill_frame_packed16_2 (GstOpenJPEGDec * self, GstVideoFrame * frame, opj_image_t * image) { gint x, y, y0, y1, w, c; guint16 *data_out, *tmp; const gint *data_in[2]; gint dstride; gint shift[2], off[2]; w = GST_VIDEO_FRAME_WIDTH (frame); data_out = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); dstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0) / 2; for (c = 0; c < 2; c++) { data_in[c] = image->comps[c].data; off[c] = (1 << (image->comps[c].prec - 1)) * image->comps[c].sgnd; shift[c] = MAX (MIN (GST_VIDEO_FRAME_COMP_DEPTH (frame, c) - image->comps[c].prec, 8), 0); } y0 = image->y0; y1 = image->y1; data_out += y0 * dstride; for (y = y0; y < y1; y++) { tmp = data_out; for (x = 0; x < w; x++) { /* alpha, from 2nd input channel */ tmp[0] = off[1] + (*data_in[1] << shift[1]); /* luminance, from first input channel */ tmp[1] = off[0] + (*data_in[0] << shift[0]); tmp[2] = tmp[1]; tmp[3] = tmp[1]; tmp += 4; data_in[0]++; data_in[1]++; } data_out += dstride; } } static void fill_frame_planar8_1 (GstOpenJPEGDec * self, GstVideoFrame * frame, opj_image_t * image) { gint x, y, y0, y1, w; guint8 *data_out, *tmp; const gint *data_in; gint dstride; gint off; w = GST_VIDEO_FRAME_WIDTH (frame); data_out = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); dstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0); data_in = image->comps[0].data; off = 0x80 * image->comps[0].sgnd; y0 = image->y0; y1 = image->y1; data_out += y0 * dstride; for (y = y0; y < y1; y++) { tmp = data_out; for (x = 0; x < w; x++) *tmp++ = off + *data_in++; data_out += dstride; } } static void fill_frame_planar16_1 (GstOpenJPEGDec * self, GstVideoFrame * frame, opj_image_t * image) { gint x, y, y0, y1, w; guint16 *data_out, *tmp; const gint *data_in; gint dstride; gint shift, off; w = GST_VIDEO_FRAME_WIDTH (frame); data_out = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); dstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0) / 2; data_in = image->comps[0].data; off = (1 << (image->comps[0].prec - 1)) * image->comps[0].sgnd; shift = MAX (MIN (GST_VIDEO_FRAME_COMP_DEPTH (frame, 0) - image->comps[0].prec, 8), 0); y0 = image->y0; y1 = image->y1; data_out += y0 * dstride; for (y = y0; y < y1; y++) { tmp = data_out; for (x = 0; x < w; x++) *tmp++ = off + (*data_in++ << shift); data_out += dstride; } } static void fill_frame_planar8_3 (GstOpenJPEGDec * self, GstVideoFrame * frame, opj_image_t * image) { gint c, x, y, y0, y1, w; guint8 *data_out, *tmp; const gint *data_in; gint dstride, off; for (c = 0; c < 3; c++) { opj_image_comp_t *comp = image->comps + c; w = GST_VIDEO_FRAME_COMP_WIDTH (frame, c); dstride = GST_VIDEO_FRAME_COMP_STRIDE (frame, c); data_out = GST_VIDEO_FRAME_COMP_DATA (frame, c); data_in = comp->data; off = 0x80 * comp->sgnd; /* copy only the stripe content (image) to the full size frame */ y0 = comp->y0; y1 = comp->y0 + comp->h; data_out += y0 * dstride; for (y = y0; y < y1; y++) { tmp = data_out; for (x = 0; x < w; x++) *tmp++ = off + *data_in++; data_out += dstride; } } } static void fill_frame_planar16_3 (GstOpenJPEGDec * self, GstVideoFrame * frame, opj_image_t * image) { gint c, x, y, y0, y1, w; guint16 *data_out, *tmp; const gint *data_in; gint dstride; gint shift, off; for (c = 0; c < 3; c++) { opj_image_comp_t *comp = image->comps + c; w = GST_VIDEO_FRAME_COMP_WIDTH (frame, c); dstride = GST_VIDEO_FRAME_COMP_STRIDE (frame, c) / 2; data_out = (guint16 *) GST_VIDEO_FRAME_COMP_DATA (frame, c); data_in = comp->data; off = (1 << (comp->prec - 1)) * comp->sgnd; shift = MAX (MIN (GST_VIDEO_FRAME_COMP_DEPTH (frame, c) - comp->prec, 8), 0); /* copy only the stripe content (image) to the full size frame */ y0 = comp->y0; y1 = comp->y0 + comp->h; data_out += y0 * dstride; for (y = y0; y < y1; y++) { tmp = data_out; for (x = 0; x < w; x++) *tmp++ = off + (*data_in++ << shift); data_out += dstride; } } } static void fill_frame_planar8_3_generic (GstOpenJPEGDec * self, GstVideoFrame * frame, opj_image_t * image) { gint x, y, y0, y1, w, c; guint8 *data_out, *tmp; const gint *data_in[3]; gint dstride; gint dx[3], dy[3], off[3]; w = GST_VIDEO_FRAME_WIDTH (frame); data_out = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); dstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0); for (c = 0; c < 3; c++) { data_in[c] = image->comps[c].data; dx[c] = image->comps[c].dx; dy[c] = image->comps[c].dy; off[c] = 0x80 * image->comps[c].sgnd; } y0 = image->y0; y1 = image->y1; data_out += y0 * dstride; for (y = y0; y < y1; y++) { tmp = data_out; for (x = 0; x < w; x++) { tmp[0] = 0xff; tmp[1] = off[0] + data_in[0][((y / dy[0]) * w + x) / dx[0]]; tmp[2] = off[1] + data_in[1][((y / dy[1]) * w + x) / dx[1]]; tmp[3] = off[2] + data_in[2][((y / dy[2]) * w + x) / dx[2]]; tmp += 4; } data_out += dstride; } } static void fill_frame_planar8_4_generic (GstOpenJPEGDec * self, GstVideoFrame * frame, opj_image_t * image) { gint x, y, y0, y1, w, c; guint8 *data_out, *tmp; const gint *data_in[4]; gint dstride; gint dx[4], dy[4], off[4]; w = GST_VIDEO_FRAME_WIDTH (frame); data_out = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); dstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0); for (c = 0; c < 4; c++) { data_in[c] = image->comps[c].data; dx[c] = image->comps[c].dx; dy[c] = image->comps[c].dy; off[c] = 0x80 * image->comps[c].sgnd; } /* copy only the stripe content (image) to the full size frame */ y0 = image->y0; y1 = image->y1; data_out += y0 * dstride; for (y = y0; y < y1; y++) { tmp = data_out; for (x = 0; x < w; x++) { tmp[0] = off[3] + data_in[3][((y / dy[3]) * w + x) / dx[3]]; tmp[1] = off[0] + data_in[0][((y / dy[0]) * w + x) / dx[0]]; tmp[2] = off[1] + data_in[1][((y / dy[1]) * w + x) / dx[1]]; tmp[3] = off[2] + data_in[2][((y / dy[2]) * w + x) / dx[2]]; tmp += 4; } data_out += dstride; } } static void fill_frame_planar16_3_generic (GstOpenJPEGDec * self, GstVideoFrame * frame, opj_image_t * image) { gint x, y, y0, y1, w, c; guint16 *data_out, *tmp; const gint *data_in[3]; gint dstride; gint dx[3], dy[3], shift[3], off[3]; w = GST_VIDEO_FRAME_WIDTH (frame); data_out = (guint16 *) GST_VIDEO_FRAME_PLANE_DATA (frame, 0); dstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0) / 2; for (c = 0; c < 3; c++) { dx[c] = image->comps[c].dx; dy[c] = image->comps[c].dy; data_in[c] = image->comps[c].data; off[c] = (1 << (image->comps[c].prec - 1)) * image->comps[c].sgnd; shift[c] = MAX (MIN (GST_VIDEO_FRAME_COMP_DEPTH (frame, c) - image->comps[c].prec, 8), 0); } y0 = image->y0; y1 = image->y1; data_out += y0 * dstride; for (y = y0; y < y1; y++) { tmp = data_out; for (x = 0; x < w; x++) { tmp[0] = 0xff; tmp[1] = off[0] + (data_in[0][((y / dy[0]) * w + x) / dx[0]] << shift[0]); tmp[2] = off[1] + (data_in[1][((y / dy[1]) * w + x) / dx[1]] << shift[1]); tmp[3] = off[2] + (data_in[2][((y / dy[2]) * w + x) / dx[2]] << shift[2]); tmp += 4; } data_out += dstride; } } static void fill_frame_planar16_4_generic (GstOpenJPEGDec * self, GstVideoFrame * frame, opj_image_t * image) { gint x, y, y0, y1, w, c; guint16 *data_out, *tmp; const gint *data_in[4]; gint dstride; gint dx[4], dy[4], shift[4], off[4]; w = GST_VIDEO_FRAME_WIDTH (frame); data_out = (guint16 *) GST_VIDEO_FRAME_PLANE_DATA (frame, 0); dstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0) / 2; for (c = 0; c < 4; c++) { dx[c] = image->comps[c].dx; dy[c] = image->comps[c].dy; data_in[c] = image->comps[c].data; off[c] = (1 << (image->comps[c].prec - 1)) * image->comps[c].sgnd; shift[c] = MAX (MIN (GST_VIDEO_FRAME_COMP_DEPTH (frame, c) - image->comps[c].prec, 8), 0); } y0 = image->y0; y1 = image->y1; data_out += y0 * dstride; for (y = y0; y < y1; y++) { tmp = data_out; for (x = 0; x < w; x++) { tmp[0] = off[3] + (data_in[3][((y / dy[3]) * w + x) / dx[3]] << shift[3]); tmp[1] = off[0] + (data_in[0][((y / dy[0]) * w + x) / dx[0]] << shift[0]); tmp[2] = off[1] + (data_in[1][((y / dy[1]) * w + x) / dx[1]] << shift[1]); tmp[3] = off[2] + (data_in[2][((y / dy[2]) * w + x) / dx[2]] << shift[2]); tmp += 4; } data_out += dstride; } } static gint get_highest_prec (opj_image_t * image) { gint i; gint ret = 0; for (i = 0; i < image->numcomps; i++) ret = MAX (image->comps[i].prec, ret); return ret; } static GstFlowReturn gst_openjpeg_dec_negotiate (GstOpenJPEGDec * self, opj_image_t * image) { GstVideoFormat format; if (image->color_space == OPJ_CLRSPC_UNKNOWN || image->color_space == 0) image->color_space = self->color_space; switch (image->color_space) { case OPJ_CLRSPC_SRGB: if (image->numcomps == 4) { if (image->comps[0].dx != 1 || image->comps[0].dy != 1 || image->comps[1].dx != 1 || image->comps[1].dy != 1 || image->comps[2].dx != 1 || image->comps[2].dy != 1 || image->comps[3].dx != 1 || image->comps[3].dy != 1) { GST_ERROR_OBJECT (self, "Sub-sampling for RGBA not supported"); return GST_FLOW_NOT_NEGOTIATED; } if (get_highest_prec (image) == 8) { self->fill_frame = fill_frame_packed8_4; format = reverse_rgb_channels (self->sampling) ? GST_VIDEO_FORMAT_ABGR : GST_VIDEO_FORMAT_ARGB; } else if (get_highest_prec (image) <= 16) { self->fill_frame = fill_frame_packed16_4; format = GST_VIDEO_FORMAT_ARGB64; } else { GST_ERROR_OBJECT (self, "Unsupported depth %d", image->comps[3].prec); return GST_FLOW_NOT_NEGOTIATED; } } else if (image->numcomps == 3) { if (image->comps[0].dx != 1 || image->comps[0].dy != 1 || image->comps[1].dx != 1 || image->comps[1].dy != 1 || image->comps[2].dx != 1 || image->comps[2].dy != 1) { GST_ERROR_OBJECT (self, "Sub-sampling for RGB not supported"); return GST_FLOW_NOT_NEGOTIATED; } if (get_highest_prec (image) == 8) { self->fill_frame = fill_frame_packed8_3; format = reverse_rgb_channels (self->sampling) ? GST_VIDEO_FORMAT_BGR : GST_VIDEO_FORMAT_RGB; } else if (get_highest_prec (image) <= 16) { self->fill_frame = fill_frame_packed16_3; format = GST_VIDEO_FORMAT_ARGB64; } else { GST_ERROR_OBJECT (self, "Unsupported depth %d", get_highest_prec (image)); return GST_FLOW_NOT_NEGOTIATED; } } else { GST_ERROR_OBJECT (self, "Unsupported number of RGB components: %d", image->numcomps); return GST_FLOW_NOT_NEGOTIATED; } break; case OPJ_CLRSPC_GRAY: if (image->numcomps == 1) { if (image->comps[0].dx != 1 && image->comps[0].dy != 1) { GST_ERROR_OBJECT (self, "Sub-sampling for GRAY not supported"); return GST_FLOW_NOT_NEGOTIATED; } if (get_highest_prec (image) == 8) { self->fill_frame = fill_frame_planar8_1; format = GST_VIDEO_FORMAT_GRAY8; } else if (get_highest_prec (image) <= 16) { self->fill_frame = fill_frame_planar16_1; #if G_BYTE_ORDER == G_LITTLE_ENDIAN format = GST_VIDEO_FORMAT_GRAY16_LE; #else format = GST_VIDEO_FORMAT_GRAY16_BE; #endif } else { GST_ERROR_OBJECT (self, "Unsupported depth %d", get_highest_prec (image)); return GST_FLOW_NOT_NEGOTIATED; } } else if (image->numcomps == 2) { if ((image->comps[0].dx != 1 && image->comps[0].dy != 1) || (image->comps[1].dx != 1 && image->comps[1].dy != 1)) { GST_ERROR_OBJECT (self, "Sub-sampling for GRAY not supported"); return GST_FLOW_NOT_NEGOTIATED; } if (get_highest_prec (image) == 8) { self->fill_frame = fill_frame_packed8_2; format = GST_VIDEO_FORMAT_ARGB; } else if (get_highest_prec (image) <= 16) { self->fill_frame = fill_frame_packed16_2; format = GST_VIDEO_FORMAT_ARGB64; } else { GST_ERROR_OBJECT (self, "Unsupported depth %d", get_highest_prec (image)); return GST_FLOW_NOT_NEGOTIATED; } } else { GST_ERROR_OBJECT (self, "Unsupported number of GRAY components: %d", image->numcomps); return GST_FLOW_NOT_NEGOTIATED; } break; case OPJ_CLRSPC_SYCC: if (image->numcomps != 3 && image->numcomps != 4) { GST_ERROR_OBJECT (self, "Unsupported number of YUV components: %d", image->numcomps); return GST_FLOW_NOT_NEGOTIATED; } if (image->comps[0].dx != 1 || image->comps[0].dy != 1) { GST_ERROR_OBJECT (self, "Sub-sampling of luma plane not supported"); return GST_FLOW_NOT_NEGOTIATED; } if (image->comps[1].dx != image->comps[2].dx || image->comps[1].dy != image->comps[2].dy) { GST_ERROR_OBJECT (self, "Different sub-sampling of chroma planes not supported"); return GST_FLOW_ERROR; } if (image->numcomps == 4) { if (image->comps[3].dx != 1 || image->comps[3].dy != 1) { GST_ERROR_OBJECT (self, "Sub-sampling of alpha plane not supported"); return GST_FLOW_NOT_NEGOTIATED; } if (get_highest_prec (image) == 8) { self->fill_frame = fill_frame_planar8_4_generic; format = GST_VIDEO_FORMAT_AYUV; } else if (image->comps[3].prec <= 16) { self->fill_frame = fill_frame_planar16_4_generic; format = GST_VIDEO_FORMAT_AYUV64; } else { GST_ERROR_OBJECT (self, "Unsupported depth %d", image->comps[0].prec); return GST_FLOW_NOT_NEGOTIATED; } } else if (image->numcomps == 3) { if (get_highest_prec (image) == 8) { if (image->comps[1].dx == 1 && image->comps[1].dy == 1) { self->fill_frame = fill_frame_planar8_3; format = GST_VIDEO_FORMAT_Y444; } else if (image->comps[1].dx == 2 && image->comps[1].dy == 1) { self->fill_frame = fill_frame_planar8_3; format = GST_VIDEO_FORMAT_Y42B; } else if (image->comps[1].dx == 2 && image->comps[1].dy == 2) { self->fill_frame = fill_frame_planar8_3; format = GST_VIDEO_FORMAT_I420; } else if (image->comps[1].dx == 4 && image->comps[1].dy == 1) { self->fill_frame = fill_frame_planar8_3; format = GST_VIDEO_FORMAT_Y41B; } else if (image->comps[1].dx == 4 && image->comps[1].dy == 4) { self->fill_frame = fill_frame_planar8_3; format = GST_VIDEO_FORMAT_YUV9; } else { self->fill_frame = fill_frame_planar8_3_generic; format = GST_VIDEO_FORMAT_AYUV; } } else if (get_highest_prec (image) <= 16) { if (image->comps[0].prec == 10 && image->comps[1].prec == 10 && image->comps[2].prec == 10) { if (image->comps[1].dx == 1 && image->comps[1].dy == 1) { self->fill_frame = fill_frame_planar16_3; #if G_BYTE_ORDER == G_LITTLE_ENDIAN format = GST_VIDEO_FORMAT_Y444_10LE; #else format = GST_VIDEO_FORMAT_Y444_10BE; #endif } else if (image->comps[1].dx == 2 && image->comps[1].dy == 1) { self->fill_frame = fill_frame_planar16_3; #if G_BYTE_ORDER == G_LITTLE_ENDIAN format = GST_VIDEO_FORMAT_I422_10LE; #else format = GST_VIDEO_FORMAT_I422_10BE; #endif } else if (image->comps[1].dx == 2 && image->comps[1].dy == 2) { self->fill_frame = fill_frame_planar16_3; #if G_BYTE_ORDER == G_LITTLE_ENDIAN format = GST_VIDEO_FORMAT_I420_10LE; #else format = GST_VIDEO_FORMAT_I420_10BE; #endif } else { self->fill_frame = fill_frame_planar16_3_generic; format = GST_VIDEO_FORMAT_AYUV64; } } else { self->fill_frame = fill_frame_planar16_3_generic; format = GST_VIDEO_FORMAT_AYUV64; } } else { GST_ERROR_OBJECT (self, "Unsupported depth %d", get_highest_prec (image)); return GST_FLOW_NOT_NEGOTIATED; } } else { GST_ERROR_OBJECT (self, "Unsupported number of YUV components: %d", image->numcomps); return GST_FLOW_NOT_NEGOTIATED; } break; default: GST_ERROR_OBJECT (self, "Unsupported colorspace %d", image->color_space); return GST_FLOW_NOT_NEGOTIATED; } if (!self->output_state || self->output_state->info.finfo->format != format || self->output_state->info.width != self->input_state->info.width || self->output_state->info.height != self->input_state->info.height) { if (self->output_state) gst_video_codec_state_unref (self->output_state); self->output_state = gst_video_decoder_set_output_state (GST_VIDEO_DECODER (self), format, self->input_state->info.width, self->input_state->info.height, self->input_state); if (!gst_video_decoder_negotiate (GST_VIDEO_DECODER (self))) return GST_FLOW_NOT_NEGOTIATED; } return GST_FLOW_OK; } static void gst_openjpeg_dec_opj_error (const char *msg, void *userdata) { GstOpenJPEGDec *self = GST_OPENJPEG_DEC (userdata); gchar *trimmed = g_strchomp (g_strdup (msg)); GST_TRACE_OBJECT (self, "openjpeg error: %s", trimmed); g_free (trimmed); } static void gst_openjpeg_dec_opj_warning (const char *msg, void *userdata) { GstOpenJPEGDec *self = GST_OPENJPEG_DEC (userdata); gchar *trimmed = g_strchomp (g_strdup (msg)); GST_TRACE_OBJECT (self, "openjpeg warning: %s", trimmed); g_free (trimmed); } static void gst_openjpeg_dec_opj_info (const char *msg, void *userdata) { GstOpenJPEGDec *self = GST_OPENJPEG_DEC (userdata); gchar *trimmed = g_strchomp (g_strdup (msg)); GST_TRACE_OBJECT (self, "openjpeg info: %s", trimmed); g_free (trimmed); } typedef struct { guint8 *data; guint offset, size; } MemStream; static OPJ_SIZE_T read_fn (void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) { MemStream *mstream = p_user_data; OPJ_SIZE_T read; if (mstream->offset == mstream->size) return -1; if (mstream->offset + p_nb_bytes > mstream->size) read = mstream->size - mstream->offset; else read = p_nb_bytes; memcpy (p_buffer, mstream->data + mstream->offset, read); mstream->offset += read; return read; } static OPJ_SIZE_T write_fn (void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) { g_return_val_if_reached (-1); } static OPJ_OFF_T skip_fn (OPJ_OFF_T p_nb_bytes, void *p_user_data) { MemStream *mstream = p_user_data; OPJ_OFF_T skip; if (mstream->offset + p_nb_bytes > mstream->size) skip = mstream->size - mstream->offset; else skip = p_nb_bytes; mstream->offset += skip; return skip; } static OPJ_BOOL seek_fn (OPJ_OFF_T p_nb_bytes, void *p_user_data) { MemStream *mstream = p_user_data; if (p_nb_bytes > mstream->size) return OPJ_FALSE; mstream->offset = p_nb_bytes; return OPJ_TRUE; } static void gst_openjpeg_dec_handle_frame_cleanup (GstOpenJPEGDec * self, GstVideoCodecFrame * frame, GstMapInfo * map, opj_codec_t * dec, opj_stream_t * stream, opj_image_t * image) { if (image) opj_image_destroy (image); if (stream) opj_stream_destroy (stream); if (dec) opj_destroy_codec (dec); if (frame) { if (map) gst_buffer_unmap (frame->input_buffer, map); gst_video_codec_frame_unref (frame); } } static GstFlowReturn gst_openjpeg_dec_handle_frame (GstVideoDecoder * decoder, GstVideoCodecFrame * frame) { GstOpenJPEGDec *self = GST_OPENJPEG_DEC (decoder); GstFlowReturn ret = GST_FLOW_OK; gint64 deadline; GstMapInfo map; opj_codec_t *dec = NULL; opj_stream_t *stream = NULL; MemStream mstream; opj_image_t *image = NULL; GstVideoFrame vframe; opj_dparameters_t params; gint max_threads; guint current_stripe = 1; current_stripe = gst_video_decoder_get_current_subframe_index (decoder); GST_DEBUG_OBJECT (self, "Handling frame with current stripe %d", current_stripe); deadline = gst_video_decoder_get_max_decode_time (decoder, frame); if (self->drop_subframes || deadline < 0) { GST_INFO_OBJECT (self, "Dropping too late frame: deadline %" G_GINT64_FORMAT, deadline); self->drop_subframes = TRUE; if (current_stripe == self->num_stripes) { ret = gst_video_decoder_drop_frame (decoder, frame); self->drop_subframes = FALSE; } else gst_video_decoder_drop_subframe (decoder, frame); return ret; } dec = opj_create_decompress (self->codec_format); if (!dec) goto initialization_error; if (G_UNLIKELY (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_TRACE)) { opj_set_info_handler (dec, gst_openjpeg_dec_opj_info, self); opj_set_warning_handler (dec, gst_openjpeg_dec_opj_warning, self); opj_set_error_handler (dec, gst_openjpeg_dec_opj_error, self); } else { opj_set_info_handler (dec, NULL, NULL); opj_set_warning_handler (dec, NULL, NULL); opj_set_error_handler (dec, NULL, NULL); } params = self->params; if (self->ncomps) params.jpwl_exp_comps = self->ncomps; if (!opj_setup_decoder (dec, ¶ms)) goto open_error; max_threads = g_atomic_int_get (&self->max_threads); if (max_threads == 0) max_threads = self->num_procs; if (!opj_codec_set_threads (dec, max_threads)) GST_WARNING_OBJECT (self, "Failed to set %d number of threads", max_threads); if (!gst_buffer_map (frame->input_buffer, &map, GST_MAP_READ)) goto map_read_error; if (self->is_jp2c && map.size < 8) goto open_error; stream = opj_stream_create (4096, OPJ_TRUE); if (!stream) goto open_error; mstream.data = map.data + (self->is_jp2c ? 8 : 0); mstream.offset = 0; mstream.size = map.size - (self->is_jp2c ? 8 : 0); opj_stream_set_read_function (stream, read_fn); opj_stream_set_write_function (stream, write_fn); opj_stream_set_skip_function (stream, skip_fn); opj_stream_set_seek_function (stream, seek_fn); opj_stream_set_user_data (stream, &mstream, NULL); opj_stream_set_user_data_length (stream, mstream.size); image = NULL; if (!opj_read_header (stream, dec, &image)) goto decode_error; if (!opj_decode (dec, stream, image)) goto decode_error; { gint i; for (i = 0; i < image->numcomps; i++) { if (image->comps[i].data == NULL) goto decode_error; } } gst_buffer_unmap (frame->input_buffer, &map); ret = gst_openjpeg_dec_negotiate (self, image); if (ret != GST_FLOW_OK) goto negotiate_error; if (!gst_video_decoder_get_subframe_mode (decoder) || gst_video_decoder_get_current_subframe_index (decoder) == 1) { ret = gst_video_decoder_allocate_output_frame (decoder, frame); if (ret != GST_FLOW_OK) goto allocate_error; self->current_frame = gst_video_codec_frame_ref (frame); } if (!gst_video_frame_map (&vframe, &self->output_state->info, self->current_frame->output_buffer, GST_MAP_WRITE)) goto map_write_error; if (current_stripe) self->fill_frame (self, &vframe, image); else { GST_ERROR_OBJECT (decoder, " current_stripe should be greater than 0"); goto map_write_error; } gst_video_frame_unmap (&vframe); opj_end_decompress (dec, stream); opj_stream_destroy (stream); opj_image_destroy (image); opj_destroy_codec (dec); if (current_stripe == self->num_stripes) { ret = gst_video_decoder_finish_frame (decoder, self->current_frame); gst_video_codec_frame_unref (frame); self->current_frame = NULL; } else if (gst_video_decoder_get_current_subframe_index (decoder) > 0) gst_video_codec_frame_unref (frame); return ret; initialization_error: { gst_video_codec_frame_unref (frame); GST_ELEMENT_ERROR (self, LIBRARY, INIT, ("Failed to initialize OpenJPEG decoder"), (NULL)); return GST_FLOW_ERROR; } map_read_error: { gst_openjpeg_dec_handle_frame_cleanup (self, frame, NULL, dec, stream, image); GST_ELEMENT_ERROR (self, CORE, FAILED, ("Failed to map input buffer"), (NULL)); return GST_FLOW_ERROR; } open_error: { gst_openjpeg_dec_handle_frame_cleanup (self, frame, &map, dec, stream, image); GST_ELEMENT_ERROR (self, LIBRARY, INIT, ("Failed to open OpenJPEG stream"), (NULL)); return GST_FLOW_ERROR; } decode_error: { gst_openjpeg_dec_handle_frame_cleanup (self, frame, &map, dec, stream, image); GST_VIDEO_DECODER_ERROR (self, 1, STREAM, DECODE, ("Failed to decode OpenJPEG stream"), (NULL), ret); return ret; } negotiate_error: { gst_openjpeg_dec_handle_frame_cleanup (self, frame, NULL, dec, stream, image); GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, ("Failed to negotiate"), (NULL)); return ret; } allocate_error: { gst_openjpeg_dec_handle_frame_cleanup (self, frame, NULL, dec, stream, image); GST_ELEMENT_ERROR (self, CORE, FAILED, ("Failed to allocate output buffer"), (NULL)); return ret; } map_write_error: { gst_openjpeg_dec_handle_frame_cleanup (self, frame, NULL, dec, stream, image); GST_ELEMENT_ERROR (self, CORE, FAILED, ("Failed to map output buffer"), (NULL)); return GST_FLOW_ERROR; } } static gboolean gst_openjpeg_dec_decide_allocation (GstVideoDecoder * decoder, GstQuery * query) { GstBufferPool *pool; GstStructure *config; if (!GST_VIDEO_DECODER_CLASS (parent_class)->decide_allocation (decoder, query)) return FALSE; g_assert (gst_query_get_n_allocation_pools (query) > 0); gst_query_parse_nth_allocation_pool (query, 0, &pool, NULL, NULL, NULL); g_assert (pool != NULL); config = gst_buffer_pool_get_config (pool); if (gst_query_find_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL)) { gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); } gst_buffer_pool_set_config (pool, config); gst_object_unref (pool); return TRUE; }