diff options
Diffstat (limited to 'gst/matroska/matroska-mux.c')
-rw-r--r-- | gst/matroska/matroska-mux.c | 253 |
1 files changed, 212 insertions, 41 deletions
diff --git a/gst/matroska/matroska-mux.c b/gst/matroska/matroska-mux.c index 422f62a..30c0cdb 100644 --- a/gst/matroska/matroska-mux.c +++ b/gst/matroska/matroska-mux.c @@ -146,10 +146,10 @@ static GstStaticPadTemplate audiosink_templ = GST_STATIC_CAPS ("audio/mpeg, " "mpegversion = (int) 1, " "layer = (int) [ 1, 3 ], " - "stream-format = (string) { raw }, " COMMON_AUDIO_CAPS "; " "audio/mpeg, " "mpegversion = (int) { 2, 4 }, " + "stream-format = (string) raw, " COMMON_AUDIO_CAPS "; " "audio/x-ac3, " COMMON_AUDIO_CAPS "; " @@ -258,6 +258,9 @@ static gboolean kate_streamheader_to_codecdata (const GValue * streamheader, GstMatroskaTrackContext * context); static gboolean flac_streamheader_to_codecdata (const GValue * streamheader, GstMatroskaTrackContext * context); +static void +gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag, + gpointer data); static void gst_matroska_mux_add_interfaces (GType type) @@ -281,14 +284,13 @@ gst_matroska_mux_class_init (GstMatroskaMuxClass * klass) gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; - gst_element_class_add_pad_template (gstelement_class, - gst_static_pad_template_get (&videosink_templ)); - gst_element_class_add_pad_template (gstelement_class, - gst_static_pad_template_get (&audiosink_templ)); - gst_element_class_add_pad_template (gstelement_class, - gst_static_pad_template_get (&subtitlesink_templ)); - gst_element_class_add_pad_template (gstelement_class, - gst_static_pad_template_get (&src_templ)); + gst_element_class_add_static_pad_template (gstelement_class, + &videosink_templ); + gst_element_class_add_static_pad_template (gstelement_class, + &audiosink_templ); + gst_element_class_add_static_pad_template (gstelement_class, + &subtitlesink_templ); + gst_element_class_add_static_pad_template (gstelement_class, &src_templ); gst_element_class_set_details_simple (gstelement_class, "Matroska muxer", "Codec/Muxer", "Muxes video/audio/subtitle streams into a matroska stream", @@ -331,6 +333,103 @@ gst_matroska_mux_class_init (GstMatroskaMuxClass * klass) GST_DEBUG_FUNCPTR (gst_matroska_mux_release_pad); } +/** + * Start of pad option handler code + */ +#define DEFAULT_PAD_FRAME_DURATION TRUE +#define DEFAULT_PAD_FRAME_DURATION_VP8 FALSE + +enum +{ + PROP_PAD_0, + PROP_PAD_FRAME_DURATION +}; + +typedef struct +{ + GstPad parent; + gboolean frame_duration; + gboolean frame_duration_user; +} GstMatroskamuxPad; + +static void gst_matroskamux_pad_class_init (GstPadClass * klass); + +static GType +gst_matroskamux_pad_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) { + type = g_type_register_static_simple (GST_TYPE_PAD, + g_intern_static_string ("GstMatroskamuxPad"), sizeof (GstPadClass), + (GClassInitFunc) gst_matroskamux_pad_class_init, + sizeof (GstMatroskamuxPad), NULL, 0); + } + return type; +} + +#define GST_TYPE_MATROSKAMUX_PAD (gst_matroskamux_pad_get_type()) +#define GST_MATROSKAMUX_PAD(pad) (G_TYPE_CHECK_INSTANCE_CAST((pad),GST_TYPE_MATROSKAMUX_PAD,GstMatroskamuxPad)) +#define GST_MATROSKAMUX_PAD_CAST(pad) ((GstMatroskamuxPad *) pad) +#define GST_IS_MATROSKAMUX_PAD(pad) (G_TYPE_CHECK_INSTANCE_TYPE((pad),GST_TYPE_MATROSKAMUX_PAD)) + +static void +gst_matroskamux_pad_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstMatroskamuxPad *pad = GST_MATROSKAMUX_PAD (object); + + switch (prop_id) { + case PROP_PAD_FRAME_DURATION: + g_value_set_boolean (value, pad->frame_duration); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_matroskamux_pad_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstMatroskamuxPad *pad = GST_MATROSKAMUX_PAD (object); + + switch (prop_id) { + case PROP_PAD_FRAME_DURATION: + pad->frame_duration = g_value_get_boolean (value); + pad->frame_duration_user = TRUE; + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_matroskamux_pad_class_init (GstPadClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->set_property = gst_matroskamux_pad_set_property; + gobject_class->get_property = gst_matroskamux_pad_get_property; + + g_object_class_install_property (gobject_class, PROP_PAD_FRAME_DURATION, + g_param_spec_boolean ("frame-duration", "Frame duration", + "Default frame duration", DEFAULT_PAD_FRAME_DURATION, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_matroskamux_pad_init (GstMatroskamuxPad * pad) +{ + pad->frame_duration = DEFAULT_PAD_FRAME_DURATION; + pad->frame_duration_user = FALSE; +} + +/* + * End of pad option handler code + **/ /** * gst_matroska_mux_init: @@ -388,6 +487,8 @@ gst_matroska_mux_finalize (GObject * object) { GstMatroskaMux *mux = GST_MATROSKA_MUX (object); + gst_event_replace (&mux->force_key_unit_event, NULL); + gst_object_unref (mux->collect); gst_object_unref (mux->ebml_write); if (mux->writing_app) @@ -576,7 +677,7 @@ gst_matroska_mux_reset (GstElement * element) * @pad: Pad which received the event. * @event: Received event. * - * handle events - copied from oggmux without understanding + * handle events - copied from oggmux without understanding * * Returns: #TRUE on success. */ @@ -653,12 +754,29 @@ gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event) event = NULL; break; } - case GST_EVENT_NEWSEGMENT: - /* We don't support NEWSEGMENT events */ - ret = FALSE; - gst_event_unref (event); - event = NULL; + case GST_EVENT_NEWSEGMENT:{ + GstFormat format; + + gst_event_parse_new_segment (event, NULL, NULL, &format, NULL, NULL, + NULL); + if (format != GST_FORMAT_TIME) { + ret = FALSE; + gst_event_unref (event); + event = NULL; + } break; + } + case GST_EVENT_CUSTOM_DOWNSTREAM:{ + const GstStructure *structure; + + structure = gst_event_get_structure (event); + if (gst_structure_has_name (structure, "GstForceKeyUnit")) { + gst_event_replace (&mux->force_key_unit_event, NULL); + mux->force_key_unit_event = event; + event = NULL; + } + break; + } default: break; } @@ -729,7 +847,15 @@ gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps) videocontext->pixel_width = width; videocontext->pixel_height = height; - if (gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d) + + /* set vp8 defaults or let user override it */ + if (GST_MATROSKAMUX_PAD_CAST (pad)->frame_duration_user == FALSE + && (!strcmp (mimetype, "video/x-vp8"))) + GST_MATROSKAMUX_PAD_CAST (pad)->frame_duration = + DEFAULT_PAD_FRAME_DURATION_VP8; + + if (GST_MATROSKAMUX_PAD_CAST (pad)->frame_duration + && gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d) && fps_n > 0) { context->default_duration = gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n); @@ -774,15 +900,14 @@ skip_details: if (!strcmp (mimetype, "video/x-raw-yuv")) { context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED); gst_structure_get_fourcc (structure, "format", &videocontext->fourcc); - } else if (!strcmp (mimetype, "image/jpeg")) { - context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MJPEG); } else if (!strcmp (mimetype, "video/x-xvid") /* MS/VfW compatibility cases */ ||!strcmp (mimetype, "video/x-huffyuv") || !strcmp (mimetype, "video/x-divx") || !strcmp (mimetype, "video/x-dv") || !strcmp (mimetype, "video/x-h263") || !strcmp (mimetype, "video/x-msmpeg") - || !strcmp (mimetype, "video/x-wmv")) { + || !strcmp (mimetype, "video/x-wmv") + || !strcmp (mimetype, "image/jpeg")) { gst_riff_strf_vids *bih; gint size = sizeof (gst_riff_strf_vids); guint32 fourcc = 0; @@ -839,6 +964,8 @@ skip_details: fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '3'); } } + } else if (!strcmp (mimetype, "image/jpeg")) { + fourcc = GST_MAKE_FOURCC ('M', 'J', 'P', 'G'); } if (!fourcc) @@ -1821,7 +1948,7 @@ gst_matroska_mux_request_new_pad (GstElement * element, GstElementClass *klass = GST_ELEMENT_GET_CLASS (element); GstMatroskaMux *mux = GST_MATROSKA_MUX (element); GstMatroskaPad *collect_pad; - GstPad *newpad = NULL; + GstMatroskamuxPad *newpad; gchar *name = NULL; const gchar *pad_name = NULL; GstPadSetCapsFunction setcapsfunc = NULL; @@ -1875,11 +2002,14 @@ gst_matroska_mux_request_new_pad (GstElement * element, return NULL; } - newpad = gst_pad_new_from_template (templ, pad_name); + newpad = g_object_new (GST_TYPE_MATROSKAMUX_PAD, + "name", pad_name, "direction", templ->direction, "template", templ, NULL); g_free (name); + + gst_matroskamux_pad_init (newpad); collect_pad = (GstMatroskaPad *) - gst_collect_pads_add_pad_full (mux->collect, newpad, - sizeof (GstMatroskaPad), + gst_collect_pads_add_pad_full (mux->collect, GST_PAD (newpad), + sizeof (GstMatroskamuxPad), (GstCollectDataDestroyNotify) gst_matroska_pad_free); collect_pad->track = context; @@ -1894,19 +2024,19 @@ gst_matroska_mux_request_new_pad (GstElement * element, * This would allow (clean) transcoding of info from demuxer/streams * to another muxer */ mux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad); - gst_pad_set_event_function (newpad, + gst_pad_set_event_function (GST_PAD (newpad), GST_DEBUG_FUNCPTR (gst_matroska_mux_handle_sink_event)); - gst_pad_set_setcaps_function (newpad, setcapsfunc); - gst_pad_set_active (newpad, TRUE); - if (!gst_element_add_pad (element, newpad)) + gst_pad_set_setcaps_function (GST_PAD (newpad), setcapsfunc); + gst_pad_set_active (GST_PAD (newpad), TRUE); + if (!gst_element_add_pad (element, GST_PAD (newpad))) goto pad_add_failed; mux->num_streams++; GST_DEBUG_OBJECT (newpad, "Added new request pad"); - return newpad; + return GST_PAD (newpad); /* ERROR cases */ pad_add_failed: @@ -2117,6 +2247,27 @@ gst_matroska_mux_start (GstMatroskaMux * mux) gst_ebml_write_master_finish (ebml, master); } + if (mux->streamable) { + const GstTagList *tags; + + /* tags */ + tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (mux)); + + if (tags != NULL && !gst_tag_list_is_empty (tags)) { + guint64 master_tags, master_tag; + + GST_DEBUG ("Writing tags"); + + /* TODO: maybe limit via the TARGETS id by looking at the source pad */ + mux->tags_pos = ebml->pos; + master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS); + master_tag = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAG); + gst_tag_list_foreach (tags, gst_matroska_mux_write_simple_tag, ebml); + gst_ebml_write_master_finish (ebml, master_tag); + gst_ebml_write_master_finish (ebml, master_tags); + } + } + /* segment info */ mux->info_pos = ebml->pos; master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENTINFO); @@ -2180,6 +2331,10 @@ gst_matroska_mux_start (GstMatroskaMux * mux) child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKENTRY); gst_matroska_mux_track_header (mux, collect_pad->track); gst_ebml_write_master_finish (ebml, child); + /* some remaing pad/track setup */ + collect_pad->default_duration_scaled = + gst_util_uint64_scale (collect_pad->track->default_duration, + 1, mux->time_scale); } } gst_ebml_write_master_finish (ebml, master); @@ -2193,7 +2348,7 @@ gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag, gpointer data) { /* TODO: more sensible tag mappings */ - struct + static const struct { const gchar *matroska_tagname; const gchar *gstreamer_tagname; @@ -2411,7 +2566,7 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) * @mux: #GstMatroskaMux * @popped: True if at least one buffer was popped from #GstCollectPads * - * Find a pad with the oldest data + * Find a pad with the oldest data * (data from this pad should be written first). * * Returns: Selected pad. @@ -2450,6 +2605,10 @@ gst_matroska_mux_best_pad (GstMatroskaMux * mux, gboolean * popped) collect_pad->buffer = NULL; return NULL; } else { + GST_LOG_OBJECT (mux, "buffer ts %" GST_TIME_FORMAT " -> %" + GST_TIME_FORMAT " running time", + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (collect_pad->buffer)), + GST_TIME_ARGS (time)); collect_pad->buffer = gst_buffer_make_metadata_writable (collect_pad->buffer); GST_BUFFER_TIMESTAMP (collect_pad->buffer) = time; @@ -2543,7 +2702,7 @@ gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux, next_parse_offset = GST_READ_UINT32_BE (data + 5); - if (G_UNLIKELY (next_parse_offset == 0)) + if (G_UNLIKELY (next_parse_offset == 0 || next_parse_offset > size)) break; data += next_parse_offset; @@ -2620,10 +2779,12 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad) gint64 relative_timestamp64; guint64 block_duration; gboolean is_video_keyframe = FALSE; + GstMatroskamuxPad *pad; /* write data */ buf = collect_pad->buffer; collect_pad->buffer = NULL; + pad = GST_MATROSKAMUX_PAD_CAST (collect_pad->collect.pad); /* vorbis/theora headers are retrieved from caps and put in CodecPrivate */ if (collect_pad->track->xiph_headers_to_skip > 0) { @@ -2664,13 +2825,20 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad) } if (mux->cluster) { - /* start a new cluster at every keyframe or when we may be reaching the - * limit of the relative timestamp */ + /* start a new cluster at every keyframe, at every GstForceKeyUnit event, + * or when we may be reaching the limit of the relative timestamp */ if (mux->cluster_time + mux->max_cluster_duration < GST_BUFFER_TIMESTAMP (buf) - || is_video_keyframe) { + || is_video_keyframe || mux->force_key_unit_event) { if (!mux->streamable) gst_ebml_write_master_finish (ebml, mux->cluster); + + /* Forward the GstForceKeyUnit event after finishing the cluster */ + if (mux->force_key_unit_event) { + gst_pad_push_event (mux->srcpad, mux->force_key_unit_event); + mux->force_key_unit_event = NULL; + } + mux->prev_cluster_size = ebml->pos - mux->cluster_pos; mux->cluster_pos = ebml->pos; gst_ebml_write_set_cache (ebml, 0x20); @@ -2743,9 +2911,14 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad) /* Check if the duration differs from the default duration. */ write_duration = FALSE; - block_duration = GST_BUFFER_DURATION (buf); - if (GST_BUFFER_DURATION_IS_VALID (buf)) { - if (block_duration != collect_pad->track->default_duration) { + block_duration = 0; + if (pad->frame_duration && GST_BUFFER_DURATION_IS_VALID (buf)) { + block_duration = gst_util_uint64_scale (GST_BUFFER_DURATION (buf), + 1, mux->time_scale); + + /* small difference should be ok. */ + if (block_duration > collect_pad->default_duration_scaled + 1 || + block_duration < collect_pad->default_duration_scaled - 1) { write_duration = TRUE; } } @@ -2786,10 +2959,8 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad) hdr = gst_matroska_mux_create_buffer_header (collect_pad->track, relative_timestamp, 0); - if (write_duration) { - gst_ebml_write_uint (ebml, GST_MATROSKA_ID_BLOCKDURATION, - gst_util_uint64_scale (block_duration, 1, mux->time_scale)); - } + if (write_duration) + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_BLOCKDURATION, block_duration); gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_BLOCK, GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr)); gst_ebml_write_buffer (ebml, hdr); |