summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Hervey <edward@centricular.com>2018-02-07 11:00:18 +0100
committerEdward Hervey <bilboed@bilboed.com>2018-03-09 12:11:23 +0100
commit60414aae16668b7f69036126f609baf41a4b343a (patch)
tree24fd7f46b2ffd37970cedac5155743e0aec362ce
parent99456dd41cdf5749c8205c51519f7e3f6ae88d4d (diff)
isomp4: qtmux: Add Closed Caption support
Supports CEA 608 and CEA 708 CC streams (as byte pairs) https://bugzilla.gnome.org/show_bug.cgi?id=606643
-rw-r--r--gst/isomp4/atoms.c59
-rw-r--r--gst/isomp4/atoms.h6
-rw-r--r--gst/isomp4/gstqtmux.c115
-rw-r--r--gst/isomp4/gstqtmux.h3
-rw-r--r--gst/isomp4/gstqtmuxmap.c16
-rw-r--r--gst/isomp4/gstqtmuxmap.h1
6 files changed, 190 insertions, 10 deletions
diff --git a/gst/isomp4/atoms.c b/gst/isomp4/atoms.c
index aaf34885a..fa40947c2 100644
--- a/gst/isomp4/atoms.c
+++ b/gst/isomp4/atoms.c
@@ -692,6 +692,7 @@ atom_stsd_remove_entries (AtomSTSD * stsd)
case TIMECODE:
sample_entry_tmcd_free ((SampleTableEntryTMCD *) se);
break;
+ case CLOSEDCAPTION:
default:
/* best possible cleanup */
atom_sample_entry_free (se);
@@ -2229,6 +2230,20 @@ sample_entry_tmcd_copy_data (SampleTableEntryTMCD * tmcd, guint8 ** buffer,
return *offset - original_offset;
}
+static guint64
+sample_entry_generic_copy_data (SampleTableEntry * entry, guint8 ** buffer,
+ guint64 * size, guint64 * offset)
+{
+ guint64 original_offset = *offset;
+
+ if (!atom_sample_entry_copy_data (entry, buffer, size, offset)) {
+ return 0;
+ }
+
+ atom_write_size (buffer, size, offset, original_offset);
+ return *offset - original_offset;
+}
+
guint64
atom_stsz_copy_data (AtomSTSZ * stsz, guint8 ** buffer, guint64 * size,
guint64 * offset)
@@ -2462,6 +2477,11 @@ atom_stsd_copy_data (AtomSTSD * stsd, guint8 ** buffer, guint64 * size,
walker->data, buffer, size, offset)) {
return 0;
}
+ } else if (se->kind == CLOSEDCAPTION) {
+ if (!sample_entry_generic_copy_data ((SampleTableEntry *)
+ walker->data, buffer, size, offset)) {
+ return 0;
+ }
} else {
if (!atom_hint_sample_entry_copy_data (
(AtomHintSampleEntry *) walker->data, buffer, size, offset)) {
@@ -4082,6 +4102,45 @@ atom_trak_set_timecode_type (AtomTRAK * trak, AtomsContext * context,
return ste;
}
+SampleTableEntry *
+atom_trak_set_caption_type (AtomTRAK * trak, AtomsContext * context,
+ guint32 caption_type)
+{
+ SampleTableEntry *ste;
+ AtomGMHD *gmhd = trak->mdia.minf.gmhd;
+ AtomSTSD *stsd = &trak->mdia.minf.stbl.stsd;
+
+ if (context->flavor != ATOMS_TREE_FLAVOR_MOV) {
+ return NULL;
+ }
+
+ /* FIXME : Set the timescale to the same timescale as the video trak ! */
+ trak->mdia.mdhd.time_info.timescale = 30000;
+ trak->mdia.hdlr.component_type = FOURCC_mhlr;
+ trak->mdia.hdlr.handler_type = FOURCC_clcp;
+ g_free (trak->mdia.hdlr.name);
+ trak->mdia.hdlr.name = g_strdup ("Closed Caption Media Handler");
+
+ ste = g_new0 (SampleTableEntry, 1);
+ atom_sample_entry_init (ste, caption_type);
+ ste->kind = CLOSEDCAPTION;
+ ste->data_reference_index = 1;
+ stsd->entries = g_list_prepend (stsd->entries, ste);
+ stsd->n_entries++;
+
+ gmhd = atom_gmhd_new ();
+ gmhd->gmin.graphics_mode = 0x0040;
+ gmhd->gmin.opcolor[0] = 0x8000;
+ gmhd->gmin.opcolor[1] = 0x8000;
+ gmhd->gmin.opcolor[2] = 0x8000;
+
+ trak->mdia.minf.gmhd = gmhd;
+ trak->is_video = FALSE;
+ trak->is_h264 = FALSE;
+
+ return ste;
+}
+
static AtomInfo *
build_pasp_extension (gint par_width, gint par_height)
{
diff --git a/gst/isomp4/atoms.h b/gst/isomp4/atoms.h
index 963c4b99a..eb54a69d9 100644
--- a/gst/isomp4/atoms.h
+++ b/gst/isomp4/atoms.h
@@ -386,7 +386,8 @@ typedef enum _SampleEntryKind
AUDIO,
VIDEO,
SUBTITLE,
- TIMECODE
+ TIMECODE,
+ CLOSEDCAPTION
} SampleEntryKind;
typedef struct _SampleTableEntry
@@ -999,7 +1000,6 @@ guint64 atom_mfra_copy_data (AtomMFRA *mfra, guint8 **buffer, guint64
/* media sample description related helpers */
-
typedef struct
{
guint16 version;
@@ -1056,6 +1056,8 @@ SampleTableEntryTX3G * atom_trak_set_subtitle_type (AtomTRAK * trak, AtomsContex
SampleTableEntryTMCD *
atom_trak_set_timecode_type (AtomTRAK * trak, AtomsContext * context, guint trak_timescale, GstVideoTimeCode * tc);
+SampleTableEntry * atom_trak_set_caption_type (AtomTRAK *trak, AtomsContext *context, guint32 caption_type);
+
void atom_trak_update_bitrates (AtomTRAK * trak, guint32 avg_bitrate,
guint32 max_bitrate);
diff --git a/gst/isomp4/gstqtmux.c b/gst/isomp4/gstqtmux.c
index 0dee96e88..8808203ac 100644
--- a/gst/isomp4/gstqtmux.c
+++ b/gst/isomp4/gstqtmux.c
@@ -449,7 +449,8 @@ gst_qt_mux_base_init (gpointer g_class)
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
GstQTMuxClass *klass = (GstQTMuxClass *) g_class;
GstQTMuxClassParams *params;
- GstPadTemplate *videosinktempl, *audiosinktempl, *subtitlesinktempl;
+ GstPadTemplate *videosinktempl, *audiosinktempl, *subtitlesinktempl,
+ *captionsinktempl;
GstPadTemplate *srctempl;
gchar *longname, *description;
@@ -494,6 +495,13 @@ gst_qt_mux_base_init (gpointer g_class)
gst_element_class_add_pad_template (element_class, subtitlesinktempl);
}
+ if (params->caption_sink_caps) {
+ captionsinktempl = gst_pad_template_new_with_gtype ("caption_%u",
+ GST_PAD_SINK, GST_PAD_REQUEST, params->caption_sink_caps,
+ GST_TYPE_QT_MUX_PAD);
+ gst_element_class_add_pad_template (element_class, captionsinktempl);
+ }
+
klass->format = params->prop->format;
}
@@ -855,6 +863,40 @@ gst_qt_mux_prepare_jpc_buffer (GstQTPad * qtpad, GstBuffer * buf,
}
static GstBuffer *
+gst_qt_mux_prepare_cdat_buffer (GstQTPad * qtpad, GstBuffer * buf,
+ GstQTMux * qtmux)
+{
+ GstBuffer *newbuf;
+ GstMapInfo map, inmap;
+ gsize size;
+
+ GST_LOG_OBJECT (qtmux, "Preparing cdat buffer");
+
+ if (buf == NULL)
+ return NULL;
+
+ size = gst_buffer_get_size (buf);
+ GST_LOG_OBJECT (qtmux, "Preparing buffer of size %" G_GSIZE_FORMAT, size + 8);
+ newbuf = gst_buffer_new_and_alloc (size + 8);
+
+ /* Let's copy over all metadata and not the memory */
+ gst_buffer_copy_into (newbuf, buf, GST_BUFFER_COPY_METADATA, 0, size);
+
+ gst_buffer_map (newbuf, &map, GST_MAP_WRITE);
+ gst_buffer_map (buf, &inmap, GST_MAP_READ);
+
+ GST_WRITE_UINT32_BE (map.data, map.size);
+ GST_WRITE_UINT32_LE (map.data + 4, FOURCC_cdat);
+ memcpy (map.data + 8, inmap.data, inmap.size);
+
+ gst_buffer_unmap (newbuf, &map);
+ gst_buffer_unmap (buf, &inmap);
+ gst_buffer_unref (buf);
+
+ return newbuf;
+}
+
+static GstBuffer *
gst_qt_mux_prepare_tx3g_buffer (GstQTPad * qtpad, GstBuffer * buf,
GstQTMux * qtmux)
{
@@ -4486,8 +4528,10 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
gst_qt_mux_register_and_push_sample (qtmux, pad, empty_buf, FALSE, 1,
last_dts + scaled_duration, empty_duration_scaled,
empty_size, chunk_offset, sync, TRUE, 0);
- } else {
- /* our only case currently is tx3g subtitles, so there is no reason to fill this yet */
+ } else if (pad->fourcc != FOURCC_c608 && pad->fourcc != FOURCC_c708) {
+ /* This assert is kept here to make sure implementors of new
+ * sparse input format decide whether there needs to be special
+ * gap handling or not */
g_assert_not_reached ();
GST_WARNING_OBJECT (qtmux,
"no empty buffer creation function found for pad %s",
@@ -5754,6 +5798,55 @@ refuse_caps:
}
static gboolean
+gst_qt_mux_caption_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
+{
+ GstPad *pad = qtpad->collect.pad;
+ GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
+ GstStructure *structure;
+ guint32 fourcc_entry;
+
+ if (qtpad->fourcc)
+ return gst_qt_mux_can_renegotiate (qtmux, pad, caps);
+
+ GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
+ GST_DEBUG_PAD_NAME (pad), caps);
+
+ /* captions default */
+ qtpad->is_out_of_order = FALSE;
+ qtpad->sync = FALSE;
+ qtpad->sparse = TRUE;
+ /* Closed caption data is within a cdat atom */
+ qtpad->prepare_buf_func = gst_qt_mux_prepare_cdat_buffer;
+
+ structure = gst_caps_get_structure (caps, 0);
+
+
+ if (gst_structure_has_name (structure, "closedcaption/x-cea-608")) {
+ fourcc_entry = FOURCC_c608;
+ } else if (gst_structure_has_name (structure, "closedcaption/x-cea-708")) {
+ fourcc_entry = FOURCC_c708;
+ } else
+ goto refuse_caps;
+
+ qtpad->fourcc = fourcc_entry;
+ qtpad->trak_ste =
+ (SampleTableEntry *) atom_trak_set_caption_type (qtpad->trak,
+ qtmux->context, fourcc_entry);
+
+ gst_object_unref (qtmux);
+ return TRUE;
+
+ /* ERRORS */
+refuse_caps:
+ {
+ GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
+ GST_PAD_NAME (pad), caps);
+ gst_object_unref (qtmux);
+ return FALSE;
+ }
+}
+
+static gboolean
gst_qt_mux_sink_event (GstCollectPads * pads, GstCollectData * data,
GstEvent * event, gpointer user_data)
{
@@ -5924,6 +6017,14 @@ gst_qt_mux_request_new_pad (GstElement * element,
name = g_strdup_printf ("subtitle_%u", qtmux->subtitle_pads++);
}
lock = FALSE;
+ } else if (templ == gst_element_class_get_pad_template (klass, "caption_%u")) {
+ setcaps_func = gst_qt_mux_caption_sink_set_caps;
+ if (req_name != NULL && sscanf (req_name, "caption_%u", &pad_id) == 1) {
+ name = g_strdup (req_name);
+ } else {
+ name = g_strdup_printf ("caption_%u", qtmux->caption_pads++);
+ }
+ lock = FALSE;
} else
goto wrong_template;
@@ -6218,7 +6319,7 @@ gst_qt_mux_register (GstPlugin * plugin)
while (TRUE) {
GstQTMuxFormatProp *prop;
- GstCaps *subtitle_caps;
+ GstCaps *subtitle_caps, *caption_caps;
prop = &gst_qt_mux_format_list[i];
format = prop->format;
@@ -6237,6 +6338,12 @@ gst_qt_mux_register (GstPlugin * plugin)
} else {
gst_caps_unref (subtitle_caps);
}
+ caption_caps = gst_static_caps_get (&prop->caption_sink_caps);
+ if (!gst_caps_is_equal (caption_caps, GST_CAPS_NONE)) {
+ params->caption_sink_caps = caption_caps;
+ } else {
+ gst_caps_unref (caption_caps);
+ }
/* create the type now */
type = g_type_register_static (GST_TYPE_ELEMENT, prop->type_name, &typeinfo,
diff --git a/gst/isomp4/gstqtmux.h b/gst/isomp4/gstqtmux.h
index c9ef21ddf..d8cb1dbfa 100644
--- a/gst/isomp4/gstqtmux.h
+++ b/gst/isomp4/gstqtmux.h
@@ -290,7 +290,7 @@ struct _GstQTMux
gboolean reserved_prefill;
/* for request pad naming */
- guint video_pads, audio_pads, subtitle_pads;
+ guint video_pads, audio_pads, subtitle_pads, caption_pads;
};
struct _GstQTMuxClass
@@ -308,6 +308,7 @@ typedef struct _GstQTMuxClassParams
GstCaps *video_sink_caps;
GstCaps *audio_sink_caps;
GstCaps *subtitle_sink_caps;
+ GstCaps *caption_sink_caps;
} GstQTMuxClassParams;
#define GST_QT_MUX_PARAMS_QDATA g_quark_from_static_string("qt-mux-params")
diff --git a/gst/isomp4/gstqtmuxmap.c b/gst/isomp4/gstqtmuxmap.c
index 4e34e137f..5a19ea9cf 100644
--- a/gst/isomp4/gstqtmuxmap.c
+++ b/gst/isomp4/gstqtmuxmap.c
@@ -166,6 +166,11 @@
"text/x-raw, " \
"format=(string)utf8"
+#define CEA608_CAPS \
+ "closedcaption/x-cea-608"
+#define CEA708_CAPS \
+ "closedcaption/x-cea-708"
+
/* FIXME 0.11 - take a look at bugs #580005 and #340375 */
GstQTMuxFormatProp gst_qt_mux_format_list[] = {
/* original QuickTime format; see Apple site (e.g. qtff.pdf) */
@@ -209,7 +214,8 @@ GstQTMuxFormatProp gst_qt_mux_format_list[] = {
"audio/x-alaw, " COMMON_AUDIO_CAPS (2, MAX) "; "
"audio/x-mulaw, " COMMON_AUDIO_CAPS (2, MAX) "; "
AMR_CAPS " ; " ALAC_CAPS " ; " OPUS_CAPS),
- GST_STATIC_CAPS (TEXT_UTF8)}
+ GST_STATIC_CAPS (TEXT_UTF8),
+ GST_STATIC_CAPS (CEA608_CAPS "; " CEA708_CAPS)}
,
/* ISO 14496-14: mp42 as ISO base media extension
* (supersedes original ISO 144996-1 mp41) */
@@ -224,7 +230,8 @@ GstQTMuxFormatProp gst_qt_mux_format_list[] = {
"video/x-mp4-part," COMMON_VIDEO_CAPS),
GST_STATIC_CAPS (MP123_CAPS "; "
AAC_CAPS " ; " AC3_CAPS " ; " ALAC_CAPS " ; " OPUS_CAPS),
- GST_STATIC_CAPS (TEXT_UTF8)}
+ GST_STATIC_CAPS (TEXT_UTF8),
+ GST_STATIC_CAPS_NONE}
,
/* Microsoft Smooth Streaming fmp4/isml */
/* TODO add WMV/WMA support */
@@ -237,6 +244,7 @@ GstQTMuxFormatProp gst_qt_mux_format_list[] = {
GST_STATIC_CAPS ("video/quicktime, variant = (string) iso-fragmented"),
GST_STATIC_CAPS (MPEG4V_CAPS "; " H264_CAPS),
GST_STATIC_CAPS (MP3_CAPS "; " AAC_CAPS),
+ GST_STATIC_CAPS_NONE,
GST_STATIC_CAPS_NONE}
,
/* 3GPP Technical Specification 26.244 V7.3.0
@@ -250,7 +258,8 @@ GstQTMuxFormatProp gst_qt_mux_format_list[] = {
GST_STATIC_CAPS ("video/quicktime, variant = (string) 3gpp"),
GST_STATIC_CAPS (H263_CAPS "; " MPEG4V_CAPS "; " H264_CAPS),
GST_STATIC_CAPS (AMR_CAPS "; " MP3_CAPS "; " AAC_CAPS "; " AC3_CAPS),
- GST_STATIC_CAPS (TEXT_UTF8)}
+ GST_STATIC_CAPS (TEXT_UTF8),
+ GST_STATIC_CAPS_NONE}
,
/* ISO 15444-3: Motion-JPEG-2000 (also ISO base media extension) */
{
@@ -263,6 +272,7 @@ GstQTMuxFormatProp gst_qt_mux_format_list[] = {
GST_STATIC_CAPS ("image/x-j2c, " COMMON_VIDEO_CAPS "; "
"image/x-jpc, " COMMON_VIDEO_CAPS),
GST_STATIC_CAPS (PCM_CAPS),
+ GST_STATIC_CAPS_NONE,
GST_STATIC_CAPS_NONE}
,
{
diff --git a/gst/isomp4/gstqtmuxmap.h b/gst/isomp4/gstqtmuxmap.h
index e578a3674..f0bae16cb 100644
--- a/gst/isomp4/gstqtmuxmap.h
+++ b/gst/isomp4/gstqtmuxmap.h
@@ -70,6 +70,7 @@ typedef struct _GstQTMuxFormatProp
GstStaticCaps video_sink_caps;
GstStaticCaps audio_sink_caps;
GstStaticCaps subtitle_sink_caps;
+ GstStaticCaps caption_sink_caps;
} GstQTMuxFormatProp;
extern GstQTMuxFormatProp gst_qt_mux_format_list[];