From c5f6e74db95301bf7f5040f1cea2e02e60078d8c Mon Sep 17 00:00:00 2001 From: Thiago Santos Date: Thu, 10 Dec 2009 22:20:45 -0300 Subject: [PATCH] qtmux: support more of j2k Reads the new caps added to qtdemux by commit c917d65e6df0b5d585f905c7ad78a8a0a44b2cb0 and adds its corresponding atoms. Also adds support for image/x-jpc as it is the same as image/x-jp2, except that the buffers need to be boxed inside a jp2c isom box before muxing. To solve this the QTPads now have a function that (if not NULL) is called when a buffer is collected. This function returns a replacement to the current collected buffer. Fixes #598916 --- gst/quicktime/atoms.c | 169 +++++++++++++++++++++++++++++++++++++------- gst/quicktime/atoms.h | 7 +- gst/quicktime/fourcc.h | 1 + gst/quicktime/gstqtmux.c | 69 ++++++++++++++++-- gst/quicktime/gstqtmux.h | 21 +++++- gst/quicktime/gstqtmuxmap.c | 3 +- 6 files changed, 236 insertions(+), 34 deletions(-) diff --git a/gst/quicktime/atoms.c b/gst/quicktime/atoms.c index 66b0a1a..84c3068 100644 --- a/gst/quicktime/atoms.c +++ b/gst/quicktime/atoms.c @@ -45,8 +45,8 @@ #include #include -/* only needed for gst_util_uint64_scale */ #include +#include /** * Creates a new AtomsContext for the given flavor. @@ -3182,52 +3182,173 @@ build_mov_aac_extension (AtomTRAK * trak, const GstBuffer * codec_data) } AtomInfo * -build_jp2h_extension (AtomTRAK * trak, gint width, gint height, guint32 fourcc) +build_fiel_extension (gint fields) +{ + AtomData *atom_data; + GstBuffer *buf; + + if (fields == 1) { + return NULL; + } + + buf = gst_buffer_new_and_alloc (1); + GST_BUFFER_DATA (buf)[0] = (guint8) fields; + + atom_data = + atom_data_new_from_gst_buffer (GST_MAKE_FOURCC ('f', 'i', 'e', 'l'), buf); + gst_buffer_unref (buf); + + return build_atom_info_wrapper ((Atom *) atom_data, atom_data_copy_data, + atom_data_free); +} + +AtomInfo * +build_jp2x_extension (const GstBuffer * prefix) +{ + AtomData *atom_data; + + if (!prefix) { + return NULL; + } + + atom_data = + atom_data_new_from_gst_buffer (GST_MAKE_FOURCC ('j', 'p', '2', 'x'), + prefix); + + return build_atom_info_wrapper ((Atom *) atom_data, atom_data_copy_data, + atom_data_free); +} + +AtomInfo * +build_jp2h_extension (AtomTRAK * trak, gint width, gint height, guint32 fourcc, + gint ncomp, const GValue * cmap_array, const GValue * cdef_array) { AtomData *atom_data; GstBuffer *buf; - guint8 *data; guint8 cenum; + gint i; + gint idhr_size = 22; + gint colr_size = 15; + gint cmap_size = 0, cdef_size = 0; + gint cmap_array_size = 0; + gint cdef_array_size = 0; + GstByteWriter writer; + + g_return_val_if_fail (cmap_array == NULL || + GST_VALUE_HOLDS_ARRAY (cmap_array), NULL); + g_return_val_if_fail (cdef_array == NULL || + GST_VALUE_HOLDS_ARRAY (cdef_array), NULL); if (fourcc == GST_MAKE_FOURCC ('s', 'R', 'G', 'B')) { cenum = 0x10; + if (ncomp == 0) + ncomp = 3; + } else if (fourcc == GST_MAKE_FOURCC ('G', 'R', 'A', 'Y')) { + cenum = 0x11; + if (ncomp == 0) + ncomp = 1; } else if (fourcc == GST_MAKE_FOURCC ('s', 'Y', 'U', 'V')) { cenum = 0x12; + if (ncomp == 0) + ncomp = 3; } else - return FALSE; + return NULL; - buf = gst_buffer_new_and_alloc (22 + 15); - data = GST_BUFFER_DATA (buf); + if (cmap_array) { + cmap_array_size = gst_value_array_get_size (cmap_array); + cmap_size = 8 + cmap_array_size * 4; + } + if (cdef_array) { + cdef_array_size = gst_value_array_get_size (cdef_array); + cdef_size = 8 + 2 + cdef_array_size * 6; + } + + buf = gst_buffer_new_and_alloc (idhr_size + colr_size + cmap_size + + cdef_size); + gst_byte_writer_init_with_buffer (&writer, buf, FALSE); /* ihdr = image header box */ - GST_WRITE_UINT32_BE (data, 22); - GST_WRITE_UINT32_LE (data + 4, GST_MAKE_FOURCC ('i', 'h', 'd', 'r')); - GST_WRITE_UINT32_BE (data + 8, height); - GST_WRITE_UINT32_BE (data + 12, width); - /* FIXME perhaps parse from stream, - * though exactly 3 in any respectable colourspace */ - GST_WRITE_UINT16_BE (data + 16, 3); + gst_byte_writer_put_uint32_be (&writer, 22); + gst_byte_writer_put_uint32_le (&writer, GST_MAKE_FOURCC ('i', 'h', 'd', 'r')); + gst_byte_writer_put_uint32_be (&writer, height); + gst_byte_writer_put_uint32_be (&writer, width); + gst_byte_writer_put_uint16_be (&writer, ncomp); /* 8 bits per component, unsigned */ - GST_WRITE_UINT8 (data + 18, 0x7); + gst_byte_writer_put_uint8 (&writer, 0x7); /* compression type; reserved */ - GST_WRITE_UINT8 (data + 19, 0x7); + gst_byte_writer_put_uint8 (&writer, 0x7); /* colour space (un)known */ - GST_WRITE_UINT8 (data + 20, 0x0); + gst_byte_writer_put_uint8 (&writer, 0x0); /* intellectual property right (box present) */ - GST_WRITE_UINT8 (data + 21, 0x0); + gst_byte_writer_put_uint8 (&writer, 0x0); /* colour specification box */ - data += 22; - GST_WRITE_UINT32_BE (data, 15); - GST_WRITE_UINT32_LE (data + 4, GST_MAKE_FOURCC ('c', 'o', 'l', 'r')); + gst_byte_writer_put_uint32_be (&writer, 15); + gst_byte_writer_put_uint32_le (&writer, GST_MAKE_FOURCC ('c', 'o', 'l', 'r')); + /* specification method: enumerated */ - GST_WRITE_UINT8 (data + 8, 0x1); + gst_byte_writer_put_uint8 (&writer, 0x1); /* precedence; reserved */ - GST_WRITE_UINT8 (data + 9, 0x0); + gst_byte_writer_put_uint8 (&writer, 0x0); /* approximation; reserved */ - GST_WRITE_UINT8 (data + 10, 0x0); + gst_byte_writer_put_uint8 (&writer, 0x0); /* enumerated colourspace */ - GST_WRITE_UINT32_BE (data + 11, cenum); + gst_byte_writer_put_uint32_be (&writer, cenum); + + if (cmap_array) { + gst_byte_writer_put_uint32_be (&writer, cmap_size); + gst_byte_writer_put_uint32_le (&writer, + GST_MAKE_FOURCC ('c', 'm', 'a', 'p')); + for (i = 0; i < cmap_array_size; i++) { + const GValue *item; + gint value; + guint16 cmp; + guint8 mtyp; + guint8 pcol; + item = gst_value_array_get_value (cmap_array, i); + value = g_value_get_int (item); + + /* value is '(mtyp << 24) | (pcol << 16) | cmp' */ + cmp = value & 0xFFFF; + mtyp = value >> 24; + pcol = (value >> 16) & 0xFF; + + if (mtyp == 1) + GST_WARNING ("MTYP of cmap atom signals Pallete Mapping, but we don't " + "handle Pallete mapping atoms yet"); + + gst_byte_writer_put_uint16_be (&writer, cmp); + gst_byte_writer_put_uint8 (&writer, mtyp); + gst_byte_writer_put_uint8 (&writer, pcol); + } + } + + if (cdef_array) { + gst_byte_writer_put_uint32_be (&writer, cdef_size); + gst_byte_writer_put_uint32_le (&writer, + GST_MAKE_FOURCC ('c', 'd', 'e', 'f')); + gst_byte_writer_put_uint16_be (&writer, cdef_array_size); + for (i = 0; i < cdef_array_size; i++) { + const GValue *item; + gint value; + item = gst_value_array_get_value (cdef_array, i); + value = g_value_get_int (item); + + gst_byte_writer_put_uint16_be (&writer, i); + if (value > 0) { + gst_byte_writer_put_uint16_be (&writer, 0); + gst_byte_writer_put_uint16_be (&writer, value); + } else if (value < 0) { + gst_byte_writer_put_uint16_be (&writer, -value); + gst_byte_writer_put_uint16_be (&writer, 0); /* TODO what here? */ + } else { + gst_byte_writer_put_uint16_be (&writer, 1); + gst_byte_writer_put_uint16_be (&writer, 0); + } + } + } + + g_assert (gst_byte_writer_get_remaining (&writer) == 0); atom_data = atom_data_new_from_gst_buffer (FOURCC_jp2h, buf); gst_buffer_unref (buf); diff --git a/gst/quicktime/atoms.h b/gst/quicktime/atoms.h index d9984a0..e1b1337 100644 --- a/gst/quicktime/atoms.h +++ b/gst/quicktime/atoms.h @@ -670,7 +670,12 @@ AtomInfo * build_mov_aac_extension (AtomTRAK * trak, const GstBuffer * cod AtomInfo * build_esds_extension (AtomTRAK * trak, guint8 object_type, guint8 stream_type, const GstBuffer * codec_data); AtomInfo * build_jp2h_extension (AtomTRAK * trak, gint width, gint height, - guint32 fourcc); + guint32 fourcc, gint ncomp, + const GValue * cmap_array, + const GValue * cdef_array); + +AtomInfo * build_jp2x_extension (const GstBuffer * prefix); +AtomInfo * build_fiel_extension (gint fields); AtomInfo * build_amr_extension (); AtomInfo * build_h263_extension (); AtomInfo * build_gama_atom (gdouble gamma); diff --git a/gst/quicktime/fourcc.h b/gst/quicktime/fourcc.h index 647ab23..52a24c2 100644 --- a/gst/quicktime/fourcc.h +++ b/gst/quicktime/fourcc.h @@ -170,6 +170,7 @@ G_BEGIN_DECLS #define FOURCC_jpeg GST_MAKE_FOURCC('j','p','e','g') #define FOURCC_mjp2 GST_MAKE_FOURCC('m','j','p','2') #define FOURCC_jp2h GST_MAKE_FOURCC('j','p','2','h') +#define FOURCC_jp2c GST_MAKE_FOURCC('j','p','2','c') #define FOURCC_gama GST_MAKE_FOURCC('g','a','m','a') /* SVQ3 fourcc */ diff --git a/gst/quicktime/gstqtmux.c b/gst/quicktime/gstqtmux.c index 3824d25..6240bff 100644 --- a/gst/quicktime/gstqtmux.c +++ b/gst/quicktime/gstqtmux.c @@ -251,6 +251,7 @@ gst_qt_mux_pad_reset (GstQTPad * qtpad) qtpad->sync = FALSE; qtpad->last_dts = 0; qtpad->first_ts = GST_CLOCK_TIME_NONE; + qtpad->prepare_buf_func = NULL; if (qtpad->last_buf) gst_buffer_replace (&qtpad->last_buf, NULL); @@ -355,6 +356,30 @@ gst_qt_mux_finalize (GObject * object) G_OBJECT_CLASS (parent_class)->finalize (object); } +static GstBuffer * +gst_qt_mux_prepare_jpc_buffer (GstQTPad * qtpad, GstBuffer * buf, + GstQTMux * qtmux) +{ + GstBuffer *newbuf; + + GST_LOG_OBJECT (qtmux, "Preparing jpc buffer"); + + if (buf == NULL) + return NULL; + + newbuf = gst_buffer_new_and_alloc (GST_BUFFER_SIZE (buf) + 8); + gst_buffer_copy_metadata (newbuf, buf, GST_BUFFER_COPY_ALL); + + GST_WRITE_UINT32_BE (GST_BUFFER_DATA (newbuf), GST_BUFFER_SIZE (newbuf)); + GST_WRITE_UINT32_LE (GST_BUFFER_DATA (newbuf) + 4, FOURCC_jp2c); + + memcpy (GST_BUFFER_DATA (newbuf) + 8, GST_BUFFER_DATA (buf), + GST_BUFFER_SIZE (buf)); + gst_buffer_unref (buf); + + return newbuf; +} + static void gst_qt_mux_add_mp4_tag (GstQTMux * qtmux, const GstTagList * list, const char *tag, const char *tag2, guint32 fourcc) @@ -1398,6 +1423,11 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf) if (!pad->fourcc) goto not_negotiated; + /* if this pad has a prepare function, call it */ + if (pad->prepare_buf_func != NULL) { + buf = pad->prepare_buf_func (pad, buf, qtmux); + } + last_buf = pad->last_buf; if (last_buf == NULL) { #ifndef GST_DISABLE_GST_DEBUG @@ -1668,6 +1698,8 @@ gst_qt_mux_audio_sink_set_caps (GstPad * pad, GstCaps * caps) qtpad = (GstQTPad *) gst_pad_get_element_private (pad); g_assert (qtpad); + qtpad->prepare_buf_func = NULL; + /* does not go well to renegotiate stream mid-way */ if (qtpad->fourcc) goto refuse_renegotiation; @@ -1916,6 +1948,8 @@ gst_qt_mux_video_sink_set_caps (GstPad * pad, GstCaps * caps) qtpad = (GstQTPad *) gst_pad_get_element_private (pad); g_assert (qtpad); + qtpad->prepare_buf_func = NULL; + /* does not go well to renegotiate stream mid-way */ if (qtpad->fourcc) goto refuse_renegotiation; @@ -2095,20 +2129,43 @@ gst_qt_mux_video_sink_set_caps (GstPad * pad, GstCaps * caps) } else if (strcmp (mimetype, "image/jpeg") == 0) { entry.fourcc = FOURCC_jpeg; sync = FALSE; - } else if (strcmp (mimetype, "image/x-j2c") == 0) { + } else if (strcmp (mimetype, "image/x-j2c") == 0 || + strcmp (mimetype, "image/x-jpc") == 0) { guint32 fourcc; + const GValue *cmap_array; + const GValue *cdef_array; + gint ncomp = 0; + gint fields = 1; + + if (strcmp (mimetype, "image/x-jpc") == 0) { + qtpad->prepare_buf_func = gst_qt_mux_prepare_jpc_buffer; + } + + gst_structure_get_int (structure, "num-components", &ncomp); + gst_structure_get_int (structure, "fields", &fields); + cmap_array = gst_structure_get_value (structure, "component-map"); + cdef_array = gst_structure_get_value (structure, "channel-definitions"); ext_atom = NULL; entry.fourcc = FOURCC_mjp2; sync = FALSE; - if (!gst_structure_get_fourcc (structure, "fourcc", &fourcc) || - !(ext_atom = - build_jp2h_extension (qtpad->trak, width, height, fourcc))) { + if (gst_structure_get_fourcc (structure, "fourcc", &fourcc) && + (ext_atom = + build_jp2h_extension (qtpad->trak, width, height, fourcc, ncomp, + cmap_array, cdef_array)) != NULL) { + ext_atom_list = g_list_append (ext_atom_list, ext_atom); + + ext_atom = build_fiel_extension (fields); + if (ext_atom) + ext_atom_list = g_list_append (ext_atom_list, ext_atom); + + ext_atom = build_jp2x_extension (codec_data); + if (ext_atom) + ext_atom_list = g_list_append (ext_atom_list, ext_atom); + } else { GST_DEBUG_OBJECT (qtmux, "missing or invalid fourcc in jp2 caps"); goto refuse_caps; } - if (ext_atom) - ext_atom_list = g_list_prepend (ext_atom_list, ext_atom); } else if (strcmp (mimetype, "video/x-qt-part") == 0) { guint32 fourcc; diff --git a/gst/quicktime/gstqtmux.h b/gst/quicktime/gstqtmux.h index 5f941a2..92a2078 100644 --- a/gst/quicktime/gstqtmux.h +++ b/gst/quicktime/gstqtmux.h @@ -62,8 +62,22 @@ G_BEGIN_DECLS typedef struct _GstQTMux GstQTMux; typedef struct _GstQTMuxClass GstQTMuxClass; +typedef struct _GstQTPad GstQTPad; -typedef struct _GstQTPad +/* + * GstQTPadPrepareBufferFunc + * + * Receives a buffer (takes ref) and returns a new buffer that should + * replace the passed one. + * + * Useful for when the pad/datatype needs some manipulation before + * being muxed. (Originally added for image/x-jpc support, for which buffers + * need to be wrapped into a isom box) + */ +typedef GstBuffer * (*GstQTPadPrepareBufferFunc) (GstQTPad * pad, + GstBuffer * buf, GstQTMux * qtmux); + +struct _GstQTPad { GstCollectData collect; /* we extend the CollectData */ @@ -89,7 +103,10 @@ typedef struct _GstQTPad /* all the atom and chunk book-keeping is delegated here * unowned/uncounted reference, parent MOOV owns */ AtomTRAK *trak; -} GstQTPad; + + /* if nothing is set, it won't be called */ + GstQTPadPrepareBufferFunc prepare_buf_func; +}; typedef enum _GstQTMuxState { diff --git a/gst/quicktime/gstqtmuxmap.c b/gst/quicktime/gstqtmuxmap.c index a96e5b2..4c225a3 100644 --- a/gst/quicktime/gstqtmuxmap.c +++ b/gst/quicktime/gstqtmuxmap.c @@ -199,7 +199,8 @@ GstQTMuxFormatProp gst_qt_mux_format_list[] = { "MJ2", "GstMJ2Mux", GST_STATIC_CAPS ("video/mj2"), - GST_STATIC_CAPS ("image/x-j2c, " COMMON_VIDEO_CAPS), + GST_STATIC_CAPS ("image/x-j2c, " COMMON_VIDEO_CAPS "; " + "image/x-jpc, " COMMON_VIDEO_CAPS), GST_STATIC_CAPS (PCM_CAPS) } , -- 2.7.4