1 /* GStreamer OGM parsing
2  * Copyright (C) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net>
3  * Copyright (C) 2006 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., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include <stdio.h>
26 #include <string.h>
27 
28 #include <gst/gst.h>
29 #include <gst/tag/tag.h>
30 #include <gst/riff/riff-media.h>
31 #include <gst/riff/riff-read.h>
32 
33 #include "gstogg.h"
34 
35 GST_DEBUG_CATEGORY_STATIC (gst_ogm_parse_debug);
36 #define GST_CAT_DEFAULT gst_ogm_parse_debug
37 
38 #define GST_TYPE_OGM_VIDEO_PARSE (gst_ogm_video_parse_get_type())
39 #define GST_IS_OGM_VIDEO_PARSE(obj) \
40   (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_OGM_VIDEO_PARSE))
41 
42 #define GST_TYPE_OGM_AUDIO_PARSE (gst_ogm_audio_parse_get_type())
43 #define GST_IS_OGM_AUDIO_PARSE(obj) \
44   (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_OGM_AUDIO_PARSE))
45 
46 #define GST_TYPE_OGM_TEXT_PARSE (gst_ogm_text_parse_get_type())
47 #define GST_IS_OGM_TEXT_PARSE(obj) \
48   (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_OGM_TEXT_PARSE))
49 
50 #define GST_TYPE_OGM_PARSE (gst_ogm_parse_get_type())
51 #define GST_OGM_PARSE(obj) \
52   (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_OGM_PARSE, GstOgmParse))
53 #define GST_OGM_PARSE_CLASS(klass) \
54   (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_OGM_PARSE, GstOgmParse))
55 #define GST_IS_OGM_PARSE(obj) \
56   (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_OGM_PARSE))
57 #define GST_IS_OGM_PARSE_CLASS(klass) \
58   (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_OGM_PARSE))
59 #define GST_OGM_PARSE_GET_CLASS(obj) \
60   (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_OGM_PARSE, GstOgmParseClass))
61 
62 typedef struct _stream_header_video
63 {
64   gint32 width;
65   gint32 height;
66 } stream_header_video;
67 
68 typedef struct _stream_header_audio
69 {
70   gint16 channels;
71   gint16 blockalign;
72   gint32 avgbytespersec;
73 } stream_header_audio;
74 
75 /* sizeof(stream_header) might differ due to structure packing and
76  * alignment differences on some architectures, so not using that */
77 #define OGM_STREAM_HEADER_SIZE (8+4+4+8+8+4+4+4+8)
78 
79 typedef struct _stream_header
80 {
81   gchar streamtype[8];
82   gchar subtype[4 + 1];
83 
84   /* size of the structure */
85   gint32 size;
86 
87   /* in reference time */
88   gint64 time_unit;
89 
90   gint64 samples_per_unit;
91 
92   /* in media time */
93   gint32 default_len;
94 
95   gint32 buffersize;
96   gint32 bits_per_sample;
97 
98   union
99   {
100     stream_header_video video;
101     stream_header_audio audio;
102     /* text has no additional data */
103   } s;
104 } stream_header;
105 
106 typedef struct _GstOgmParse
107 {
108   GstElement element;
109 
110   /* pads */
111   GstPad *srcpad, *sinkpad;
112   GstPadTemplate *srcpadtempl;
113 
114   /* we need to cache events that we receive before creating the source pad */
115   GList *cached_events;
116 
117   /* audio or video */
118   stream_header hdr;
119 
120   /* expected next granulepos (used for timestamp guessing) */
121   guint64 next_granulepos;
122 } GstOgmParse;
123 
124 typedef struct _GstOgmParseClass
125 {
126   GstElementClass parent_class;
127 } GstOgmParseClass;
128 
129 static GstStaticPadTemplate sink_factory_video =
130 GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
131     GST_STATIC_CAPS ("application/x-ogm-video"));
132 static GstStaticPadTemplate sink_factory_audio =
133 GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
134     GST_STATIC_CAPS ("application/x-ogm-audio"));
135 static GstStaticPadTemplate sink_factory_text =
136 GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
137     GST_STATIC_CAPS ("application/x-ogm-text"));
138 static GstPadTemplate *video_src_templ, *audio_src_templ, *text_src_templ;
139 
140 static GType gst_ogm_audio_parse_get_type (void);
141 static GType gst_ogm_video_parse_get_type (void);
142 static GType gst_ogm_text_parse_get_type (void);
143 static GType gst_ogm_parse_get_type (void);
144 
145 static void gst_ogm_audio_parse_base_init (GstOgmParseClass * klass);
146 static void gst_ogm_video_parse_base_init (GstOgmParseClass * klass);
147 static void gst_ogm_text_parse_base_init (GstOgmParseClass * klass);
148 static void gst_ogm_parse_class_init (GstOgmParseClass * klass);
149 static void gst_ogm_parse_init (GstOgmParse * ogm);
150 static void gst_ogm_video_parse_init (GstOgmParse * ogm);
151 static void gst_ogm_audio_parse_init (GstOgmParse * ogm);
152 static void gst_ogm_text_parse_init (GstOgmParse * ogm);
153 
154 static gboolean gst_ogm_parse_sink_event (GstPad * pad, GstObject * parent,
155     GstEvent * event);
156 static gboolean gst_ogm_parse_sink_query (GstPad * pad, GstObject * parent,
157     GstQuery * query);
158 static gboolean gst_ogm_parse_sink_convert (GstPad * pad, GstFormat src_format,
159     gint64 src_value, GstFormat * dest_format, gint64 * dest_value);
160 
161 static GstFlowReturn gst_ogm_parse_chain (GstPad * pad, GstObject * parent,
162     GstBuffer * buffer);
163 
164 static GstStateChangeReturn gst_ogm_parse_change_state (GstElement * element,
165     GstStateChange transition);
166 
167 static GstElementClass *parent_class = NULL;
168 
169 static GType
gst_ogm_parse_get_type(void)170 gst_ogm_parse_get_type (void)
171 {
172   static GType ogm_parse_type = 0;
173 
174   if (!ogm_parse_type) {
175     static const GTypeInfo ogm_parse_info = {
176       sizeof (GstOgmParseClass),
177       NULL,
178       NULL,
179       (GClassInitFunc) gst_ogm_parse_class_init,
180       NULL,
181       NULL,
182       sizeof (GstOgmParse),
183       0,
184       (GInstanceInitFunc) gst_ogm_parse_init,
185     };
186 
187     ogm_parse_type =
188         g_type_register_static (GST_TYPE_ELEMENT,
189         "GstOgmParse", &ogm_parse_info, 0);
190   }
191 
192   return ogm_parse_type;
193 }
194 
195 static GType
gst_ogm_audio_parse_get_type(void)196 gst_ogm_audio_parse_get_type (void)
197 {
198   static GType ogm_audio_parse_type = 0;
199 
200   if (!ogm_audio_parse_type) {
201     static const GTypeInfo ogm_audio_parse_info = {
202       sizeof (GstOgmParseClass),
203       (GBaseInitFunc) gst_ogm_audio_parse_base_init,
204       NULL,
205       NULL,
206       NULL,
207       NULL,
208       sizeof (GstOgmParse),
209       0,
210       (GInstanceInitFunc) gst_ogm_audio_parse_init,
211     };
212 
213     ogm_audio_parse_type =
214         g_type_register_static (GST_TYPE_OGM_PARSE,
215         "GstOgmAudioParse", &ogm_audio_parse_info, 0);
216   }
217 
218   return ogm_audio_parse_type;
219 }
220 
221 static GType
gst_ogm_video_parse_get_type(void)222 gst_ogm_video_parse_get_type (void)
223 {
224   static GType ogm_video_parse_type = 0;
225 
226   if (!ogm_video_parse_type) {
227     static const GTypeInfo ogm_video_parse_info = {
228       sizeof (GstOgmParseClass),
229       (GBaseInitFunc) gst_ogm_video_parse_base_init,
230       NULL,
231       NULL,
232       NULL,
233       NULL,
234       sizeof (GstOgmParse),
235       0,
236       (GInstanceInitFunc) gst_ogm_video_parse_init,
237     };
238 
239     ogm_video_parse_type =
240         g_type_register_static (GST_TYPE_OGM_PARSE,
241         "GstOgmVideoParse", &ogm_video_parse_info, 0);
242   }
243 
244   return ogm_video_parse_type;
245 }
246 
247 static GType
gst_ogm_text_parse_get_type(void)248 gst_ogm_text_parse_get_type (void)
249 {
250   static GType ogm_text_parse_type = 0;
251 
252   if (!ogm_text_parse_type) {
253     static const GTypeInfo ogm_text_parse_info = {
254       sizeof (GstOgmParseClass),
255       (GBaseInitFunc) gst_ogm_text_parse_base_init,
256       NULL,
257       NULL,
258       NULL,
259       NULL,
260       sizeof (GstOgmParse),
261       0,
262       (GInstanceInitFunc) gst_ogm_text_parse_init,
263     };
264 
265     ogm_text_parse_type =
266         g_type_register_static (GST_TYPE_OGM_PARSE,
267         "GstOgmTextParse", &ogm_text_parse_info, 0);
268   }
269 
270   return ogm_text_parse_type;
271 }
272 
273 static void
gst_ogm_audio_parse_base_init(GstOgmParseClass * klass)274 gst_ogm_audio_parse_base_init (GstOgmParseClass * klass)
275 {
276   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
277   GstCaps *caps = gst_riff_create_audio_template_caps ();
278 
279   gst_element_class_set_static_metadata (element_class,
280       "OGM audio stream parser", "Codec/Parser/Audio",
281       "parse an OGM audio header and stream",
282       "GStreamer maintainers <gstreamer-devel@lists.freedesktop.org>");
283 
284   gst_element_class_add_static_pad_template (element_class,
285       &sink_factory_audio);
286   audio_src_templ =
287       gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_SOMETIMES, caps);
288   gst_element_class_add_pad_template (element_class, audio_src_templ);
289   gst_caps_unref (caps);
290 }
291 
292 static void
gst_ogm_video_parse_base_init(GstOgmParseClass * klass)293 gst_ogm_video_parse_base_init (GstOgmParseClass * klass)
294 {
295   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
296   GstCaps *caps = gst_riff_create_video_template_caps ();
297 
298   gst_element_class_set_static_metadata (element_class,
299       "OGM video stream parser", "Codec/Parser/Video",
300       "parse an OGM video header and stream",
301       "GStreamer maintainers <gstreamer-devel@lists.freedesktop.org>");
302 
303   gst_element_class_add_static_pad_template (element_class,
304       &sink_factory_video);
305   video_src_templ =
306       gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_SOMETIMES, caps);
307   gst_element_class_add_pad_template (element_class, video_src_templ);
308   gst_caps_unref (caps);
309 }
310 
311 static void
gst_ogm_text_parse_base_init(GstOgmParseClass * klass)312 gst_ogm_text_parse_base_init (GstOgmParseClass * klass)
313 {
314   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
315   GstCaps *caps = gst_caps_new_simple ("text/x-raw", "format", G_TYPE_STRING,
316       "utf8", NULL);
317 
318   gst_element_class_set_static_metadata (element_class,
319       "OGM text stream parser", "Codec/Decoder/Subtitle",
320       "parse an OGM text header and stream",
321       "GStreamer maintainers <gstreamer-devel@lists.freedesktop.org>");
322 
323   gst_element_class_add_static_pad_template (element_class, &sink_factory_text);
324   text_src_templ = gst_pad_template_new ("src",
325       GST_PAD_SRC, GST_PAD_SOMETIMES, caps);
326   gst_element_class_add_pad_template (element_class, text_src_templ);
327   gst_caps_unref (caps);
328 }
329 
330 static void
gst_ogm_parse_class_init(GstOgmParseClass * klass)331 gst_ogm_parse_class_init (GstOgmParseClass * klass)
332 {
333   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
334 
335   parent_class = g_type_class_peek_parent (klass);
336 
337   gstelement_class->change_state =
338       GST_DEBUG_FUNCPTR (gst_ogm_parse_change_state);
339 }
340 
341 static void
gst_ogm_parse_init(GstOgmParse * ogm)342 gst_ogm_parse_init (GstOgmParse * ogm)
343 {
344   memset (&ogm->hdr, 0, sizeof (ogm->hdr));
345   ogm->next_granulepos = 0;
346   ogm->srcpad = NULL;
347   ogm->cached_events = NULL;
348 }
349 
350 static void
gst_ogm_audio_parse_init(GstOgmParse * ogm)351 gst_ogm_audio_parse_init (GstOgmParse * ogm)
352 {
353   ogm->sinkpad = gst_pad_new_from_static_template (&sink_factory_audio, "sink");
354   gst_pad_set_query_function (ogm->sinkpad,
355       GST_DEBUG_FUNCPTR (gst_ogm_parse_sink_query));
356   gst_pad_set_chain_function (ogm->sinkpad,
357       GST_DEBUG_FUNCPTR (gst_ogm_parse_chain));
358   gst_pad_set_event_function (ogm->sinkpad,
359       GST_DEBUG_FUNCPTR (gst_ogm_parse_sink_event));
360   gst_element_add_pad (GST_ELEMENT (ogm), ogm->sinkpad);
361 
362   ogm->srcpad = NULL;
363   ogm->srcpadtempl = audio_src_templ;
364 }
365 
366 static void
gst_ogm_video_parse_init(GstOgmParse * ogm)367 gst_ogm_video_parse_init (GstOgmParse * ogm)
368 {
369   ogm->sinkpad = gst_pad_new_from_static_template (&sink_factory_video, "sink");
370   gst_pad_set_query_function (ogm->sinkpad,
371       GST_DEBUG_FUNCPTR (gst_ogm_parse_sink_query));
372   gst_pad_set_chain_function (ogm->sinkpad,
373       GST_DEBUG_FUNCPTR (gst_ogm_parse_chain));
374   gst_pad_set_event_function (ogm->sinkpad,
375       GST_DEBUG_FUNCPTR (gst_ogm_parse_sink_event));
376   gst_element_add_pad (GST_ELEMENT (ogm), ogm->sinkpad);
377 
378   ogm->srcpad = NULL;
379   ogm->srcpadtempl = video_src_templ;
380 }
381 
382 static void
gst_ogm_text_parse_init(GstOgmParse * ogm)383 gst_ogm_text_parse_init (GstOgmParse * ogm)
384 {
385   ogm->sinkpad = gst_pad_new_from_static_template (&sink_factory_text, "sink");
386   gst_pad_set_query_function (ogm->sinkpad,
387       GST_DEBUG_FUNCPTR (gst_ogm_parse_sink_query));
388   gst_pad_set_chain_function (ogm->sinkpad,
389       GST_DEBUG_FUNCPTR (gst_ogm_parse_chain));
390   gst_pad_set_event_function (ogm->sinkpad,
391       GST_DEBUG_FUNCPTR (gst_ogm_parse_sink_event));
392   gst_element_add_pad (GST_ELEMENT (ogm), ogm->sinkpad);
393 
394   ogm->srcpad = NULL;
395   ogm->srcpadtempl = text_src_templ;
396 }
397 
398 static gboolean
gst_ogm_parse_sink_convert(GstPad * pad,GstFormat src_format,gint64 src_value,GstFormat * dest_format,gint64 * dest_value)399 gst_ogm_parse_sink_convert (GstPad * pad,
400     GstFormat src_format, gint64 src_value,
401     GstFormat * dest_format, gint64 * dest_value)
402 {
403   gboolean res = FALSE;
404   GstOgmParse *ogm = GST_OGM_PARSE (gst_pad_get_parent (pad));
405 
406   switch (src_format) {
407     case GST_FORMAT_DEFAULT:
408       switch (*dest_format) {
409         case GST_FORMAT_TIME:
410           switch (ogm->hdr.streamtype[0]) {
411             case 'a':
412               *dest_value = GST_SECOND * src_value / ogm->hdr.samples_per_unit;
413               res = TRUE;
414               break;
415             case 'v':
416             case 't':
417               *dest_value = (GST_SECOND / 10000000) *
418                   ogm->hdr.time_unit * src_value;
419               res = TRUE;
420               break;
421             default:
422               break;
423           }
424           break;
425         default:
426           break;
427       }
428       break;
429     case GST_FORMAT_TIME:
430       switch (*dest_format) {
431         case GST_FORMAT_DEFAULT:
432           switch (ogm->hdr.streamtype[0]) {
433             case 'a':
434               *dest_value = ogm->hdr.samples_per_unit * src_value / GST_SECOND;
435               res = TRUE;
436               break;
437             case 'v':
438             case 't':
439               *dest_value = src_value /
440                   ((GST_SECOND / 10000000) * ogm->hdr.time_unit);
441               res = TRUE;
442               break;
443             default:
444               break;
445           }
446           break;
447         default:
448           break;
449       }
450       break;
451     default:
452       break;
453   }
454 
455   gst_object_unref (ogm);
456   return res;
457 }
458 
459 static gboolean
gst_ogm_parse_sink_query(GstPad * pad,GstObject * parent,GstQuery * query)460 gst_ogm_parse_sink_query (GstPad * pad, GstObject * parent, GstQuery * query)
461 {
462   GstOgmParse *ogm = GST_OGM_PARSE (parent);
463   GstFormat format;
464   gboolean res = FALSE;
465 
466   switch (GST_QUERY_TYPE (query)) {
467     case GST_QUERY_POSITION:
468     {
469       gint64 val;
470 
471       gst_query_parse_position (query, &format, NULL);
472 
473       if (format != GST_FORMAT_DEFAULT && format != GST_FORMAT_TIME)
474         break;
475 
476       if ((res = gst_ogm_parse_sink_convert (pad,
477                   GST_FORMAT_DEFAULT, ogm->next_granulepos, &format, &val))) {
478         /* don't know the total length here.. */
479         gst_query_set_position (query, format, val);
480       }
481       break;
482     }
483     case GST_QUERY_CONVERT:
484     {
485       GstFormat src_fmt, dest_fmt;
486       gint64 src_val, dest_val;
487 
488       /* peel off input */
489       gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val);
490       if ((res = gst_ogm_parse_sink_convert (pad, src_fmt, src_val,
491                   &dest_fmt, &dest_val))) {
492         gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
493       }
494       break;
495     }
496     default:
497       res = gst_pad_query_default (pad, parent, query);
498       break;
499   }
500 
501   return res;
502 }
503 
504 static GstFlowReturn
gst_ogm_parse_stream_header(GstOgmParse * ogm,const guint8 * data,guint size)505 gst_ogm_parse_stream_header (GstOgmParse * ogm, const guint8 * data, guint size)
506 {
507   GstCaps *caps = NULL;
508 
509   /* stream header */
510   if (size < OGM_STREAM_HEADER_SIZE)
511     goto buffer_too_small;
512 
513   if (!memcmp (data, "video\000\000\000", 8)) {
514     ogm->hdr.s.video.width = GST_READ_UINT32_LE (&data[44]);
515     ogm->hdr.s.video.height = GST_READ_UINT32_LE (&data[48]);
516   } else if (!memcmp (data, "audio\000\000\000", 8)) {
517     ogm->hdr.s.audio.channels = GST_READ_UINT32_LE (&data[44]);
518     ogm->hdr.s.audio.blockalign = GST_READ_UINT32_LE (&data[46]);
519     ogm->hdr.s.audio.avgbytespersec = GST_READ_UINT32_LE (&data[48]);
520   } else if (!memcmp (data, "text\000\000\000\000", 8)) {
521     /* nothing here */
522   } else {
523     goto cannot_decode;
524   }
525   memcpy (ogm->hdr.streamtype, &data[0], 8);
526   memcpy (ogm->hdr.subtype, &data[8], 4);
527   ogm->hdr.subtype[4] = '\0';
528   ogm->hdr.size = GST_READ_UINT32_LE (&data[12]);
529   ogm->hdr.time_unit = GST_READ_UINT64_LE (&data[16]);
530   ogm->hdr.samples_per_unit = GST_READ_UINT64_LE (&data[24]);
531   ogm->hdr.default_len = GST_READ_UINT32_LE (&data[32]);
532   ogm->hdr.buffersize = GST_READ_UINT32_LE (&data[36]);
533   ogm->hdr.bits_per_sample = GST_READ_UINT32_LE (&data[40]);
534 
535   switch (ogm->hdr.streamtype[0]) {
536     case 'a':{
537       guint codec_id = 0;
538 
539       if (sscanf (ogm->hdr.subtype, "%04x", &codec_id) != 1) {
540         GST_WARNING_OBJECT (ogm, "cannot parse subtype %s", ogm->hdr.subtype);
541       }
542 
543       /* FIXME: Need to do something with the reorder map */
544       caps =
545           gst_riff_create_audio_caps (codec_id, NULL, NULL, NULL, NULL, NULL,
546           NULL);
547 
548       if (caps == NULL) {
549         GST_WARNING_OBJECT (ogm, "no audio caps for codec %u found", codec_id);
550         caps = gst_caps_new_simple ("audio/x-ogm-unknown", "codec_id",
551             G_TYPE_INT, (gint) codec_id, NULL);
552       }
553 
554       gst_caps_set_simple (caps,
555           "channels", G_TYPE_INT, ogm->hdr.s.audio.channels,
556           "rate", G_TYPE_INT, ogm->hdr.samples_per_unit, NULL);
557 
558       GST_LOG_OBJECT (ogm, "Type: %s, subtype: 0x%04x, channels: %d, "
559           "samplerate: %d, blockalign: %d, bps: %d, caps = %" GST_PTR_FORMAT,
560           ogm->hdr.streamtype, codec_id, ogm->hdr.s.audio.channels,
561           (gint) ogm->hdr.samples_per_unit, ogm->hdr.s.audio.blockalign,
562           ogm->hdr.s.audio.avgbytespersec, caps);
563       break;
564     }
565     case 'v':{
566       guint32 fourcc;
567       gint time_unit;
568 
569       fourcc = GST_MAKE_FOURCC (ogm->hdr.subtype[0],
570           ogm->hdr.subtype[1], ogm->hdr.subtype[2], ogm->hdr.subtype[3]);
571 
572       caps = gst_riff_create_video_caps (fourcc, NULL, NULL, NULL, NULL, NULL);
573 
574       if (caps == NULL) {
575         gchar *fstr =
576             g_strdup_printf ("%" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc));
577         GST_WARNING_OBJECT (ogm, "could not find video caps for fourcc '%s'",
578             fstr);
579         caps =
580             gst_caps_new_simple ("video/x-ogm-unknown", "fourcc", G_TYPE_STRING,
581             fstr, NULL);
582         g_free (fstr);
583         break;
584       }
585 
586       GST_LOG_OBJECT (ogm, "Type: %s, subtype: %" GST_FOURCC_FORMAT
587           ", size: %dx%d, timeunit: %" G_GINT64_FORMAT
588           " (fps: %lf), s/u: %" G_GINT64_FORMAT ", "
589           "def.len: %d, bufsize: %d, bps: %d, caps = %" GST_PTR_FORMAT,
590           ogm->hdr.streamtype, GST_FOURCC_ARGS (fourcc),
591           ogm->hdr.s.video.width, ogm->hdr.s.video.height,
592           ogm->hdr.time_unit, 10000000. / ogm->hdr.time_unit,
593           ogm->hdr.samples_per_unit, ogm->hdr.default_len,
594           ogm->hdr.buffersize, ogm->hdr.bits_per_sample, caps);
595 
596       /* GST_TYPE_FRACTION contains gint */
597       if (ogm->hdr.time_unit > G_MAXINT || ogm->hdr.time_unit < 1)
598         GST_WARNING_OBJECT (ogm, "timeunit is out of range");
599 
600       time_unit = (gint) CLAMP (ogm->hdr.time_unit, 1, G_MAXINT);
601       gst_caps_set_simple (caps,
602           "width", G_TYPE_INT, ogm->hdr.s.video.width,
603           "height", G_TYPE_INT, ogm->hdr.s.video.height,
604           "framerate", GST_TYPE_FRACTION, 10000000, time_unit, NULL);
605       break;
606     }
607     case 't':{
608       GST_LOG_OBJECT (ogm, "Type: %s, s/u: %" G_GINT64_FORMAT
609           ", timeunit=%" G_GINT64_FORMAT,
610           ogm->hdr.streamtype, ogm->hdr.samples_per_unit, ogm->hdr.time_unit);
611       caps = gst_caps_new_simple ("text/x-raw", "format", G_TYPE_STRING,
612           "utf8", NULL);
613       break;
614     }
615     default:
616       g_assert_not_reached ();
617   }
618 
619   if (caps == NULL)
620     goto cannot_decode;
621 
622   if (!gst_caps_is_fixed (caps))
623     goto non_fixed_caps;
624 
625   if (ogm->srcpad) {
626     GstCaps *current_caps = gst_pad_get_current_caps (ogm->srcpad);
627 
628     if (current_caps) {
629       if (caps && !gst_caps_is_equal (current_caps, caps)) {
630         GST_WARNING_OBJECT (ogm, "Already an existing pad %s:%s",
631             GST_DEBUG_PAD_NAME (ogm->srcpad));
632         gst_pad_set_active (ogm->srcpad, FALSE);
633         gst_element_remove_pad (GST_ELEMENT (ogm), ogm->srcpad);
634         ogm->srcpad = NULL;
635       } else {
636         GST_DEBUG_OBJECT (ogm, "Existing pad has the same caps, do nothing");
637       }
638       gst_caps_unref (current_caps);
639     }
640   }
641 
642   if (ogm->srcpad == NULL) {
643     GList *l, *cached_events;
644 
645     ogm->srcpad = gst_pad_new_from_template (ogm->srcpadtempl, "src");
646     gst_pad_use_fixed_caps (ogm->srcpad);
647     gst_pad_set_active (ogm->srcpad, TRUE);
648     gst_element_add_pad (GST_ELEMENT (ogm), ogm->srcpad);
649     GST_INFO_OBJECT (ogm, "Added pad %s:%s with caps %" GST_PTR_FORMAT,
650         GST_DEBUG_PAD_NAME (ogm->srcpad), caps);
651 
652     GST_OBJECT_LOCK (ogm);
653     cached_events = ogm->cached_events;
654     ogm->cached_events = NULL;
655     GST_OBJECT_UNLOCK (ogm);
656 
657     for (l = cached_events; l; l = l->next) {
658       GstEvent *event = GST_EVENT_CAST (l->data);
659 
660       GST_DEBUG_OBJECT (ogm, "Pushing cached event %" GST_PTR_FORMAT, event);
661       gst_pad_push_event (ogm->srcpad, event);
662     }
663     g_list_free (cached_events);
664 
665     gst_pad_set_caps (ogm->srcpad, caps);
666     {
667       GstTagList *tags;
668 
669       tags = gst_tag_list_new (GST_TAG_SUBTITLE_CODEC, "Ogm", NULL);
670       gst_pad_push_event (ogm->srcpad, gst_event_new_tag (tags));
671     }
672   }
673 
674   gst_caps_unref (caps);
675 
676   return GST_FLOW_OK;
677 
678 /* ERRORS */
679 buffer_too_small:
680   {
681     GST_ELEMENT_ERROR (ogm, STREAM, WRONG_TYPE, ("Buffer too small"), (NULL));
682     return GST_FLOW_ERROR;
683   }
684 cannot_decode:
685   {
686     GST_ELEMENT_ERROR (ogm, STREAM, DECODE, (NULL), ("unknown ogm format"));
687     return GST_FLOW_ERROR;
688   }
689 non_fixed_caps:
690   {
691     gst_caps_unref (caps);
692     GST_ELEMENT_ERROR (ogm, STREAM, DECODE, (NULL), ("broken ogm format"));
693     return GST_FLOW_ERROR;
694   }
695 }
696 
697 static GstFlowReturn
gst_ogm_parse_comment_packet(GstOgmParse * ogm,GstBuffer * buf)698 gst_ogm_parse_comment_packet (GstOgmParse * ogm, GstBuffer * buf)
699 {
700   GstFlowReturn ret;
701 
702   if (ogm->srcpad == NULL) {
703     GST_DEBUG ("no source pad");
704     return GST_FLOW_FLUSHING;
705   }
706 
707   /* if this is not a subtitle stream, push the vorbiscomment packet
708    * on downstream, the respective decoder will handle it; if it is
709    * a subtitle stream, we will have to handle the comment ourself */
710   if (ogm->hdr.streamtype[0] == 't') {
711     GstTagList *tags;
712 
713     tags = gst_tag_list_from_vorbiscomment_buffer (buf,
714         (guint8 *) "\003vorbis", 7, NULL);
715 
716     if (tags) {
717       GST_DEBUG_OBJECT (ogm, "tags = %" GST_PTR_FORMAT, tags);
718       gst_pad_push_event (ogm->srcpad, gst_event_new_tag (tags));
719     } else {
720       GST_DEBUG_OBJECT (ogm, "failed to extract tags from vorbis comment");
721     }
722     /* do not push packet downstream, just let parent unref it */
723     ret = GST_FLOW_OK;
724   } else {
725     ret = gst_pad_push (ogm->srcpad, buf);
726   }
727 
728   return ret;
729 }
730 
731 static void
gst_ogm_text_parse_strip_trailing_zeroes(GstOgmParse * ogm,GstBuffer * buf)732 gst_ogm_text_parse_strip_trailing_zeroes (GstOgmParse * ogm, GstBuffer * buf)
733 {
734   GstMapInfo map;
735   gsize size;
736 
737   g_assert (gst_buffer_is_writable (buf));
738 
739   /* zeroes are not valid UTF-8 characters, so strip them from output */
740   gst_buffer_map (buf, &map, GST_MAP_WRITE);
741   size = map.size;
742   while (size > 0 && map.data[size - 1] == '\0') {
743     --size;
744   }
745   gst_buffer_unmap (buf, &map);
746 }
747 
748 static GstFlowReturn
gst_ogm_parse_data_packet(GstOgmParse * ogm,GstBuffer * buf,const guint8 * data,gsize size)749 gst_ogm_parse_data_packet (GstOgmParse * ogm, GstBuffer * buf,
750     const guint8 * data, gsize size)
751 {
752   GstFlowReturn ret;
753   GstBuffer *sbuf;
754   gboolean keyframe;
755   guint len, n, xsize = 0;
756 
757   if ((data[0] & 0x01) != 0)
758     goto invalid_startcode;
759 
760   /* data - push on */
761   len = ((data[0] & 0xc0) >> 6) | ((data[0] & 0x02) << 1);
762   keyframe = (((data[0] & 0x08) >> 3) != 0);
763 
764   if ((1 + len) > size)
765     goto buffer_too_small;
766 
767   for (n = len; n > 0; n--) {
768     xsize = (xsize << 8) | data[n];
769   }
770 
771   GST_LOG_OBJECT (ogm, "[0x%02x] samples: %d, hdrbytes: %d, datasize: %"
772       G_GSIZE_FORMAT, data[0], xsize, len, size - len - 1);
773 
774   sbuf =
775       gst_buffer_copy_region (buf, GST_BUFFER_COPY_ALL, len + 1,
776       size - len - 1);
777 
778   if (GST_BUFFER_OFFSET_END_IS_VALID (buf))
779     ogm->next_granulepos = GST_BUFFER_OFFSET_END (buf);
780 
781   switch (ogm->hdr.streamtype[0]) {
782     case 't':
783     case 'v':{
784       GstClockTime ts, next_ts;
785       guint samples;
786 
787       samples = (ogm->hdr.streamtype[0] == 'v') ? 1 : xsize;
788 
789       if (!keyframe) {
790         GST_BUFFER_FLAG_SET (sbuf, GST_BUFFER_FLAG_DELTA_UNIT);
791       }
792 
793       /* shouldn't this be granulepos - samples? (tpm) */
794       ts = gst_util_uint64_scale (ogm->next_granulepos,
795           ogm->hdr.time_unit * GST_SECOND, 10000000);
796       next_ts = gst_util_uint64_scale (ogm->next_granulepos + samples,
797           ogm->hdr.time_unit * GST_SECOND, 10000000);
798 
799       GST_BUFFER_TIMESTAMP (sbuf) = ts;
800       GST_BUFFER_DURATION (sbuf) = next_ts - ts;
801 
802       ogm->next_granulepos += samples;
803 
804       if (ogm->hdr.streamtype[0] == 't') {
805         gst_ogm_text_parse_strip_trailing_zeroes (ogm, sbuf);
806       }
807       break;
808     }
809     case 'a':{
810       GstClockTime ts, next_ts;
811 
812       /* shouldn't this be granulepos - samples? (tpm) */
813       ts = gst_util_uint64_scale_int (ogm->next_granulepos,
814           GST_SECOND, ogm->hdr.samples_per_unit);
815       next_ts = gst_util_uint64_scale_int (ogm->next_granulepos + xsize,
816           GST_SECOND, ogm->hdr.samples_per_unit);
817 
818       GST_BUFFER_TIMESTAMP (sbuf) = ts;
819       GST_BUFFER_DURATION (sbuf) = next_ts - ts;
820 
821       ogm->next_granulepos += xsize;
822       break;
823     }
824     default:
825       g_assert_not_reached ();
826       break;
827   }
828 
829   if (ogm->srcpad) {
830     GST_LOG_OBJECT (ogm, "Pushing buffer with ts=%" GST_TIME_FORMAT,
831         GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (sbuf)));
832     ret = gst_pad_push (ogm->srcpad, sbuf);
833     if (ret != GST_FLOW_OK) {
834       GST_DEBUG_OBJECT (ogm, "Flow on %s:%s = %s",
835           GST_DEBUG_PAD_NAME (ogm->srcpad), gst_flow_get_name (ret));
836     }
837   } else {
838     ret = GST_FLOW_FLUSHING;
839   }
840 
841   return ret;
842 
843 /* ERRORS */
844 invalid_startcode:
845   {
846     GST_ELEMENT_ERROR (ogm, STREAM, DECODE, (NULL),
847         ("unexpected packet startcode 0x%02x", data[0]));
848     return GST_FLOW_ERROR;
849   }
850 buffer_too_small:
851   {
852     GST_ELEMENT_ERROR (ogm, STREAM, DECODE, (NULL),
853         ("buffer too small, len+1=%u, size=%" G_GSIZE_FORMAT, len + 1, size));
854     return GST_FLOW_ERROR;
855   }
856 }
857 
858 static GstFlowReturn
gst_ogm_parse_chain(GstPad * pad,GstObject * parent,GstBuffer * buf)859 gst_ogm_parse_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
860 {
861   GstFlowReturn ret = GST_FLOW_OK;
862   GstOgmParse *ogm = GST_OGM_PARSE (parent);
863   GstMapInfo map;
864 
865   gst_buffer_map (buf, &map, GST_MAP_READ);
866   if (map.size < 1)
867     goto buffer_too_small;
868 
869   GST_LOG_OBJECT (ogm, "Packet with start code 0x%02x", map.data[0]);
870 
871   switch (map.data[0]) {
872     case 0x01:{
873       ret = gst_ogm_parse_stream_header (ogm, map.data + 1, map.size - 1);
874       break;
875     }
876     case 0x03:{
877       ret = gst_ogm_parse_comment_packet (ogm, buf);
878       break;
879     }
880     default:{
881       ret = gst_ogm_parse_data_packet (ogm, buf, map.data, map.size);
882       break;
883     }
884   }
885 
886   gst_buffer_unmap (buf, &map);
887   gst_buffer_unref (buf);
888 
889   if (ret != GST_FLOW_OK) {
890     GST_DEBUG_OBJECT (ogm, "Flow: %s", gst_flow_get_name (ret));
891   }
892 
893   return ret;
894 
895 /* ERRORS */
896 buffer_too_small:
897   {
898     GST_ELEMENT_ERROR (ogm, STREAM, DECODE, (NULL), ("buffer too small"));
899     gst_buffer_unmap (buf, &map);
900     gst_buffer_unref (buf);
901     return GST_FLOW_ERROR;
902   }
903 }
904 
905 static gboolean
gst_ogm_parse_sink_event(GstPad * pad,GstObject * parent,GstEvent * event)906 gst_ogm_parse_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
907 {
908   GstOgmParse *ogm = GST_OGM_PARSE (parent);
909   gboolean res;
910 
911   GST_LOG_OBJECT (ogm, "processing %s event", GST_EVENT_TYPE_NAME (event));
912 
913   GST_OBJECT_LOCK (ogm);
914   if (ogm->srcpad == NULL) {
915     ogm->cached_events = g_list_append (ogm->cached_events, event);
916     GST_OBJECT_UNLOCK (ogm);
917     res = TRUE;
918   } else {
919     GST_OBJECT_UNLOCK (ogm);
920     res = gst_pad_event_default (pad, parent, event);
921   }
922 
923   return res;
924 }
925 
926 static GstStateChangeReturn
gst_ogm_parse_change_state(GstElement * element,GstStateChange transition)927 gst_ogm_parse_change_state (GstElement * element, GstStateChange transition)
928 {
929   GstStateChangeReturn ret;
930   GstOgmParse *ogm = GST_OGM_PARSE (element);
931 
932   ret = parent_class->change_state (element, transition);
933   if (ret != GST_STATE_CHANGE_SUCCESS)
934     return ret;
935 
936   switch (transition) {
937     case GST_STATE_CHANGE_PAUSED_TO_READY:
938       if (ogm->srcpad) {
939         gst_pad_set_active (ogm->srcpad, FALSE);
940         gst_element_remove_pad (element, ogm->srcpad);
941         ogm->srcpad = NULL;
942       }
943       memset (&ogm->hdr, 0, sizeof (ogm->hdr));
944       ogm->next_granulepos = 0;
945       g_list_foreach (ogm->cached_events, (GFunc) gst_mini_object_unref, NULL);
946       g_list_free (ogm->cached_events);
947       ogm->cached_events = NULL;
948       break;
949     default:
950       break;
951   }
952 
953   return ret;
954 }
955 
956 gboolean
gst_ogm_parse_plugin_init(GstPlugin * plugin)957 gst_ogm_parse_plugin_init (GstPlugin * plugin)
958 {
959   gst_riff_init ();
960 
961   GST_DEBUG_CATEGORY_INIT (gst_ogm_parse_debug, "ogmparse", 0, "ogm parser");
962 
963   return gst_element_register (plugin, "ogmaudioparse", GST_RANK_PRIMARY,
964       GST_TYPE_OGM_AUDIO_PARSE) &&
965       gst_element_register (plugin, "ogmvideoparse", GST_RANK_PRIMARY,
966       GST_TYPE_OGM_VIDEO_PARSE) &&
967       gst_element_register (plugin, "ogmtextparse", GST_RANK_PRIMARY,
968       GST_TYPE_OGM_TEXT_PARSE);
969 }
970