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