/* GStreamer CDXA sync strippper / VCD parser * Copyright (C) 2004 Ronald Bultje * Copyright (C) 2008 Tim-Philipp Müller * * 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 #endif #include #include "gstvcdparse.h" GST_DEBUG_CATEGORY_EXTERN (vcdparse_debug); #define GST_CAT_DEFAULT vcdparse_debug static gboolean gst_vcd_parse_sink_event (GstPad * pad, GstEvent * event); static gboolean gst_vcd_parse_src_event (GstPad * pad, GstEvent * event); static gboolean gst_vcd_parse_src_query (GstPad * pad, GstQuery * query); static GstFlowReturn gst_vcd_parse_chain (GstPad * pad, GstBuffer * buf); static GstStateChangeReturn gst_vcd_parse_change_state (GstElement * element, GstStateChange transition); static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/x-vcd") ); static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/mpeg, systemstream = (boolean) TRUE") ); GST_BOILERPLATE (GstVcdParse, gst_vcd_parse, GstElement, GST_TYPE_ELEMENT); static void gst_vcd_parse_base_init (gpointer klass) { GstElementClass *element_class = GST_ELEMENT_CLASS (klass); gst_element_class_add_static_pad_template (element_class, &sink_factory); gst_element_class_add_static_pad_template (element_class, &src_factory); gst_element_class_set_static_metadata (element_class, "(S)VCD stream parser", "Codec/Parser", "Strip (S)VCD stream from its sync headers", "Tim-Philipp Müller , " "Ronald Bultje "); } static void gst_vcd_parse_class_init (GstVcdParseClass * klass) { GstElementClass *element_class = GST_ELEMENT_CLASS (klass); element_class->change_state = GST_DEBUG_FUNCPTR (gst_vcd_parse_change_state); } static void gst_vcd_parse_init (GstVcdParse * vcd, GstVcdParseClass * klass) { vcd->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink"); gst_pad_set_chain_function (vcd->sinkpad, GST_DEBUG_FUNCPTR (gst_vcd_parse_chain)); gst_pad_set_event_function (vcd->sinkpad, GST_DEBUG_FUNCPTR (gst_vcd_parse_sink_event)); gst_element_add_pad (GST_ELEMENT (vcd), vcd->sinkpad); vcd->srcpad = gst_pad_new_from_static_template (&src_factory, "src"); gst_pad_set_event_function (vcd->srcpad, GST_DEBUG_FUNCPTR (gst_vcd_parse_src_event)); gst_pad_set_query_function (vcd->srcpad, GST_DEBUG_FUNCPTR (gst_vcd_parse_src_query)); gst_pad_use_fixed_caps (vcd->srcpad); gst_pad_set_caps (vcd->srcpad, gst_static_pad_template_get_caps (&src_factory)); gst_element_add_pad (GST_ELEMENT (vcd), vcd->srcpad); } /* These conversion functions assume there's no junk between sectors */ static gint64 gst_vcd_parse_get_out_offset (gint64 in_offset) { gint64 out_offset, chunknum, rest; if (in_offset == -1) return -1; if (G_UNLIKELY (in_offset < -1)) { GST_WARNING ("unexpected/invalid in_offset %" G_GINT64_FORMAT, in_offset); return in_offset; } chunknum = in_offset / GST_CDXA_SECTOR_SIZE; rest = in_offset % GST_CDXA_SECTOR_SIZE; out_offset = chunknum * GST_CDXA_DATA_SIZE; if (rest > GST_CDXA_HEADER_SIZE) { if (rest >= GST_CDXA_HEADER_SIZE + GST_CDXA_DATA_SIZE) out_offset += GST_CDXA_DATA_SIZE; else out_offset += rest - GST_CDXA_HEADER_SIZE; } GST_LOG ("transformed in_offset %" G_GINT64_FORMAT " to out_offset %" G_GINT64_FORMAT, in_offset, out_offset); return out_offset; } static gint64 gst_vcd_parse_get_in_offset (gint64 out_offset) { gint64 in_offset, chunknum, rest; if (out_offset == -1) return -1; if (G_UNLIKELY (out_offset < -1)) { GST_WARNING ("unexpected/invalid out_offset %" G_GINT64_FORMAT, out_offset); return out_offset; } chunknum = out_offset / GST_CDXA_DATA_SIZE; rest = out_offset % GST_CDXA_DATA_SIZE; in_offset = chunknum * GST_CDXA_SECTOR_SIZE; if (rest > 0) in_offset += GST_CDXA_HEADER_SIZE + rest; GST_LOG ("transformed out_offset %" G_GINT64_FORMAT " to in_offset %" G_GINT64_FORMAT, out_offset, in_offset); return in_offset; } static gboolean gst_vcd_parse_src_query (GstPad * pad, GstQuery * query) { GstVcdParse *vcd = GST_VCD_PARSE (gst_pad_get_parent (pad)); gboolean res = FALSE; switch (GST_QUERY_TYPE (query)) { case GST_QUERY_DURATION:{ GstFormat format; gint64 dur; /* first try upstream */ if (!gst_pad_query_default (pad, query)) break; /* we can only handle BYTES */ gst_query_parse_duration (query, &format, &dur); if (format != GST_FORMAT_BYTES) break; gst_query_set_duration (query, GST_FORMAT_BYTES, gst_vcd_parse_get_out_offset (dur)); res = TRUE; break; } case GST_QUERY_POSITION:{ GstFormat format; gint64 pos; /* first try upstream */ if (!gst_pad_query_default (pad, query)) break; /* we can only handle BYTES */ gst_query_parse_position (query, &format, &pos); if (format != GST_FORMAT_BYTES) break; gst_query_set_position (query, GST_FORMAT_BYTES, gst_vcd_parse_get_out_offset (pos)); res = TRUE; break; } default: res = gst_pad_query_default (pad, query); break; } gst_object_unref (vcd); return res; } static gboolean gst_vcd_parse_sink_event (GstPad * pad, GstEvent * event) { GstVcdParse *vcd = GST_VCD_PARSE (gst_pad_get_parent (pad)); gboolean res; switch (GST_EVENT_TYPE (event)) { case GST_EVENT_NEWSEGMENT:{ GstFormat format; gboolean update; gdouble rate, applied_rate; gint64 start, stop, position; gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, &format, &start, &stop, &position); if (format == GST_FORMAT_BYTES) { gst_event_unref (event); event = gst_event_new_new_segment_full (update, rate, applied_rate, GST_FORMAT_BYTES, gst_vcd_parse_get_out_offset (start), gst_vcd_parse_get_out_offset (stop), position); } else { GST_WARNING_OBJECT (vcd, "newsegment event in non-byte format"); } res = gst_pad_event_default (pad, event); break; } case GST_EVENT_FLUSH_START: gst_adapter_clear (vcd->adapter); /* fall through */ default: res = gst_pad_event_default (pad, event); break; } gst_object_unref (vcd); return res; } static gboolean gst_vcd_parse_src_event (GstPad * pad, GstEvent * event) { GstVcdParse *vcd = GST_VCD_PARSE (gst_pad_get_parent (pad)); gboolean res; switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK:{ GstSeekType start_type, stop_type; GstSeekFlags flags; GstFormat format; gdouble rate; gint64 start, stop; gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start, &stop_type, &stop); if (format == GST_FORMAT_BYTES) { gst_event_unref (event); if (start_type != GST_SEEK_TYPE_NONE) start = gst_vcd_parse_get_in_offset (start); if (stop_type != GST_SEEK_TYPE_NONE) stop = gst_vcd_parse_get_in_offset (stop); event = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, start_type, start, stop_type, stop); } else { GST_WARNING_OBJECT (vcd, "seek event in non-byte format"); } res = gst_pad_event_default (pad, event); break; } default: res = gst_pad_event_default (pad, event); break; } gst_object_unref (vcd); return res; } /* -1 = no sync (discard buffer), * otherwise offset indicates sync point in buffer */ static gint gst_vcd_parse_sync (const guint8 * data, guint size) { const guint8 sync_marker[12] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 }; guint off = 0; while (size >= 12) { if (memcmp (data, sync_marker, 12) == 0) return off; --size; ++data; ++off; } return -1; } static GstFlowReturn gst_vcd_parse_chain (GstPad * pad, GstBuffer * buf) { GstVcdParse *vcd = GST_VCD_PARSE (GST_PAD_PARENT (pad)); GstFlowReturn flow = GST_FLOW_OK; gst_adapter_push (vcd->adapter, buf); buf = NULL; while (gst_adapter_available (vcd->adapter) >= GST_CDXA_SECTOR_SIZE) { const guint8 *data; guint8 header[4 + 8]; gint sync_offset; /* find sync (we could peek any size though really) */ data = gst_adapter_peek (vcd->adapter, GST_CDXA_SECTOR_SIZE); sync_offset = gst_vcd_parse_sync (data, GST_CDXA_SECTOR_SIZE); GST_LOG_OBJECT (vcd, "sync offset = %d", sync_offset); if (sync_offset < 0) { gst_adapter_flush (vcd->adapter, GST_CDXA_SECTOR_SIZE - 12); continue; /* try again */ } gst_adapter_flush (vcd->adapter, sync_offset); if (gst_adapter_available (vcd->adapter) < GST_CDXA_SECTOR_SIZE) { GST_LOG_OBJECT (vcd, "not enough data in adapter, waiting for more"); break; } GST_LOG_OBJECT (vcd, "have full sector"); /* have one sector: a sector is 2352 bytes long and is composed of: * * +-------------------------------------------------------+ * ! sync ! header ! subheader ! data ... ! edc ! * ! 12 bytes ! 4 bytes ! 8 bytes ! 2324 bytes ! 4 bytes ! * +-------------------------------------------------------+ * * We strip the data out of it and send it to the srcpad. * * sync : 00 FF FF FF FF FF FF FF FF FF FF 00 * header : hour minute second mode * sub-header : track channel sub_mode coding repeat (4 bytes) * edc : checksum */ /* Skip CDXA header and edc footer, only keep data in the middle */ gst_adapter_copy (vcd->adapter, header, 12, sizeof (header)); gst_adapter_flush (vcd->adapter, GST_CDXA_HEADER_SIZE); buf = gst_adapter_take_buffer (vcd->adapter, GST_CDXA_DATA_SIZE); gst_adapter_flush (vcd->adapter, 4); /* we could probably do something clever to keep track of buffer offsets */ buf = gst_buffer_make_metadata_writable (buf); GST_BUFFER_OFFSET (buf) = GST_BUFFER_OFFSET_NONE; GST_BUFFER_TIMESTAMP (buf) = GST_CLOCK_TIME_NONE; gst_buffer_set_caps (buf, GST_PAD_CAPS (vcd->srcpad)); flow = gst_pad_push (vcd->srcpad, buf); buf = NULL; if (G_UNLIKELY (flow != GST_FLOW_OK)) { GST_DEBUG_OBJECT (vcd, "flow: %s", gst_flow_get_name (flow)); break; } } return flow; } static GstStateChangeReturn gst_vcd_parse_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn res = GST_STATE_CHANGE_SUCCESS; GstVcdParse *vcd = GST_VCD_PARSE (element); switch (transition) { case GST_STATE_CHANGE_READY_TO_PAUSED: vcd->adapter = gst_adapter_new (); break; default: break; } res = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: case GST_STATE_CHANGE_READY_TO_NULL: if (vcd->adapter) { g_object_unref (vcd->adapter); vcd->adapter = NULL; } break; default: break; } return res; }