/* GStreamer CDXA sync strippper * Copyright (C) 2004 Ronald Bultje * * 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., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "gstcdxastrip.h" static void gst_cdxastrip_base_init (GstCDXAStripClass * klass); static void gst_cdxastrip_class_init (GstCDXAStripClass * klass); static void gst_cdxastrip_init (GstCDXAStrip * cdxastrip); static const GstEventMask *gst_cdxastrip_get_event_mask (GstPad * pad); static gboolean gst_cdxastrip_handle_src_event (GstPad * pad, GstEvent * event); static const GstFormat *gst_cdxastrip_get_src_formats (GstPad * pad); static const GstQueryType *gst_cdxastrip_get_src_query_types (GstPad * pad); static gboolean gst_cdxastrip_handle_src_query (GstPad * pad, GstQueryType type, GstFormat * format, gint64 * value); static void gst_cdxastrip_chain (GstPad * pad, GstData * data); static GstStateChangeReturn gst_cdxastrip_change_state (GstElement * element, GstStateChange transition); static GstStaticPadTemplate sink_template_factory = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/x-vcd") ); static GstStaticPadTemplate src_template_factory = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/mpeg, " "systemstream = (boolean) TRUE") ); static GstElementClass *parent_class = NULL; GType gst_cdxastrip_get_type (void) { static GType cdxastrip_type = 0; if (!cdxastrip_type) { static const GTypeInfo cdxastrip_info = { sizeof (GstCDXAStripClass), (GBaseInitFunc) gst_cdxastrip_base_init, NULL, (GClassInitFunc) gst_cdxastrip_class_init, NULL, NULL, sizeof (GstCDXAStrip), 0, (GInstanceInitFunc) gst_cdxastrip_init, }; cdxastrip_type = g_type_register_static (GST_TYPE_ELEMENT, "GstCDXAStrip", &cdxastrip_info, 0); } return cdxastrip_type; } static void gst_cdxastrip_base_init (GstCDXAStripClass * klass) { GstElementClass *element_class = GST_ELEMENT_CLASS (klass); static GstElementDetails gst_cdxastrip_details = GST_ELEMENT_DETAILS ("vcd parser", "Codec/Parser", "Strip (S)VCD stream from its syncheaders", "Ronald Bultje "); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&sink_template_factory)); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&src_template_factory)); gst_element_class_set_details (element_class, &gst_cdxastrip_details); } static void gst_cdxastrip_class_init (GstCDXAStripClass * klass) { GstElementClass *element_class = GST_ELEMENT_CLASS (klass); parent_class = g_type_class_ref (GST_TYPE_ELEMENT); element_class->change_state = gst_cdxastrip_change_state; } static void gst_cdxastrip_init (GstCDXAStrip * cdxastrip) { GST_OBJECT_FLAG_SET (cdxastrip, GST_ELEMENT_EVENT_AWARE); cdxastrip->sinkpad = gst_pad_new_from_template (gst_static_pad_template_get (&sink_template_factory), "sink"); gst_pad_set_chain_function (cdxastrip->sinkpad, gst_cdxastrip_chain); gst_element_add_pad (GST_ELEMENT (cdxastrip), cdxastrip->sinkpad); cdxastrip->srcpad = gst_pad_new_from_template (gst_static_pad_template_get (&src_template_factory), "src"); gst_pad_set_formats_function (cdxastrip->srcpad, gst_cdxastrip_get_src_formats); gst_pad_set_event_mask_function (cdxastrip->srcpad, gst_cdxastrip_get_event_mask); gst_pad_set_event_function (cdxastrip->srcpad, gst_cdxastrip_handle_src_event); gst_pad_set_query_type_function (cdxastrip->srcpad, gst_cdxastrip_get_src_query_types); gst_pad_set_query_function (cdxastrip->srcpad, gst_cdxastrip_handle_src_query); gst_element_add_pad (GST_ELEMENT (cdxastrip), cdxastrip->srcpad); } /* * Stuff. */ static const GstFormat * gst_cdxastrip_get_src_formats (GstPad * pad) { static const GstFormat formats[] = { GST_FORMAT_BYTES, 0 }; return formats; } static const GstQueryType * gst_cdxastrip_get_src_query_types (GstPad * pad) { static const GstQueryType types[] = { GST_QUERY_TOTAL, GST_QUERY_POSITION, 0 }; return types; } static gboolean gst_cdxastrip_handle_src_query (GstPad * pad, GstQueryType type, GstFormat * format, gint64 * value) { GstCDXAStrip *cdxa = GST_CDXASTRIP (gst_pad_get_parent (pad)); if (!gst_pad_query (GST_PAD_PEER (cdxa->sinkpad), type, format, value)) return FALSE; if (*format != GST_FORMAT_BYTES) return TRUE; switch (type) { case GST_QUERY_TOTAL: case GST_QUERY_POSITION:{ gint num, rest; num = *value / GST_CDXA_SECTOR_SIZE; rest = *value % GST_CDXA_SECTOR_SIZE; *value = num * GST_CDXA_DATA_SIZE; if (rest > GST_CDXA_HEADER_SIZE) { if (rest >= GST_CDXA_HEADER_SIZE + GST_CDXA_DATA_SIZE) *value += GST_CDXA_DATA_SIZE; else *value += rest - GST_CDXA_HEADER_SIZE; } break; } default: break; } return TRUE; } static const GstEventMask * gst_cdxastrip_get_event_mask (GstPad * pad) { static const GstEventMask masks[] = { {GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_KEY_UNIT}, {0,} }; return masks; } static gboolean gst_cdxastrip_handle_src_event (GstPad * pad, GstEvent * event) { GstCDXAStrip *cdxa = GST_CDXASTRIP (gst_pad_get_parent (pad)); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK: switch (GST_EVENT_SEEK_FORMAT (event)) { case GST_FORMAT_BYTES:{ GstEvent *new; gint64 off; gint num, rest; off = GST_EVENT_SEEK_OFFSET (event); num = off / GST_CDXA_DATA_SIZE; rest = off % GST_CDXA_DATA_SIZE; off = num * GST_CDXA_SECTOR_SIZE; if (rest > 0) off += rest + GST_CDXA_HEADER_SIZE; new = gst_event_new_seek (GST_EVENT_SEEK_TYPE (event), off); gst_event_unref (event); event = new; } default: break; } break; default: break; } return gst_pad_send_event (GST_PAD_PEER (cdxa->sinkpad), event); } /* * 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 */ GstBuffer * gst_cdxastrip_strip (GstBuffer * buf) { GstBuffer *sub; g_assert (GST_BUFFER_SIZE (buf) >= GST_CDXA_SECTOR_SIZE); /* Skip CDXA headers, only keep data. * FIXME: check sync, resync, ... */ sub = gst_buffer_create_sub (buf, GST_CDXA_HEADER_SIZE, GST_CDXA_DATA_SIZE); gst_buffer_unref (buf); return sub; } /* * -1 = no sync (discard buffer), * otherwise offset indicates syncpoint in buffer. */ gint gst_cdxastrip_sync (GstBuffer * buf) { guint size, off = 0; guint8 *data; for (size = GST_BUFFER_SIZE (buf), data = GST_BUFFER_DATA (buf); size >= 12; size--, data++, off++) { /* we could do a checksum check as well, but who cares... */ if (!memcmp (data, "\000\377\377\377\377\377\377\377\377\377\377\000", 12)) return off; } return -1; } /* * Do stuff. */ static void gst_cdxastrip_handle_event (GstCDXAStrip * cdxa, GstEvent * event) { switch (GST_EVENT_TYPE (event)) { case GST_EVENT_DISCONTINUOUS:{ gint64 new_off, off; if (gst_event_discont_get_value (event, GST_FORMAT_BYTES, &new_off)) { GstEvent *new; gint chunknum, rest; chunknum = new_off / GST_CDXA_SECTOR_SIZE; rest = new_off % GST_CDXA_SECTOR_SIZE; off = chunknum * GST_CDXA_DATA_SIZE; if (rest > GST_CDXA_HEADER_SIZE) { if (rest >= GST_CDXA_HEADER_SIZE + GST_CDXA_DATA_SIZE) off += GST_CDXA_DATA_SIZE; else off += rest - GST_CDXA_HEADER_SIZE; } new = gst_event_new_discontinuous (GST_EVENT_DISCONT_NEW_MEDIA (event), GST_FORMAT_BYTES, new_off, GST_FORMAT_UNDEFINED); gst_event_unref (event); event = new; } gst_pad_event_default (cdxa->sinkpad, event); break; } case GST_EVENT_FLUSH: if (cdxa->cache) { gst_buffer_unref (cdxa->cache); cdxa->cache = NULL; } /* fall-through */ default: gst_pad_event_default (cdxa->sinkpad, event); break; } } static void gst_cdxastrip_chain (GstPad * pad, GstData * data) { GstCDXAStrip *cdxa = GST_CDXASTRIP (gst_pad_get_parent (pad)); GstBuffer *buf, *sub; gint sync; if (GST_IS_EVENT (data)) { gst_cdxastrip_handle_event (cdxa, GST_EVENT (data)); return; } buf = GST_BUFFER (data); if (cdxa->cache) { buf = gst_buffer_join (cdxa->cache, buf); } cdxa->cache = NULL; while (buf && GST_BUFFER_SIZE (buf) >= GST_CDXA_SECTOR_SIZE) { /* sync */ sync = gst_cdxastrip_sync (buf); if (sync < 0) { gst_buffer_unref (buf); return; } sub = gst_buffer_create_sub (buf, sync, GST_BUFFER_SIZE (buf) - sync); gst_buffer_unref (buf); buf = sub; if (GST_BUFFER_SIZE (buf) < GST_CDXA_SECTOR_SIZE) break; /* one chunk */ sub = gst_cdxastrip_strip (gst_buffer_ref (buf)); gst_pad_push (cdxa->srcpad, GST_DATA (sub)); /* cache */ if (GST_BUFFER_SIZE (buf) != GST_CDXA_SECTOR_SIZE) { sub = gst_buffer_create_sub (buf, GST_CDXA_SECTOR_SIZE, GST_BUFFER_SIZE (buf) - GST_CDXA_SECTOR_SIZE); } else { sub = NULL; } gst_buffer_unref (buf); buf = sub; } cdxa->cache = buf; } static GstStateChangeReturn gst_cdxastrip_change_state (GstElement * element, GstStateChange transition) { GstCDXAStrip *cdxa = GST_CDXASTRIP (element); switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: if (cdxa->cache) { gst_buffer_unref (cdxa->cache); cdxa->cache = NULL; } break; default: break; } if (parent_class->change_state) return parent_class->change_state (element, transition); return GST_STATE_CHANGE_SUCCESS; }