codecparser: mpeg4 type error
[profile/ivi/gst-plugins-bad.git] / gst / cdxaparse / gstvcdparse.c
1 /* GStreamer CDXA sync strippper / VCD parser
2  * Copyright (C) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net>
3  * Copyright (C) 2008 Tim-Philipp Müller <tim centricular net>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <string.h>
26
27 #include "gstvcdparse.h"
28
29 GST_DEBUG_CATEGORY_EXTERN (vcdparse_debug);
30 #define GST_CAT_DEFAULT vcdparse_debug
31
32 static gboolean gst_vcd_parse_sink_event (GstPad * pad, GstEvent * event);
33 static gboolean gst_vcd_parse_src_event (GstPad * pad, GstEvent * event);
34 static gboolean gst_vcd_parse_src_query (GstPad * pad, GstQuery * query);
35 static GstFlowReturn gst_vcd_parse_chain (GstPad * pad, GstBuffer * buf);
36 static GstStateChangeReturn gst_vcd_parse_change_state (GstElement * element,
37     GstStateChange transition);
38
39 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
40     GST_PAD_SINK,
41     GST_PAD_ALWAYS,
42     GST_STATIC_CAPS ("video/x-vcd")
43     );
44
45 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
46     GST_PAD_SRC,
47     GST_PAD_ALWAYS,
48     GST_STATIC_CAPS ("video/mpeg, systemstream = (boolean) TRUE")
49     );
50
51 GST_BOILERPLATE (GstVcdParse, gst_vcd_parse, GstElement, GST_TYPE_ELEMENT);
52
53 static void
54 gst_vcd_parse_base_init (gpointer klass)
55 {
56   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
57
58   gst_element_class_add_static_pad_template (element_class, &sink_factory);
59   gst_element_class_add_static_pad_template (element_class, &src_factory);
60
61   gst_element_class_set_details_simple (element_class, "(S)VCD stream parser",
62       "Codec/Parser", "Strip (S)VCD stream from its sync headers",
63       "Tim-Philipp Müller <tim centricular net>, "
64       "Ronald Bultje <rbultje@ronald.bitfreak.net>");
65 }
66
67 static void
68 gst_vcd_parse_class_init (GstVcdParseClass * klass)
69 {
70   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
71
72   element_class->change_state = GST_DEBUG_FUNCPTR (gst_vcd_parse_change_state);
73 }
74
75 static void
76 gst_vcd_parse_init (GstVcdParse * vcd, GstVcdParseClass * klass)
77 {
78   vcd->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
79   gst_pad_set_chain_function (vcd->sinkpad,
80       GST_DEBUG_FUNCPTR (gst_vcd_parse_chain));
81   gst_pad_set_event_function (vcd->sinkpad,
82       GST_DEBUG_FUNCPTR (gst_vcd_parse_sink_event));
83   gst_element_add_pad (GST_ELEMENT (vcd), vcd->sinkpad);
84
85   vcd->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
86   gst_pad_set_event_function (vcd->srcpad,
87       GST_DEBUG_FUNCPTR (gst_vcd_parse_src_event));
88   gst_pad_set_query_function (vcd->srcpad,
89       GST_DEBUG_FUNCPTR (gst_vcd_parse_src_query));
90   gst_pad_use_fixed_caps (vcd->srcpad);
91   gst_pad_set_caps (vcd->srcpad,
92       gst_static_pad_template_get_caps (&src_factory));
93   gst_element_add_pad (GST_ELEMENT (vcd), vcd->srcpad);
94 }
95
96 /* These conversion functions assume there's no junk between sectors */
97
98 static gint64
99 gst_vcd_parse_get_out_offset (gint64 in_offset)
100 {
101   gint64 out_offset, chunknum, rest;
102
103   if (in_offset == -1)
104     return -1;
105
106   if (G_UNLIKELY (in_offset < -1)) {
107     GST_WARNING ("unexpected/invalid in_offset %" G_GINT64_FORMAT, in_offset);
108     return in_offset;
109   }
110
111   chunknum = in_offset / GST_CDXA_SECTOR_SIZE;
112   rest = in_offset % GST_CDXA_SECTOR_SIZE;
113
114   out_offset = chunknum * GST_CDXA_DATA_SIZE;
115   if (rest > GST_CDXA_HEADER_SIZE) {
116     if (rest >= GST_CDXA_HEADER_SIZE + GST_CDXA_DATA_SIZE)
117       out_offset += GST_CDXA_DATA_SIZE;
118     else
119       out_offset += rest - GST_CDXA_HEADER_SIZE;
120   }
121
122   GST_LOG ("transformed in_offset %" G_GINT64_FORMAT " to out_offset %"
123       G_GINT64_FORMAT, in_offset, out_offset);
124
125   return out_offset;
126 }
127
128 static gint64
129 gst_vcd_parse_get_in_offset (gint64 out_offset)
130 {
131   gint64 in_offset, chunknum, rest;
132
133   if (out_offset == -1)
134     return -1;
135
136   if (G_UNLIKELY (out_offset < -1)) {
137     GST_WARNING ("unexpected/invalid out_offset %" G_GINT64_FORMAT, out_offset);
138     return out_offset;
139   }
140
141   chunknum = out_offset / GST_CDXA_DATA_SIZE;
142   rest = out_offset % GST_CDXA_DATA_SIZE;
143
144   in_offset = chunknum * GST_CDXA_SECTOR_SIZE;
145   if (rest > 0)
146     in_offset += GST_CDXA_HEADER_SIZE + rest;
147
148   GST_LOG ("transformed out_offset %" G_GINT64_FORMAT " to in_offset %"
149       G_GINT64_FORMAT, out_offset, in_offset);
150
151   return in_offset;
152 }
153
154 static gboolean
155 gst_vcd_parse_src_query (GstPad * pad, GstQuery * query)
156 {
157   GstVcdParse *vcd = GST_VCD_PARSE (gst_pad_get_parent (pad));
158   gboolean res = FALSE;
159
160   switch (GST_QUERY_TYPE (query)) {
161     case GST_QUERY_DURATION:{
162       GstFormat format;
163       gint64 dur;
164
165       /* first try upstream */
166       if (!gst_pad_query_default (pad, query))
167         break;
168
169       /* we can only handle BYTES */
170       gst_query_parse_duration (query, &format, &dur);
171       if (format != GST_FORMAT_BYTES)
172         break;
173
174       gst_query_set_duration (query, GST_FORMAT_BYTES,
175           gst_vcd_parse_get_out_offset (dur));
176
177       res = TRUE;
178       break;
179     }
180     case GST_QUERY_POSITION:{
181       GstFormat format;
182       gint64 pos;
183
184       /* first try upstream */
185       if (!gst_pad_query_default (pad, query))
186         break;
187
188       /* we can only handle BYTES */
189       gst_query_parse_position (query, &format, &pos);
190       if (format != GST_FORMAT_BYTES)
191         break;
192
193       gst_query_set_position (query, GST_FORMAT_BYTES,
194           gst_vcd_parse_get_out_offset (pos));
195
196       res = TRUE;
197       break;
198     }
199     default:
200       res = gst_pad_query_default (pad, query);
201       break;
202   }
203
204   gst_object_unref (vcd);
205   return res;
206 }
207
208 static gboolean
209 gst_vcd_parse_sink_event (GstPad * pad, GstEvent * event)
210 {
211   GstVcdParse *vcd = GST_VCD_PARSE (gst_pad_get_parent (pad));
212   gboolean res;
213
214   switch (GST_EVENT_TYPE (event)) {
215     case GST_EVENT_NEWSEGMENT:{
216       GstFormat format;
217       gboolean update;
218       gdouble rate, applied_rate;
219       gint64 start, stop, position;
220
221       gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate,
222           &format, &start, &stop, &position);
223
224       if (format == GST_FORMAT_BYTES) {
225         gst_event_unref (event);
226         event = gst_event_new_new_segment_full (update, rate, applied_rate,
227             GST_FORMAT_BYTES, gst_vcd_parse_get_out_offset (start),
228             gst_vcd_parse_get_out_offset (stop), position);
229       } else {
230         GST_WARNING_OBJECT (vcd, "newsegment event in non-byte format");
231       }
232       res = gst_pad_event_default (pad, event);
233       break;
234     }
235     case GST_EVENT_FLUSH_START:
236       gst_adapter_clear (vcd->adapter);
237       /* fall through */
238     default:
239       res = gst_pad_event_default (pad, event);
240       break;
241   }
242
243   gst_object_unref (vcd);
244   return res;
245 }
246
247 static gboolean
248 gst_vcd_parse_src_event (GstPad * pad, GstEvent * event)
249 {
250   GstVcdParse *vcd = GST_VCD_PARSE (gst_pad_get_parent (pad));
251   gboolean res;
252
253   switch (GST_EVENT_TYPE (event)) {
254     case GST_EVENT_SEEK:{
255       GstSeekType start_type, stop_type;
256       GstSeekFlags flags;
257       GstFormat format;
258       gdouble rate;
259       gint64 start, stop;
260
261       gst_event_parse_seek (event, &rate, &format, &flags, &start_type,
262           &start, &stop_type, &stop);
263
264       if (format == GST_FORMAT_BYTES) {
265         gst_event_unref (event);
266         if (start_type != GST_SEEK_TYPE_NONE)
267           start = gst_vcd_parse_get_in_offset (start);
268         if (stop_type != GST_SEEK_TYPE_NONE)
269           stop = gst_vcd_parse_get_in_offset (stop);
270         event = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, start_type,
271             start, stop_type, stop);
272       } else {
273         GST_WARNING_OBJECT (vcd, "seek event in non-byte format");
274       }
275       res = gst_pad_event_default (pad, event);
276       break;
277     }
278     default:
279       res = gst_pad_event_default (pad, event);
280       break;
281   }
282
283   gst_object_unref (vcd);
284   return res;
285 }
286
287 /* -1 = no sync (discard buffer),
288  * otherwise offset indicates sync point in buffer */
289 static gint
290 gst_vcd_parse_sync (const guint8 * data, guint size)
291 {
292   const guint8 sync_marker[12] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
293     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00
294   };
295   guint off = 0;
296
297   while (size >= 12) {
298     if (memcmp (data, sync_marker, 12) == 0)
299       return off;
300
301     --size;
302     ++data;
303     ++off;
304   }
305   return -1;
306 }
307
308 static GstFlowReturn
309 gst_vcd_parse_chain (GstPad * pad, GstBuffer * buf)
310 {
311   GstVcdParse *vcd = GST_VCD_PARSE (GST_PAD_PARENT (pad));
312   GstFlowReturn flow = GST_FLOW_OK;
313
314   gst_adapter_push (vcd->adapter, buf);
315   buf = NULL;
316
317   while (gst_adapter_available (vcd->adapter) >= GST_CDXA_SECTOR_SIZE) {
318     const guint8 *data;
319     guint8 header[4 + 8];
320     gint sync_offset;
321
322     /* find sync (we could peek any size though really) */
323     data = gst_adapter_peek (vcd->adapter, GST_CDXA_SECTOR_SIZE);
324     sync_offset = gst_vcd_parse_sync (data, GST_CDXA_SECTOR_SIZE);
325     GST_LOG_OBJECT (vcd, "sync offset = %d", sync_offset);
326
327     if (sync_offset < 0) {
328       gst_adapter_flush (vcd->adapter, GST_CDXA_SECTOR_SIZE - 12);
329       continue;                 /* try again */
330     }
331
332     gst_adapter_flush (vcd->adapter, sync_offset);
333
334     if (gst_adapter_available (vcd->adapter) < GST_CDXA_SECTOR_SIZE) {
335       GST_LOG_OBJECT (vcd, "not enough data in adapter, waiting for more");
336       break;
337     }
338
339     GST_LOG_OBJECT (vcd, "have full sector");
340
341     /* have one sector: a sector is 2352 bytes long and is composed of:
342      *
343      * +-------------------------------------------------------+
344      * !  sync    !  header ! subheader ! data ...   ! edc     !
345      * ! 12 bytes ! 4 bytes ! 8 bytes   ! 2324 bytes ! 4 bytes !
346      * +-------------------------------------------------------+
347      * 
348      * We strip the data out of it and send it to the srcpad.
349      * 
350      * sync       : 00 FF FF FF FF FF FF FF FF FF FF 00
351      * header     : hour minute second mode
352      * sub-header : track channel sub_mode coding repeat (4 bytes)
353      * edc        : checksum
354      */
355
356     /* Skip CDXA header and edc footer, only keep data in the middle */
357     gst_adapter_copy (vcd->adapter, header, 12, sizeof (header));
358     gst_adapter_flush (vcd->adapter, GST_CDXA_HEADER_SIZE);
359     buf = gst_adapter_take_buffer (vcd->adapter, GST_CDXA_DATA_SIZE);
360     gst_adapter_flush (vcd->adapter, 4);
361
362     /* we could probably do something clever to keep track of buffer offsets */
363     buf = gst_buffer_make_metadata_writable (buf);
364     GST_BUFFER_OFFSET (buf) = GST_BUFFER_OFFSET_NONE;
365     GST_BUFFER_TIMESTAMP (buf) = GST_CLOCK_TIME_NONE;
366     gst_buffer_set_caps (buf, GST_PAD_CAPS (vcd->srcpad));
367
368     flow = gst_pad_push (vcd->srcpad, buf);
369     buf = NULL;
370
371     if (G_UNLIKELY (flow != GST_FLOW_OK)) {
372       GST_DEBUG_OBJECT (vcd, "flow: %s", gst_flow_get_name (flow));
373       break;
374     }
375   }
376
377   return flow;
378 }
379
380 static GstStateChangeReturn
381 gst_vcd_parse_change_state (GstElement * element, GstStateChange transition)
382 {
383   GstStateChangeReturn res = GST_STATE_CHANGE_SUCCESS;
384   GstVcdParse *vcd = GST_VCD_PARSE (element);
385
386   switch (transition) {
387     case GST_STATE_CHANGE_READY_TO_PAUSED:
388       vcd->adapter = gst_adapter_new ();
389       break;
390     default:
391       break;
392   }
393
394   res = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
395
396   switch (transition) {
397     case GST_STATE_CHANGE_PAUSED_TO_READY:
398     case GST_STATE_CHANGE_READY_TO_NULL:
399       if (vcd->adapter) {
400         g_object_unref (vcd->adapter);
401         vcd->adapter = NULL;
402       }
403       break;
404     default:
405       break;
406   }
407
408   return res;
409 }