--- /dev/null
+/* GStreamer Ogg Granulepos Mapping Utility Functions
+ * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net>
+ * Copyright (C) 2009 David Schleef <ds@schleef.org>
+ *
+ * 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 "config.h"
+#endif
+
+#include "gstoggstream.h"
+#include "dirac_parse.h"
+#include "vorbis_parse.h"
+
+#include <gst/riff/riff-media.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+GST_DEBUG_CATEGORY_EXTERN (gst_ogg_demux_debug);
+GST_DEBUG_CATEGORY_EXTERN (gst_ogg_demux_setup_debug);
+#define GST_CAT_DEFAULT gst_ogg_demux_debug
+
+typedef struct _GstOggMap GstOggMap;
+
+typedef gboolean (*GstOggMapSetupFunc) (GstOggStream * pad,
+ ogg_packet * packet);
+typedef GstClockTime (*GstOggMapToTimeFunc) (GstOggStream * pad,
+ gint64 granulepos);
+typedef gint64 (*GstOggMapToGranuleFunc) (GstOggStream * pad,
+ gint64 granulepos);
+typedef gint64 (*GstOggMapToGranuleposFunc) (GstOggStream * pad,
+ gint64 granule, gint64 keyframe_granule);
+
+/* returns TRUE if the granulepos denotes a key frame */
+typedef gboolean (*GstOggMapIsKeyFrameFunc) (GstOggStream * pad,
+ gint64 granulepos);
+
+/* returns TRUE if the given packet is a stream header packet */
+typedef gboolean (*GstOggMapIsHeaderPacketFunc) (GstOggStream * pad,
+ ogg_packet * packet);
+typedef gint64 (*GstOggMapPacketDurationFunc) (GstOggStream * pad,
+ ogg_packet * packet);
+typedef void (*GstOggMapExtractTagsFunc) (GstOggStream * pad,
+ ogg_packet * packet);
+
+typedef gint64 (*GstOggMapGranuleposToKeyGranuleFunc) (GstOggStream * pad,
+ gint64 granulepos);
+
+#define SKELETON_FISBONE_MIN_SIZE 52
+#define SKELETON_FISHEAD_3_3_MIN_SIZE 112
+#define SKELETON_FISHEAD_4_0_MIN_SIZE 80
+
+struct _GstOggMap
+{
+ const gchar *id;
+ int id_length;
+ int min_packet_size;
+ const gchar *media_type;
+ GstOggMapSetupFunc setup_func;
+ GstOggMapToGranuleFunc granulepos_to_granule_func;
+ GstOggMapToGranuleposFunc granule_to_granulepos_func;
+ GstOggMapIsKeyFrameFunc is_key_frame_func;
+ GstOggMapIsHeaderPacketFunc is_header_func;
+ GstOggMapPacketDurationFunc packet_duration_func;
+ GstOggMapGranuleposToKeyGranuleFunc granulepos_to_key_granule_func;
+ GstOggMapExtractTagsFunc extract_tags_func;
+};
+
+extern const GstOggMap mappers[];
+
+GstClockTime
+gst_ogg_stream_get_packet_start_time (GstOggStream * pad, ogg_packet * packet)
+{
+ int duration;
+
+ if (packet->granulepos == -1) {
+ return GST_CLOCK_TIME_NONE;
+ }
+
+ duration = gst_ogg_stream_get_packet_duration (pad, packet);
+ if (duration == -1) {
+ return GST_CLOCK_TIME_NONE;
+ }
+
+ return gst_ogg_stream_granule_to_time (pad,
+ gst_ogg_stream_granulepos_to_granule (pad,
+ packet->granulepos) - duration);
+}
+
+GstClockTime
+gst_ogg_stream_get_start_time_for_granulepos (GstOggStream * pad,
+ gint64 granulepos)
+{
+ if (pad->frame_size == 0)
+ return GST_CLOCK_TIME_NONE;
+
+ return gst_ogg_stream_granule_to_time (pad,
+ gst_ogg_stream_granulepos_to_granule (pad, granulepos));
+}
+
+GstClockTime
+gst_ogg_stream_get_end_time_for_granulepos (GstOggStream * pad,
+ gint64 granulepos)
+{
+ return gst_ogg_stream_granule_to_time (pad,
+ gst_ogg_stream_granulepos_to_granule (pad, granulepos));
+}
+
+GstClockTime
+gst_ogg_stream_granule_to_time (GstOggStream * pad, gint64 granule)
+{
+ if (granule == 0 || pad->granulerate_n == 0 || pad->granulerate_d == 0)
+ return 0;
+
+ return gst_util_uint64_scale (granule, GST_SECOND * pad->granulerate_d,
+ pad->granulerate_n);
+}
+
+gint64
+gst_ogg_stream_granulepos_to_granule (GstOggStream * pad, gint64 granulepos)
+{
+ if (granulepos == -1 || granulepos == 0) {
+ return granulepos;
+ }
+
+ if (mappers[pad->map].granulepos_to_granule_func == NULL) {
+ GST_WARNING ("Failed to convert granulepos to granule");
+ return -1;
+ }
+
+ return mappers[pad->map].granulepos_to_granule_func (pad, granulepos);
+}
+
+gint64
+gst_ogg_stream_granulepos_to_key_granule (GstOggStream * pad, gint64 granulepos)
+{
+ if (mappers[pad->map].granulepos_to_key_granule_func)
+ return mappers[pad->map].granulepos_to_key_granule_func (pad, granulepos);
+
+ if (granulepos == -1 || granulepos == 0) {
+ return granulepos;
+ }
+
+ return granulepos >> pad->granuleshift;
+}
+
+gint64
+gst_ogg_stream_granule_to_granulepos (GstOggStream * pad, gint64 granule,
+ gint64 keyframe_granule)
+{
+ if (granule == -1 || granule == 0) {
+ return granule;
+ }
+
+ if (mappers[pad->map].granule_to_granulepos_func == NULL) {
+ GST_WARNING ("Failed to convert granule to granulepos");
+ return -1;
+ }
+
+ return mappers[pad->map].granule_to_granulepos_func (pad, granule,
+ keyframe_granule);
+}
+
+gboolean
+gst_ogg_stream_granulepos_is_key_frame (GstOggStream * pad, gint64 granulepos)
+{
+ if (granulepos == -1) {
+ return FALSE;
+ }
+
+ if (mappers[pad->map].is_key_frame_func == NULL) {
+ GST_WARNING ("Failed to determine key frame");
+ return FALSE;
+ }
+
+ return mappers[pad->map].is_key_frame_func (pad, granulepos);
+}
+
+gboolean
+gst_ogg_stream_packet_is_header (GstOggStream * pad, ogg_packet * packet)
+{
+ if (mappers[pad->map].is_header_func == NULL) {
+ GST_WARNING ("Failed to determine header");
+ return FALSE;
+ }
+
+ return mappers[pad->map].is_header_func (pad, packet);
+}
+
+gint64
+gst_ogg_stream_get_packet_duration (GstOggStream * pad, ogg_packet * packet)
+{
+ if (mappers[pad->map].packet_duration_func == NULL) {
+ GST_WARNING ("Failed to determine packet duration");
+ return -1;
+ }
+
+ return mappers[pad->map].packet_duration_func (pad, packet);
+}
+
+
+void
+gst_ogg_stream_extract_tags (GstOggStream * pad, ogg_packet * packet)
+{
+ if (mappers[pad->map].extract_tags_func == NULL) {
+ GST_DEBUG ("No tag extraction");
+ return;
+ }
+
+ mappers[pad->map].extract_tags_func (pad, packet);
+}
+
+/* some generic functions */
+
+static gboolean
+is_keyframe_true (GstOggStream * pad, gint64 granulepos)
+{
+ return TRUE;
+}
+
+static gint64
+granulepos_to_granule_default (GstOggStream * pad, gint64 granulepos)
+{
+ gint64 keyindex, keyoffset;
+
+ if (pad->granuleshift != 0) {
+ keyindex = granulepos >> pad->granuleshift;
+ keyoffset = granulepos - (keyindex << pad->granuleshift);
+ return keyindex + keyoffset;
+ } else {
+ return granulepos;
+ }
+}
+
+
+static gint64
+granule_to_granulepos_default (GstOggStream * pad, gint64 granule,
+ gint64 keyframe_granule)
+{
+ gint64 keyoffset;
+
+ if (pad->granuleshift != 0) {
+ /* If we don't know where the previous keyframe is yet, assume it is
+ at 0 or 1, depending on bitstream version. If nothing else, this
+ avoids getting negative granpos back. */
+ if (keyframe_granule < 0)
+ keyframe_granule = pad->theora_has_zero_keyoffset ? 0 : 1;
+ keyoffset = granule - keyframe_granule;
+ return (keyframe_granule << pad->granuleshift) | keyoffset;
+ } else {
+ return granule;
+ }
+}
+
+#ifdef unused
+static gboolean
+is_header_unknown (GstOggStream * pad, ogg_packet * packet)
+{
+ GST_WARNING ("don't know how to detect header");
+ return FALSE;
+}
+#endif
+
+static gboolean
+is_header_true (GstOggStream * pad, ogg_packet * packet)
+{
+ return TRUE;
+}
+
+static gboolean
+is_header_count (GstOggStream * pad, ogg_packet * packet)
+{
+ if (pad->n_header_packets_seen < pad->n_header_packets) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gint64
+packet_duration_constant (GstOggStream * pad, ogg_packet * packet)
+{
+ return pad->frame_size;
+}
+
+/* helper: extracts tags from vorbis comment ogg packet.
+ * Returns result in *tags after free'ing existing *tags (if any) */
+static gboolean
+tag_list_from_vorbiscomment_packet (ogg_packet * packet,
+ const guint8 * id_data, const guint id_data_length, GstTagList ** tags)
+{
+ GstBuffer *buf = NULL;
+ gchar *encoder = NULL;
+ GstTagList *list;
+ gboolean ret = TRUE;
+
+ g_return_val_if_fail (tags != NULL, FALSE);
+
+ buf = gst_buffer_new ();
+ GST_BUFFER_DATA (buf) = (guint8 *) packet->packet;
+ GST_BUFFER_SIZE (buf) = packet->bytes;
+
+ list = gst_tag_list_from_vorbiscomment_buffer (buf, id_data, id_data_length,
+ &encoder);
+
+ if (!list) {
+ GST_WARNING ("failed to decode vorbis comments");
+ ret = FALSE;
+ goto exit;
+ }
+
+ if (encoder) {
+ if (encoder[0])
+ gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_ENCODER, encoder,
+ NULL);
+ g_free (encoder);
+ }
+
+exit:
+ if (*tags)
+ gst_tag_list_free (*tags);
+ *tags = list;
+
+ gst_buffer_unref (buf);
+
+ return ret;
+}
+
+/* theora */
+
+static gboolean
+setup_theora_mapper (GstOggStream * pad, ogg_packet * packet)
+{
+ guint8 *data = packet->packet;
+ guint w, h, par_d, par_n;
+ guint8 vmaj, vmin, vrev;
+
+ vmaj = data[7];
+ vmin = data[8];
+ vrev = data[9];
+
+ w = GST_READ_UINT24_BE (data + 14) & 0xFFFFFF;
+ h = GST_READ_UINT24_BE (data + 17) & 0xFFFFFF;
+
+ pad->granulerate_n = GST_READ_UINT32_BE (data + 22);
+ pad->granulerate_d = GST_READ_UINT32_BE (data + 26);
+
+ par_n = GST_READ_UINT24_BE (data + 30);
+ par_d = GST_READ_UINT24_BE (data + 33);
+
+ GST_LOG ("fps = %d/%d, PAR = %u/%u, width = %u, height = %u",
+ pad->granulerate_n, pad->granulerate_d, par_n, par_d, w, h);
+
+ /* 2 bits + 3 bits = 5 bits KFGSHIFT */
+ pad->granuleshift = ((GST_READ_UINT8 (data + 40) & 0x03) << 3) +
+ (GST_READ_UINT8 (data + 41) >> 5);
+
+ pad->is_video = TRUE;
+ pad->n_header_packets = 3;
+ pad->frame_size = 1;
+
+ pad->bitrate = GST_READ_UINT24_BE (data + 37);
+ GST_LOG ("bit rate: %d", pad->bitrate);
+
+ if (pad->granulerate_n == 0 || pad->granulerate_d == 0) {
+ GST_WARNING ("frame rate %d/%d", pad->granulerate_n, pad->granulerate_d);
+ return FALSE;
+ }
+
+ /* The interpretation of the granule position has changed with 3.2.1.
+ The granule is now made from the number of frames encoded, rather than
+ the index of the frame being encoded - so there is a difference of 1. */
+ pad->theora_has_zero_keyoffset =
+ ((vmaj << 16) | (vmin << 8) | vrev) < 0x030201;
+
+ pad->caps = gst_caps_new_simple ("video/x-theora", NULL);
+
+ if (w > 0 && h > 0) {
+ gst_caps_set_simple (pad->caps, "width", G_TYPE_INT, w, "height",
+ G_TYPE_INT, h, NULL);
+ }
+
+ /* PAR of 0:N, N:0 and 0:0 is allowed and maps to 1:1 */
+ if (par_n == 0 || par_d == 0)
+ par_n = par_d = 1;
+
+ /* only add framerate now so caps look prettier, with width/height first */
+ gst_caps_set_simple (pad->caps, "framerate", GST_TYPE_FRACTION,
+ pad->granulerate_n, pad->granulerate_d, "pixel-aspect-ratio",
+ GST_TYPE_FRACTION, par_n, par_d, NULL);
+
+ return TRUE;
+}
+
+static gint64
+granulepos_to_granule_theora (GstOggStream * pad, gint64 granulepos)
+{
+ gint64 keyindex, keyoffset;
+
+ if (pad->granuleshift != 0) {
+ keyindex = granulepos >> pad->granuleshift;
+ keyoffset = granulepos - (keyindex << pad->granuleshift);
+ if (pad->theora_has_zero_keyoffset) {
+ keyoffset++;
+ }
+ return keyindex + keyoffset;
+ } else {
+ return granulepos;
+ }
+}
+
+static gboolean
+is_keyframe_theora (GstOggStream * pad, gint64 granulepos)
+{
+ gint64 frame_mask;
+
+ if (granulepos == (gint64) - 1)
+ return FALSE;
+
+ frame_mask = (1 << pad->granuleshift) - 1;
+
+ return ((granulepos & frame_mask) == 0);
+}
+
+static gboolean
+is_header_theora (GstOggStream * pad, ogg_packet * packet)
+{
+ return (packet->bytes > 0 && (packet->packet[0] & 0x80) == 0x80);
+}
+
+static void
+extract_tags_theora (GstOggStream * pad, ogg_packet * packet)
+{
+ if (packet->bytes > 0 && packet->packet[0] == 0x81) {
+ tag_list_from_vorbiscomment_packet (packet,
+ (const guint8 *) "\201theora", 7, &pad->taglist);
+
+ if (!pad->taglist)
+ pad->taglist = gst_tag_list_new ();
+
+ if (pad->bitrate)
+ gst_tag_list_add (pad->taglist, GST_TAG_MERGE_REPLACE,
+ GST_TAG_BITRATE, (guint) pad->bitrate, NULL);
+ }
+}
+
+/* dirac */
+
+static gboolean
+setup_dirac_mapper (GstOggStream * pad, ogg_packet * packet)
+{
+ int ret;
+ DiracSequenceHeader header;
+
+ ret = dirac_sequence_header_parse (&header, packet->packet + 13,
+ packet->bytes - 13);
+ if (ret == 0) {
+ GST_DEBUG ("Failed to parse Dirac sequence header");
+ return FALSE;
+ }
+
+ pad->is_video = TRUE;
+ pad->granulerate_n = header.frame_rate_numerator * 2;
+ pad->granulerate_d = header.frame_rate_denominator;
+ pad->granuleshift = 22;
+ pad->n_header_packets = 1;
+ pad->frame_size = 2;
+
+ if (header.interlaced_coding != 0) {
+ GST_DEBUG ("non-progressive Dirac coding not implemented");
+ return FALSE;
+ }
+
+ pad->caps = gst_caps_new_simple ("video/x-dirac",
+ "width", G_TYPE_INT, header.width,
+ "height", G_TYPE_INT, header.height,
+ "interlaced", G_TYPE_BOOLEAN, header.interlaced,
+ "pixel-aspect-ratio", GST_TYPE_FRACTION,
+ header.aspect_ratio_numerator, header.aspect_ratio_denominator,
+ "framerate", GST_TYPE_FRACTION, header.frame_rate_numerator,
+ header.frame_rate_denominator, NULL);
+
+ return TRUE;
+}
+
+#define OGG_DIRAC_GRANULE_LOW_MASK ((1<<22) - 1)
+static gboolean
+is_keyframe_dirac (GstOggStream * pad, gint64 granulepos)
+{
+ int dist_h;
+ int dist_l;
+ int dist;
+
+ if (granulepos == -1)
+ return -1;
+
+ dist_h = (granulepos >> 22) & 0xff;
+ dist_l = granulepos & 0xff;
+ dist = (dist_h << 8) | dist_l;
+
+ return (dist == 0);
+}
+
+static gint64
+granulepos_to_granule_dirac (GstOggStream * pad, gint64 gp)
+{
+ gint64 pt;
+ int delay;
+ gint64 dt;
+
+ pt = ((gp >> 22) + (gp & OGG_DIRAC_GRANULE_LOW_MASK)) >> 9;
+ delay = (gp >> 9) & 0x1fff;
+ dt = pt - delay;
+
+ GST_DEBUG ("pt %" G_GINT64_FORMAT " delay %d", pt, delay);
+
+ return dt + 4;
+}
+
+static gint64
+granule_to_granulepos_dirac (GstOggStream * pad, gint64 granule,
+ gint64 keyframe_granule)
+{
+ /* This conversion requires knowing more details about the Dirac
+ * stream. */
+ return -1;
+}
+
+static gint64
+granulepos_to_key_granule_dirac (GstOggStream * pad, gint64 gp)
+{
+ gint64 pt;
+ int dist_h;
+ int dist_l;
+ int dist;
+ int delay;
+ gint64 dt;
+
+ if (gp == -1 || gp == 0)
+ return gp;
+
+ pt = ((gp >> 22) + (gp & OGG_DIRAC_GRANULE_LOW_MASK)) >> 9;
+ dist_h = (gp >> 22) & 0xff;
+ dist_l = gp & 0xff;
+ dist = (dist_h << 8) | dist_l;
+ delay = (gp >> 9) & 0x1fff;
+ dt = pt - delay;
+
+ return dt - 2 * dist + 4;
+}
+
+/* VP8 */
+
+static gboolean
+setup_vp8_mapper (GstOggStream * pad, ogg_packet * packet)
+{
+ gint width, height, par_n, par_d, fps_n, fps_d;
+
+ if (packet->bytes < 26) {
+ GST_DEBUG ("Failed to parse VP8 BOS page");
+ return FALSE;
+ }
+
+ width = GST_READ_UINT16_BE (packet->packet + 8);
+ height = GST_READ_UINT16_BE (packet->packet + 10);
+ par_n = GST_READ_UINT24_BE (packet->packet + 12);
+ par_d = GST_READ_UINT24_BE (packet->packet + 15);
+ fps_n = GST_READ_UINT32_BE (packet->packet + 18);
+ fps_d = GST_READ_UINT32_BE (packet->packet + 22);
+
+ pad->is_video = TRUE;
+ pad->is_vp8 = TRUE;
+ pad->granulerate_n = fps_n;
+ pad->granulerate_d = fps_d;
+ pad->n_header_packets = 2;
+ pad->frame_size = 1;
+
+ pad->caps = gst_caps_new_simple ("video/x-vp8",
+ "width", G_TYPE_INT, width,
+ "height", G_TYPE_INT, height,
+ "pixel-aspect-ratio", GST_TYPE_FRACTION,
+ par_n, par_d, "framerate", GST_TYPE_FRACTION, fps_n, fps_d, NULL);
+
+ return TRUE;
+}
+
+static gboolean
+is_keyframe_vp8 (GstOggStream * pad, gint64 granulepos)
+{
+ guint64 gpos = granulepos;
+
+ if (granulepos == -1)
+ return FALSE;
+
+ /* Get rid of flags */
+ gpos >>= 3;
+
+ return ((gpos & 0x07ffffff) == 0);
+}
+
+static gint64
+granulepos_to_granule_vp8 (GstOggStream * pad, gint64 gpos)
+{
+ guint64 gp = (guint64) gpos;
+ guint32 pt;
+ guint32 dist;
+
+ pt = (gp >> 32);
+ dist = (gp >> 3) & 0x07ffffff;
+
+ GST_DEBUG ("pt %u, dist %u", pt, dist);
+
+ return pt;
+}
+
+static gint64
+granule_to_granulepos_vp8 (GstOggStream * pad, gint64 granule,
+ gint64 keyframe_granule)
+{
+ /* FIXME: This requires to look into the content of the packets
+ * because the simple granule counter doesn't know about invisible
+ * frames...
+ */
+ return -1;
+}
+
+/* Check if this packet contains an invisible frame or not */
+static gint64
+packet_duration_vp8 (GstOggStream * pad, ogg_packet * packet)
+{
+ guint32 hdr;
+
+ if (packet->bytes < 3)
+ return 0;
+
+ hdr = GST_READ_UINT24_LE (packet->packet);
+
+ return (((hdr >> 4) & 1) != 0) ? 1 : 0;
+}
+
+static gint64
+granulepos_to_key_granule_vp8 (GstOggStream * pad, gint64 granulepos)
+{
+ guint64 gp = granulepos;
+ guint64 pts = (gp >> 32);
+ guint32 dist = (gp >> 3) & 0x07ffffff;
+
+ if (granulepos == -1 || granulepos == 0)
+ return granulepos;
+
+ if (dist > pts)
+ return 0;
+
+ return pts - dist;
+}
+
+static gboolean
+is_header_vp8 (GstOggStream * pad, ogg_packet * packet)
+{
+ if (packet->bytes >= 5 && packet->packet[0] == 0x4F &&
+ packet->packet[1] == 0x56 && packet->packet[2] == 0x50 &&
+ packet->packet[3] == 0x38 && packet->packet[4] == 0x30)
+ return TRUE;
+ return FALSE;
+}
+
+static void
+extract_tags_vp8 (GstOggStream * pad, ogg_packet * packet)
+{
+ if (packet->bytes >= 7 && memcmp (packet->packet, "OVP80\2 ", 7) == 0) {
+ tag_list_from_vorbiscomment_packet (packet,
+ (const guint8 *) "OVP80\2 ", 7, &pad->taglist);
+ }
+}
+
+/* vorbis */
+
+static gboolean
+setup_vorbis_mapper (GstOggStream * pad, ogg_packet * packet)
+{
+ guint8 *data = packet->packet;
+ guint chans;
+
+ data += 1 + 6;
+ pad->version = GST_READ_UINT32_LE (data);
+ data += 4;
+ chans = GST_READ_UINT8 (data);
+ data += 1;
+ pad->granulerate_n = GST_READ_UINT32_LE (data);
+ pad->granulerate_d = 1;
+ pad->granuleshift = 0;
+ pad->last_size = 0;
+ GST_LOG ("sample rate: %d", pad->granulerate_n);
+
+ data += 4;
+ pad->bitrate_upper = GST_READ_UINT32_LE (data);
+ data += 4;
+ pad->bitrate_nominal = GST_READ_UINT32_LE (data);
+ data += 4;
+ pad->bitrate_lower = GST_READ_UINT32_LE (data);
+
+ if (pad->bitrate_nominal > 0)
+ pad->bitrate = pad->bitrate_nominal;
+
+ if (pad->bitrate_upper > 0 && !pad->bitrate)
+ pad->bitrate = pad->bitrate_upper;
+
+ if (pad->bitrate_lower > 0 && !pad->bitrate)
+ pad->bitrate = pad->bitrate_lower;
+
+ GST_LOG ("bit rate: %d", pad->bitrate);
+
+ pad->n_header_packets = 3;
+
+ if (pad->granulerate_n == 0)
+ return FALSE;
+
+ parse_vorbis_header_packet (pad, packet);
+
+ pad->caps = gst_caps_new_simple ("audio/x-vorbis",
+ "rate", G_TYPE_INT, pad->granulerate_n, "channels", G_TYPE_INT, chans,
+ NULL);
+
+ return TRUE;
+}
+
+static gboolean
+is_header_vorbis (GstOggStream * pad, ogg_packet * packet)
+{
+ if (packet->bytes > 0 && (packet->packet[0] & 0x01) == 0)
+ return FALSE;
+
+ if (packet->packet[0] == 5) {
+ parse_vorbis_setup_packet (pad, packet);
+ }
+
+ return TRUE;
+}
+
+static void
+extract_tags_vorbis (GstOggStream * pad, ogg_packet * packet)
+{
+ if (packet->bytes == 0 || (packet->packet[0] & 0x01) == 0)
+ return;
+
+ if (((guint8 *) (packet->packet))[0] == 0x03) {
+ tag_list_from_vorbiscomment_packet (packet,
+ (const guint8 *) "\003vorbis", 7, &pad->taglist);
+
+ if (!pad->taglist)
+ pad->taglist = gst_tag_list_new ();
+
+ gst_tag_list_add (pad->taglist, GST_TAG_MERGE_REPLACE,
+ GST_TAG_ENCODER_VERSION, pad->version, NULL);
+
+ if (pad->bitrate_nominal > 0)
+ gst_tag_list_add (pad->taglist, GST_TAG_MERGE_REPLACE,
+ GST_TAG_NOMINAL_BITRATE, (guint) pad->bitrate_nominal, NULL);
+
+ if (pad->bitrate_upper > 0)
+ gst_tag_list_add (pad->taglist, GST_TAG_MERGE_REPLACE,
+ GST_TAG_MAXIMUM_BITRATE, (guint) pad->bitrate_upper, NULL);
+
+ if (pad->bitrate_lower > 0)
+ gst_tag_list_add (pad->taglist, GST_TAG_MERGE_REPLACE,
+ GST_TAG_MINIMUM_BITRATE, (guint) pad->bitrate_lower, NULL);
+
+ if (pad->bitrate)
+ gst_tag_list_add (pad->taglist, GST_TAG_MERGE_REPLACE,
+ GST_TAG_BITRATE, (guint) pad->bitrate, NULL);
+ }
+}
+
+static gint64
+packet_duration_vorbis (GstOggStream * pad, ogg_packet * packet)
+{
+ int mode;
+ int size;
+ int duration;
+
+ if (packet->packet[0] & 1)
+ return 0;
+
+ mode = (packet->packet[0] >> 1) & ((1 << pad->vorbis_log2_num_modes) - 1);
+ size = pad->vorbis_mode_sizes[mode] ? pad->long_size : pad->short_size;
+
+ if (pad->last_size == 0) {
+ duration = 0;
+ } else {
+ duration = pad->last_size / 4 + size / 4;
+ }
+ pad->last_size = size;
+
+ GST_DEBUG ("duration %d", (int) duration);
+
+ return duration;
+}
+
+/* speex */
+
+
+static gboolean
+setup_speex_mapper (GstOggStream * pad, ogg_packet * packet)
+{
+ guint8 *data = packet->packet;
+ guint chans;
+
+ data += 8 + 20 + 4 + 4;
+ pad->granulerate_n = GST_READ_UINT32_LE (data);
+ pad->granulerate_d = 1;
+ pad->granuleshift = 0;
+
+ data += 4 + 4 + 4;
+ chans = GST_READ_UINT32_LE (data);
+ data += 4;
+ pad->bitrate = GST_READ_UINT32_LE (data);
+
+ GST_LOG ("sample rate: %d, channels: %u", pad->granulerate_n, chans);
+ GST_LOG ("bit rate: %d", pad->bitrate);
+
+ pad->n_header_packets = GST_READ_UINT32_LE (packet->packet + 68) + 2;
+ pad->frame_size = GST_READ_UINT32_LE (packet->packet + 64) *
+ GST_READ_UINT32_LE (packet->packet + 56);
+
+ if (pad->granulerate_n == 0)
+ return FALSE;
+
+ pad->caps = gst_caps_new_simple ("audio/x-speex", "rate", G_TYPE_INT,
+ pad->granulerate_n, "channels", G_TYPE_INT, chans, NULL);
+
+ return TRUE;
+}
+
+static void
+extract_tags_count (GstOggStream * pad, ogg_packet * packet)
+{
+ /* packet 2 must be comment packet */
+ if (packet->bytes > 0 && pad->n_header_packets_seen == 1) {
+ tag_list_from_vorbiscomment_packet (packet, NULL, 0, &pad->taglist);
+
+ if (!pad->taglist)
+ pad->taglist = gst_tag_list_new ();
+
+ if (pad->bitrate)
+ gst_tag_list_add (pad->taglist, GST_TAG_MERGE_REPLACE,
+ GST_TAG_BITRATE, (guint) pad->bitrate, NULL);
+ }
+}
+
+
+/* flac */
+
+static gboolean
+setup_fLaC_mapper (GstOggStream * pad, ogg_packet * packet)
+{
+ pad->granulerate_n = 0;
+ pad->granulerate_d = 1;
+ pad->granuleshift = 0;
+
+ pad->n_header_packets = 3;
+
+ pad->caps = gst_caps_new_simple ("audio/x-flac", NULL);
+
+ return TRUE;
+}
+
+static gboolean
+is_header_fLaC (GstOggStream * pad, ogg_packet * packet)
+{
+ if (pad->n_header_packets_seen == 1) {
+ pad->granulerate_n = (packet->packet[14] << 12) |
+ (packet->packet[15] << 4) | ((packet->packet[16] >> 4) & 0xf);
+ }
+
+ if (pad->n_header_packets_seen < pad->n_header_packets) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+setup_flac_mapper (GstOggStream * pad, ogg_packet * packet)
+{
+ guint8 *data = packet->packet;
+ guint chans;
+
+ /* see http://flac.sourceforge.net/ogg_mapping.html */
+
+ pad->granulerate_n = (GST_READ_UINT32_BE (data + 27) & 0xFFFFF000) >> 12;
+ pad->granulerate_d = 1;
+ pad->granuleshift = 0;
+ chans = ((GST_READ_UINT32_BE (data + 27) & 0x00000E00) >> 9) + 1;
+
+ GST_DEBUG ("sample rate: %d, channels: %u", pad->granulerate_n, chans);
+
+ pad->n_header_packets = GST_READ_UINT16_BE (packet->packet + 7);
+
+ if (pad->granulerate_n == 0)
+ return FALSE;
+
+ pad->caps = gst_caps_new_simple ("audio/x-flac", "rate", G_TYPE_INT,
+ pad->granulerate_n, "channels", G_TYPE_INT, chans, NULL);
+
+ return TRUE;
+}
+
+static gboolean
+is_header_flac (GstOggStream * pad, ogg_packet * packet)
+{
+ return (packet->bytes > 0 && (packet->packet[0] != 0xff));
+}
+
+static gint64
+packet_duration_flac (GstOggStream * pad, ogg_packet * packet)
+{
+ int block_size_index;
+
+ if (packet->bytes < 4)
+ return -1;
+
+ block_size_index = packet->packet[2] >> 4;
+ if (block_size_index == 1)
+ return 192;
+ if (block_size_index >= 2 && block_size_index <= 5) {
+ return 576 << (block_size_index - 2);
+ }
+ if (block_size_index >= 8) {
+ return 256 << (block_size_index - 8);
+ }
+ if (block_size_index == 6 || block_size_index == 7) {
+ guint len, bytes = (block_size_index - 6) + 1;
+ guint8 tmp;
+
+ if (packet->bytes < 4 + 1 + bytes)
+ return -1;
+ tmp = packet->packet[4];
+ /* utf-8 prefix */
+ len = 0;
+ while (tmp & 0x80) {
+ len++;
+ tmp <<= 1;
+ }
+ if (len == 2)
+ return -1;
+ if (len == 0)
+ len++;
+ if (packet->bytes < 4 + len + bytes)
+ return -1;
+ if (bytes == 1) {
+ return packet->packet[4 + len] + 1;
+ } else {
+ return GST_READ_UINT16_BE (packet->packet + 4 + len) + 1;
+ }
+ }
+ return -1;
+}
+
+static void
+extract_tags_flac (GstOggStream * pad, ogg_packet * packet)
+{
+ if (packet->bytes > 4 && ((packet->packet[0] & 0x7F) == 0x4)) {
+ tag_list_from_vorbiscomment_packet (packet,
+ packet->packet, 4, &pad->taglist);
+ }
+}
+
+/* fishead */
+
+static gboolean
+setup_fishead_mapper (GstOggStream * pad, ogg_packet * packet)
+{
+ guint8 *data;
+ gint64 prestime_n, prestime_d;
+ gint64 basetime_n, basetime_d;
+
+ data = packet->packet;
+
+ data += 8; /* header */
+
+ pad->skeleton_major = GST_READ_UINT16_LE (data);
+ data += 2;
+ pad->skeleton_minor = GST_READ_UINT16_LE (data);
+ data += 2;
+
+ prestime_n = (gint64) GST_READ_UINT64_LE (data);
+ data += 8;
+ prestime_d = (gint64) GST_READ_UINT64_LE (data);
+ data += 8;
+ basetime_n = (gint64) GST_READ_UINT64_LE (data);
+ data += 8;
+ basetime_d = (gint64) GST_READ_UINT64_LE (data);
+ data += 8;
+
+ /* FIXME: we don't use basetime anywhere in the demuxer! */
+ if (basetime_d != 0)
+ pad->basetime = gst_util_uint64_scale (GST_SECOND, basetime_n, basetime_d);
+ else
+ pad->basetime = -1;
+
+ if (prestime_d != 0)
+ pad->prestime = gst_util_uint64_scale (GST_SECOND, prestime_n, prestime_d);
+ else
+ pad->prestime = -1;
+
+ /* Ogg Skeleton 3.3+ streams provide additional information in the header */
+ if (packet->bytes >= SKELETON_FISHEAD_3_3_MIN_SIZE && pad->skeleton_major == 3
+ && pad->skeleton_minor > 0) {
+ gint64 firstsampletime_n, firstsampletime_d;
+ gint64 lastsampletime_n, lastsampletime_d;
+ gint64 firstsampletime, lastsampletime;
+ guint64 segment_length, content_offset;
+
+ firstsampletime_n = GST_READ_UINT64_LE (data + 64);
+ firstsampletime_d = GST_READ_UINT64_LE (data + 72);
+ lastsampletime_n = GST_READ_UINT64_LE (data + 80);
+ lastsampletime_d = GST_READ_UINT64_LE (data + 88);
+ segment_length = GST_READ_UINT64_LE (data + 96);
+ content_offset = GST_READ_UINT64_LE (data + 104);
+
+ GST_INFO ("firstsampletime %" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT,
+ firstsampletime_n, firstsampletime_d);
+ GST_INFO ("lastsampletime %" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT,
+ lastsampletime_n, lastsampletime_d);
+ GST_INFO ("segment length %" G_GUINT64_FORMAT, segment_length);
+ GST_INFO ("content offset %" G_GUINT64_FORMAT, content_offset);
+
+ if (firstsampletime_d > 0)
+ firstsampletime = gst_util_uint64_scale (GST_SECOND,
+ firstsampletime_n, firstsampletime_d);
+ else
+ firstsampletime = 0;
+
+ if (lastsampletime_d > 0)
+ lastsampletime = gst_util_uint64_scale (GST_SECOND,
+ lastsampletime_n, lastsampletime_d);
+ else
+ lastsampletime = 0;
+
+ if (lastsampletime > firstsampletime)
+ pad->total_time = lastsampletime - firstsampletime;
+ else
+ pad->total_time = -1;
+
+ GST_INFO ("skeleton fishead parsed total: %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (pad->total_time));
+ } else if (packet->bytes >= SKELETON_FISHEAD_4_0_MIN_SIZE
+ && pad->skeleton_major == 4) {
+ guint64 segment_length, content_offset;
+
+ segment_length = GST_READ_UINT64_LE (data + 64);
+ content_offset = GST_READ_UINT64_LE (data + 72);
+
+ GST_INFO ("segment length %" G_GUINT64_FORMAT, segment_length);
+ GST_INFO ("content offset %" G_GUINT64_FORMAT, content_offset);
+ } else {
+ pad->total_time = -1;
+ }
+
+ GST_INFO ("skeleton fishead %u.%u parsed (basetime: %" GST_TIME_FORMAT
+ ", prestime: %" GST_TIME_FORMAT ")", pad->skeleton_major,
+ pad->skeleton_minor, GST_TIME_ARGS (pad->basetime),
+ GST_TIME_ARGS (pad->prestime));
+
+ pad->is_skeleton = TRUE;
+ pad->is_sparse = TRUE;
+
+ pad->caps = gst_caps_new_simple ("none/none", NULL);
+
+ return TRUE;
+}
+
+gboolean
+gst_ogg_map_parse_fisbone (GstOggStream * pad, const guint8 * data, guint size,
+ guint32 * serialno, GstOggSkeleton * type)
+{
+ GstOggSkeleton stype;
+ guint serial_offset;
+
+ if (size < SKELETON_FISBONE_MIN_SIZE) {
+ GST_WARNING ("small fisbone packet of size %d, ignoring", size);
+ return FALSE;
+ }
+
+ if (memcmp (data, "fisbone\0", 8) == 0) {
+ GST_INFO ("got fisbone packet");
+ stype = GST_OGG_SKELETON_FISBONE;
+ serial_offset = 12;
+ } else if (memcmp (data, "index\0", 6) == 0) {
+ GST_INFO ("got index packet");
+ stype = GST_OGG_SKELETON_INDEX;
+ serial_offset = 6;
+ } else if (memcmp (data, "fishead\0", 8) == 0) {
+ return FALSE;
+ } else {
+ GST_WARNING ("unknown skeleton packet \"%10.10s\"", data);
+ return FALSE;
+ }
+
+ if (serialno)
+ *serialno = GST_READ_UINT32_LE (data + serial_offset);
+
+ if (type)
+ *type = stype;
+
+ return TRUE;
+}
+
+gboolean
+gst_ogg_map_add_fisbone (GstOggStream * pad, GstOggStream * skel_pad,
+ const guint8 * data, guint size, GstClockTime * p_start_time)
+{
+ GstClockTime start_time;
+ gint64 start_granule;
+
+ if (pad->have_fisbone) {
+ GST_DEBUG ("already have fisbone, ignoring second one");
+ return FALSE;
+ }
+
+ /* skip "fisbone\0" + headers offset + serialno + num headers */
+ data += 8 + 4 + 4 + 4;
+
+ pad->have_fisbone = TRUE;
+
+ /* we just overwrite whatever was set before by the format-specific setup */
+ pad->granulerate_n = GST_READ_UINT64_LE (data);
+ pad->granulerate_d = GST_READ_UINT64_LE (data + 8);
+
+ start_granule = GST_READ_UINT64_LE (data + 16);
+ pad->preroll = GST_READ_UINT32_LE (data + 24);
+ pad->granuleshift = GST_READ_UINT8 (data + 28);
+
+ start_time = granulepos_to_granule_default (pad, start_granule);
+
+ GST_INFO ("skeleton fisbone parsed "
+ "(start time: %" GST_TIME_FORMAT
+ " granulerate_n: %d granulerate_d: %d "
+ " preroll: %" G_GUINT32_FORMAT " granuleshift: %d)",
+ GST_TIME_ARGS (start_time),
+ pad->granulerate_n, pad->granulerate_d, pad->preroll, pad->granuleshift);
+
+ if (p_start_time)
+ *p_start_time = start_time;
+
+ return TRUE;
+}
+
+static gboolean
+read_vlc (const guint8 ** data, guint * size, guint64 * result)
+{
+ gint shift = 0;
+ guint8 byte;
+
+ *result = 0;
+
+ do {
+ if (G_UNLIKELY (*size < 1))
+ return FALSE;
+
+ byte = **data;
+ *result |= ((byte & 0x7f) << shift);
+ shift += 7;
+
+ (*data)++;
+ (*size)--;
+ } while ((byte & 0x80) != 0x80);
+
+ return TRUE;
+}
+
+gboolean
+gst_ogg_map_add_index (GstOggStream * pad, GstOggStream * skel_pad,
+ const guint8 * data, guint size)
+{
+ guint64 i, n_keypoints, isize;
+ guint64 offset, timestamp;
+ guint64 offset_d, timestamp_d;
+
+ if (pad->index) {
+ GST_DEBUG ("already have index, ignoring second one");
+ return TRUE;
+ }
+
+ if ((skel_pad->skeleton_major == 3 && size < 26) ||
+ (skel_pad->skeleton_major == 4 && size < 62)) {
+ GST_WARNING ("small index packet of size %u, ignoring", size);
+ return FALSE;
+ }
+
+ /* skip "index\0" + serialno */
+ data += 6 + 4;
+ size -= 6 + 4;
+
+ n_keypoints = GST_READ_UINT64_LE (data);
+
+ data += 8;
+ size -= 8;
+
+ pad->kp_denom = GST_READ_UINT64_LE (data);
+ if (pad->kp_denom == 0)
+ pad->kp_denom = 1;
+
+ data += 8;
+ size -= 8;
+
+ if (skel_pad->skeleton_major == 4) {
+ gint64 firstsampletime_n;
+ gint64 lastsampletime_n;
+ gint64 firstsampletime, lastsampletime;
+
+ firstsampletime_n = GST_READ_UINT64_LE (data + 0);
+ lastsampletime_n = GST_READ_UINT64_LE (data + 8);
+
+ GST_INFO ("firstsampletime %" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT,
+ firstsampletime_n, pad->kp_denom);
+ GST_INFO ("lastsampletime %" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT,
+ lastsampletime_n, pad->kp_denom);
+
+ firstsampletime = gst_util_uint64_scale (GST_SECOND,
+ firstsampletime_n, pad->kp_denom);
+ lastsampletime = gst_util_uint64_scale (GST_SECOND,
+ lastsampletime_n, pad->kp_denom);
+
+ if (lastsampletime > firstsampletime)
+ pad->total_time = lastsampletime - firstsampletime;
+ else
+ pad->total_time = -1;
+
+ GST_INFO ("skeleton index parsed total: %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (pad->total_time));
+
+ data += 16;
+ size -= 16;
+ }
+
+ GST_INFO ("skeleton index has %" G_GUINT64_FORMAT " keypoints, denom: %"
+ G_GINT64_FORMAT, n_keypoints, pad->kp_denom);
+
+ pad->index = g_try_new (GstOggIndex, n_keypoints);
+ if (!pad->index)
+ return FALSE;
+
+ isize = 0;
+ offset = 0;
+ timestamp = 0;
+
+ for (i = 0; i < n_keypoints; i++) {
+ /* read deltas */
+ if (!read_vlc (&data, &size, &offset_d))
+ break;
+ if (!read_vlc (&data, &size, ×tamp_d))
+ break;
+
+ offset += offset_d;
+ timestamp += timestamp_d;
+
+ pad->index[i].offset = offset;
+ pad->index[i].timestamp = timestamp;
+ isize++;
+
+ GST_INFO ("offset %" G_GUINT64_FORMAT " time %" G_GUINT64_FORMAT, offset,
+ timestamp);
+ }
+ if (isize != n_keypoints) {
+ GST_WARNING ("truncated index, expected %" G_GUINT64_FORMAT ", found %"
+ G_GUINT64_FORMAT, n_keypoints, isize);
+ }
+ pad->n_index = isize;
+ /* try to use the index to estimate the bitrate */
+ if (isize > 2) {
+ guint64 so, eo, st, et, b, t;
+
+ /* get start and end offset and timestamps */
+ so = pad->index[0].offset;
+ st = pad->index[0].timestamp;
+ eo = pad->index[isize - 1].offset;
+ et = pad->index[isize - 1].timestamp;
+
+ b = eo - so;
+ t = et - st;
+
+ GST_DEBUG ("bytes/time %" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT, b, t);
+
+ /* this is the total stream bitrate according to this index */
+ pad->idx_bitrate = gst_util_uint64_scale (8 * b, pad->kp_denom, t);
+
+ GST_DEBUG ("bitrate %" G_GUINT64_FORMAT, pad->idx_bitrate);
+ }
+
+ return TRUE;
+}
+
+static gint
+gst_ogg_index_compare (const GstOggIndex * index, const guint64 * ts,
+ gpointer user_data)
+{
+ if (index->timestamp < *ts)
+ return -1;
+ else if (index->timestamp > *ts)
+ return 1;
+ else
+ return 0;
+}
+
+gboolean
+gst_ogg_map_search_index (GstOggStream * pad, gboolean before,
+ guint64 * timestamp, guint64 * offset)
+{
+ guint64 n_index;
+ guint64 ts;
+ GstOggIndex *best;
+
+ n_index = pad->n_index;
+ if (n_index == 0 || pad->index == NULL)
+ return FALSE;
+
+ ts = gst_util_uint64_scale (*timestamp, pad->kp_denom, GST_SECOND);
+ GST_INFO ("timestamp %" G_GUINT64_FORMAT, ts);
+
+ best =
+ gst_util_array_binary_search (pad->index, n_index, sizeof (GstOggIndex),
+ (GCompareDataFunc) gst_ogg_index_compare, GST_SEARCH_MODE_BEFORE, &ts,
+ NULL);
+
+ if (best == NULL)
+ return FALSE;
+
+ GST_INFO ("found at index %u", (guint) (best - pad->index));
+
+ if (offset)
+ *offset = best->offset;
+ if (timestamp)
+ *timestamp =
+ gst_util_uint64_scale (best->timestamp, GST_SECOND, pad->kp_denom);
+
+ return TRUE;
+}
+
+/* Do we need these for something?
+ * ogm->hdr.size = GST_READ_UINT32_LE (&data[13]);
+ * ogm->hdr.time_unit = GST_READ_UINT64_LE (&data[17]);
+ * ogm->hdr.samples_per_unit = GST_READ_UINT64_LE (&data[25]);
+ * ogm->hdr.default_len = GST_READ_UINT32_LE (&data[33]);
+ * ogm->hdr.buffersize = GST_READ_UINT32_LE (&data[37]);
+ * ogm->hdr.bits_per_sample = GST_READ_UINT32_LE (&data[41]);
+ */
+
+static gboolean
+is_header_ogm (GstOggStream * pad, ogg_packet * packet)
+{
+ if (packet->bytes >= 1 && (packet->packet[0] & 0x01))
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+extract_tags_ogm (GstOggStream * pad, ogg_packet * packet)
+{
+ if (!(packet->packet[0] & 1) && (packet->packet[0] & 3 && pad->is_ogm_text)) {
+ tag_list_from_vorbiscomment_packet (packet,
+ (const guint8 *) "\003vorbis", 7, &pad->taglist);
+ }
+}
+
+static gint64
+packet_duration_ogm (GstOggStream * pad, ogg_packet * packet)
+{
+ const guint8 *data;
+ int samples;
+ int offset;
+ int n;
+
+ data = packet->packet;
+ offset = 1 + (((data[0] & 0xc0) >> 6) | ((data[0] & 0x02) << 1));
+
+ if (offset > packet->bytes) {
+ GST_ERROR ("buffer too small");
+ return -1;
+ }
+
+ samples = 0;
+ for (n = offset - 1; n > 0; n--) {
+ samples = (samples << 8) | data[n];
+ }
+
+ return samples;
+}
+
+static gboolean
+setup_ogmaudio_mapper (GstOggStream * pad, ogg_packet * packet)
+{
+ guint8 *data = packet->packet;
+ guint32 fourcc;
+
+ pad->granulerate_n = GST_READ_UINT64_LE (data + 25);
+ pad->granulerate_d = 1;
+
+ fourcc = GST_READ_UINT32_LE (data + 9);
+ GST_DEBUG ("fourcc: %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc));
+
+ pad->caps = gst_riff_create_audio_caps (fourcc, NULL, NULL, NULL, NULL, NULL);
+
+ GST_LOG ("sample rate: %d", pad->granulerate_n);
+ if (pad->granulerate_n == 0)
+ return FALSE;
+
+ if (pad->caps) {
+ gst_caps_set_simple (pad->caps,
+ "rate", G_TYPE_INT, pad->granulerate_n, NULL);
+ } else {
+ pad->caps = gst_caps_new_simple ("audio/x-ogm-unknown",
+ "fourcc", GST_TYPE_FOURCC, fourcc,
+ "rate", G_TYPE_INT, pad->granulerate_n, NULL);
+ }
+
+ pad->n_header_packets = 1;
+ pad->is_ogm = TRUE;
+
+ return TRUE;
+}
+
+static gboolean
+setup_ogmvideo_mapper (GstOggStream * pad, ogg_packet * packet)
+{
+ guint8 *data = packet->packet;
+ guint32 fourcc;
+ int width, height;
+ gint64 time_unit;
+
+ GST_DEBUG ("time unit %d", GST_READ_UINT32_LE (data + 16));
+ GST_DEBUG ("samples per unit %d", GST_READ_UINT32_LE (data + 24));
+
+ pad->is_video = TRUE;
+ pad->granulerate_n = 10000000;
+ time_unit = GST_READ_UINT64_LE (data + 17);
+ if (time_unit > G_MAXINT || time_unit < G_MININT) {
+ GST_WARNING ("timeunit is out of range");
+ }
+ pad->granulerate_d = (gint) CLAMP (time_unit, G_MININT, G_MAXINT);
+
+ GST_LOG ("fps = %d/%d = %.3f",
+ pad->granulerate_n, pad->granulerate_d,
+ (double) pad->granulerate_n / pad->granulerate_d);
+
+ fourcc = GST_READ_UINT32_LE (data + 9);
+ width = GST_READ_UINT32_LE (data + 45);
+ height = GST_READ_UINT32_LE (data + 49);
+ GST_DEBUG ("fourcc: %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc));
+
+ pad->caps = gst_riff_create_video_caps (fourcc, NULL, NULL, NULL, NULL, NULL);
+
+ if (pad->caps == NULL) {
+ pad->caps = gst_caps_new_simple ("video/x-ogm-unknown",
+ "fourcc", GST_TYPE_FOURCC, fourcc,
+ "framerate", GST_TYPE_FRACTION, pad->granulerate_n,
+ pad->granulerate_d, NULL);
+ } else {
+ gst_caps_set_simple (pad->caps,
+ "framerate", GST_TYPE_FRACTION, pad->granulerate_n,
+ pad->granulerate_d,
+ "width", G_TYPE_INT, width, "height", G_TYPE_INT, height, NULL);
+ }
+ GST_DEBUG ("caps: %" GST_PTR_FORMAT, pad->caps);
+
+ pad->n_header_packets = 1;
+ pad->frame_size = 1;
+ pad->is_ogm = TRUE;
+
+ return TRUE;
+}
+
+static gboolean
+setup_ogmtext_mapper (GstOggStream * pad, ogg_packet * packet)
+{
+ guint8 *data = packet->packet;
+ gint64 time_unit;
+
+ pad->granulerate_n = 10000000;
+ time_unit = GST_READ_UINT64_LE (data + 17);
+ if (time_unit > G_MAXINT || time_unit < G_MININT) {
+ GST_WARNING ("timeunit is out of range");
+ }
+ pad->granulerate_d = (gint) CLAMP (time_unit, G_MININT, G_MAXINT);
+
+ GST_LOG ("fps = %d/%d = %.3f",
+ pad->granulerate_n, pad->granulerate_d,
+ (double) pad->granulerate_n / pad->granulerate_d);
+
+ if (pad->granulerate_d <= 0)
+ return FALSE;
+
+ pad->caps = gst_caps_new_simple ("text/plain", NULL);
+
+ pad->n_header_packets = 1;
+ pad->is_ogm = TRUE;
+ pad->is_ogm_text = TRUE;
+ pad->is_sparse = TRUE;
+
+ return TRUE;
+}
+
+/* PCM */
+
+#define OGGPCM_FMT_S8 0x00000000 /* Signed integer 8 bit */
+#define OGGPCM_FMT_U8 0x00000001 /* Unsigned integer 8 bit */
+#define OGGPCM_FMT_S16_LE 0x00000002 /* Signed integer 16 bit little endian */
+#define OGGPCM_FMT_S16_BE 0x00000003 /* Signed integer 16 bit big endian */
+#define OGGPCM_FMT_S24_LE 0x00000004 /* Signed integer 24 bit little endian */
+#define OGGPCM_FMT_S24_BE 0x00000005 /* Signed integer 24 bit big endian */
+#define OGGPCM_FMT_S32_LE 0x00000006 /* Signed integer 32 bit little endian */
+#define OGGPCM_FMT_S32_BE 0x00000007 /* Signed integer 32 bit big endian */
+
+#define OGGPCM_FMT_ULAW 0x00000010 /* G.711 u-law encoding (8 bit) */
+#define OGGPCM_FMT_ALAW 0x00000011 /* G.711 A-law encoding (8 bit) */
+
+#define OGGPCM_FMT_FLT32_LE 0x00000020 /* IEEE Float [-1,1] 32 bit little endian */
+#define OGGPCM_FMT_FLT32_BE 0x00000021 /* IEEE Float [-1,1] 32 bit big endian */
+#define OGGPCM_FMT_FLT64_LE 0x00000022 /* IEEE Float [-1,1] 64 bit little endian */
+#define OGGPCM_FMT_FLT64_BE 0x00000023 /* IEEE Float [-1,1] 64 bit big endian */
+
+
+static gboolean
+setup_pcm_mapper (GstOggStream * pad, ogg_packet * packet)
+{
+ guint8 *data = packet->packet;
+ int format;
+ int channels;
+ GstCaps *caps;
+
+ pad->granulerate_n = GST_READ_UINT32_LE (data + 16);
+ pad->granulerate_d = 1;
+ GST_LOG ("sample rate: %d", pad->granulerate_n);
+
+ format = GST_READ_UINT32_LE (data + 12);
+ channels = GST_READ_UINT8 (data + 21);
+
+ pad->n_header_packets = 2 + GST_READ_UINT32_LE (data + 24);
+
+ if (pad->granulerate_n == 0)
+ return FALSE;
+
+ switch (format) {
+ case OGGPCM_FMT_S8:
+ caps = gst_caps_new_simple ("audio/x-raw-int",
+ "depth", G_TYPE_INT, 8,
+ "width", G_TYPE_INT, 8, "signed", G_TYPE_BOOLEAN, TRUE, NULL);
+ break;
+ case OGGPCM_FMT_U8:
+ caps = gst_caps_new_simple ("audio/x-raw-int",
+ "depth", G_TYPE_INT, 8,
+ "width", G_TYPE_INT, 8, "signed", G_TYPE_BOOLEAN, FALSE, NULL);
+ break;
+ case OGGPCM_FMT_S16_LE:
+ caps = gst_caps_new_simple ("audio/x-raw-int",
+ "depth", G_TYPE_INT, 16,
+ "width", G_TYPE_INT, 16,
+ "endianness", G_TYPE_INT, G_LITTLE_ENDIAN,
+ "signed", G_TYPE_BOOLEAN, TRUE, NULL);
+ break;
+ case OGGPCM_FMT_S16_BE:
+ caps = gst_caps_new_simple ("audio/x-raw-int",
+ "depth", G_TYPE_INT, 16,
+ "width", G_TYPE_INT, 16,
+ "endianness", G_TYPE_INT, G_BIG_ENDIAN,
+ "signed", G_TYPE_BOOLEAN, TRUE, NULL);
+ break;
+ case OGGPCM_FMT_S24_LE:
+ caps = gst_caps_new_simple ("audio/x-raw-int",
+ "depth", G_TYPE_INT, 24,
+ "width", G_TYPE_INT, 24,
+ "endianness", G_TYPE_INT, G_LITTLE_ENDIAN,
+ "signed", G_TYPE_BOOLEAN, TRUE, NULL);
+ break;
+ case OGGPCM_FMT_S24_BE:
+ caps = gst_caps_new_simple ("audio/x-raw-int",
+ "depth", G_TYPE_INT, 24,
+ "width", G_TYPE_INT, 24,
+ "endianness", G_TYPE_INT, G_BIG_ENDIAN,
+ "signed", G_TYPE_BOOLEAN, TRUE, NULL);
+ break;
+ case OGGPCM_FMT_S32_LE:
+ caps = gst_caps_new_simple ("audio/x-raw-int",
+ "depth", G_TYPE_INT, 32,
+ "width", G_TYPE_INT, 32,
+ "endianness", G_TYPE_INT, G_LITTLE_ENDIAN,
+ "signed", G_TYPE_BOOLEAN, TRUE, NULL);
+ break;
+ case OGGPCM_FMT_S32_BE:
+ caps = gst_caps_new_simple ("audio/x-raw-int",
+ "depth", G_TYPE_INT, 32,
+ "width", G_TYPE_INT, 32,
+ "endianness", G_TYPE_INT, G_BIG_ENDIAN,
+ "signed", G_TYPE_BOOLEAN, TRUE, NULL);
+ break;
+ case OGGPCM_FMT_ULAW:
+ caps = gst_caps_new_simple ("audio/x-mulaw", NULL);
+ break;
+ case OGGPCM_FMT_ALAW:
+ caps = gst_caps_new_simple ("audio/x-alaw", NULL);
+ break;
+ case OGGPCM_FMT_FLT32_LE:
+ caps = gst_caps_new_simple ("audio/x-raw-float",
+ "width", G_TYPE_INT, 32,
+ "endianness", G_TYPE_INT, G_LITTLE_ENDIAN, NULL);
+ break;
+ case OGGPCM_FMT_FLT32_BE:
+ caps = gst_caps_new_simple ("audio/x-raw-float",
+ "width", G_TYPE_INT, 32,
+ "endianness", G_TYPE_INT, G_BIG_ENDIAN, NULL);
+ break;
+ case OGGPCM_FMT_FLT64_LE:
+ caps = gst_caps_new_simple ("audio/x-raw-float",
+ "width", G_TYPE_INT, 64,
+ "endianness", G_TYPE_INT, G_LITTLE_ENDIAN, NULL);
+ break;
+ case OGGPCM_FMT_FLT64_BE:
+ caps = gst_caps_new_simple ("audio/x-raw-float",
+ "width", G_TYPE_INT, 64,
+ "endianness", G_TYPE_INT, G_BIG_ENDIAN, NULL);
+ break;
+ default:
+ return FALSE;
+ }
+
+ gst_caps_set_simple (caps, "audio/x-raw-int",
+ "rate", G_TYPE_INT, pad->granulerate_n,
+ "channels", G_TYPE_INT, channels, NULL);
+ pad->caps = caps;
+
+ return TRUE;
+}
+
+/* cmml */
+
+static gboolean
+setup_cmml_mapper (GstOggStream * pad, ogg_packet * packet)
+{
+ guint8 *data = packet->packet;
+
+ pad->granulerate_n = GST_READ_UINT64_LE (data + 12);
+ pad->granulerate_d = GST_READ_UINT64_LE (data + 20);
+ pad->granuleshift = data[28];
+ GST_LOG ("sample rate: %d", pad->granulerate_n);
+
+ pad->n_header_packets = 3;
+
+ if (pad->granulerate_n == 0)
+ return FALSE;
+
+ data += 4 + (4 + 4 + 4);
+ GST_DEBUG ("blocksize0: %u", 1 << (data[0] >> 4));
+ GST_DEBUG ("blocksize1: %u", 1 << (data[0] & 0x0F));
+
+ pad->caps = gst_caps_new_simple ("text/x-cmml", NULL);
+ pad->is_sparse = TRUE;
+
+ return TRUE;
+}
+
+/* celt */
+
+static gboolean
+setup_celt_mapper (GstOggStream * pad, ogg_packet * packet)
+{
+ guint8 *data = packet->packet;
+
+ pad->granulerate_n = GST_READ_UINT32_LE (data + 36);
+ pad->granulerate_d = 1;
+ pad->granuleshift = 0;
+ GST_LOG ("sample rate: %d", pad->granulerate_n);
+
+ pad->frame_size = GST_READ_UINT32_LE (packet->packet + 44);
+ pad->n_header_packets = GST_READ_UINT32_LE (packet->packet + 56) + 2;
+
+ if (pad->granulerate_n == 0)
+ return FALSE;
+
+ pad->caps = gst_caps_new_simple ("audio/x-celt",
+ "rate", G_TYPE_INT, pad->granulerate_n, NULL);
+
+ return TRUE;
+}
+
+/* kate */
+
+static gboolean
+setup_kate_mapper (GstOggStream * pad, ogg_packet * packet)
+{
+ guint8 *data = packet->packet;
+ const char *category;
+
+ if (packet->bytes < 64)
+ return FALSE;
+
+ pad->granulerate_n = GST_READ_UINT32_LE (data + 24);
+ pad->granulerate_d = GST_READ_UINT32_LE (data + 28);
+ pad->granuleshift = GST_READ_UINT8 (data + 15);
+ GST_LOG ("sample rate: %d", pad->granulerate_n);
+
+ pad->n_header_packets = GST_READ_UINT8 (data + 11);
+ GST_LOG ("kate header packets: %d", pad->n_header_packets);
+
+ if (pad->granulerate_n == 0)
+ return FALSE;
+
+ category = (const char *) data + 48;
+ if (strcmp (category, "subtitles") == 0 || strcmp (category, "SUB") == 0 ||
+ strcmp (category, "spu-subtitles") == 0 ||
+ strcmp (category, "K-SPU") == 0) {
+ pad->caps = gst_caps_new_simple ("subtitle/x-kate", NULL);
+ } else {
+ pad->caps = gst_caps_new_simple ("application/x-kate", NULL);
+ }
+
+ pad->is_sparse = TRUE;
+
+ return TRUE;
+}
+
+static gint64
+packet_duration_kate (GstOggStream * pad, ogg_packet * packet)
+{
+ gint64 duration;
+
+ if (packet->bytes < 1)
+ return 0;
+
+ switch (packet->packet[0]) {
+ case 0x00: /* text data */
+ if (packet->bytes < 1 + 8 * 2) {
+ duration = 0;
+ } else {
+ duration = GST_READ_UINT64_LE (packet->packet + 1 + 8);
+ if (duration < 0)
+ duration = 0;
+ }
+ break;
+ default:
+ duration = GST_CLOCK_TIME_NONE;
+ break;
+ }
+
+ return duration;
+}
+
+static void
+extract_tags_kate (GstOggStream * pad, ogg_packet * packet)
+{
+ GstTagList *list = NULL;
+
+ if (packet->bytes <= 0)
+ return;
+
+ switch (packet->packet[0]) {
+ case 0x80:{
+ const gchar *canonical;
+ char language[16];
+
+ if (packet->bytes < 64) {
+ GST_WARNING ("Kate ID header packet is less than 64 bytes, ignored");
+ break;
+ }
+
+ /* the language tag is 16 bytes at offset 32, ensure NUL terminator */
+ memcpy (language, packet->packet + 32, 16);
+ language[15] = 0;
+
+ /* language is an ISO 639-1 code or RFC 3066 language code, we
+ * truncate to ISO 639-1 */
+ g_strdelimit (language, NULL, '\0');
+ canonical = gst_tag_get_language_code_iso_639_1 (language);
+ if (canonical) {
+ list = gst_tag_list_new_full (GST_TAG_LANGUAGE_CODE, canonical, NULL);
+ } else {
+ GST_WARNING ("Unknown or invalid language code %s, ignored", language);
+ }
+ break;
+ }
+ case 0x81:
+ tag_list_from_vorbiscomment_packet (packet,
+ (const guint8 *) "\201kate\0\0\0\0", 9, &list);
+ break;
+ default:
+ break;
+ }
+
+ if (list) {
+ if (pad->taglist) {
+ /* ensure the comment packet cannot override the category/language
+ from the identification header */
+ gst_tag_list_insert (pad->taglist, list, GST_TAG_MERGE_KEEP_ALL);
+ } else
+ pad->taglist = list;
+ }
+}
+
+
+/* *INDENT-OFF* */
+/* indent hates our freedoms */
+const GstOggMap mappers[] = {
+ {
+ "\200theora", 7, 42,
+ "video/x-theora",
+ setup_theora_mapper,
+ granulepos_to_granule_theora,
+ granule_to_granulepos_default,
+ is_keyframe_theora,
+ is_header_theora,
+ packet_duration_constant,
+ NULL,
+ extract_tags_theora
+ },
+ {
+ "\001vorbis", 7, 22,
+ "audio/x-vorbis",
+ setup_vorbis_mapper,
+ granulepos_to_granule_default,
+ granule_to_granulepos_default,
+ is_keyframe_true,
+ is_header_vorbis,
+ packet_duration_vorbis,
+ NULL,
+ extract_tags_vorbis
+ },
+ {
+ "Speex", 5, 80,
+ "audio/x-speex",
+ setup_speex_mapper,
+ granulepos_to_granule_default,
+ granule_to_granulepos_default,
+ is_keyframe_true,
+ is_header_count,
+ packet_duration_constant,
+ NULL,
+ extract_tags_count
+ },
+ {
+ "PCM ", 8, 0,
+ "audio/x-raw-int",
+ setup_pcm_mapper,
+ NULL,
+ NULL,
+ NULL,
+ is_header_count,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ "CMML\0\0\0\0", 8, 0,
+ "text/x-cmml",
+ setup_cmml_mapper,
+ NULL,
+ NULL,
+ NULL,
+ is_header_count,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ "Annodex", 7, 0,
+ "application/x-annodex",
+ setup_fishead_mapper,
+ granulepos_to_granule_default,
+ granule_to_granulepos_default,
+ NULL,
+ is_header_count,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ "fishead", 7, 64,
+ "application/octet-stream",
+ setup_fishead_mapper,
+ NULL,
+ NULL,
+ NULL,
+ is_header_true,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ "fLaC", 4, 0,
+ "audio/x-flac",
+ setup_fLaC_mapper,
+ granulepos_to_granule_default,
+ granule_to_granulepos_default,
+ is_keyframe_true,
+ is_header_fLaC,
+ packet_duration_flac,
+ NULL,
+ NULL
+ },
+ {
+ "\177FLAC", 5, 36,
+ "audio/x-flac",
+ setup_flac_mapper,
+ granulepos_to_granule_default,
+ granule_to_granulepos_default,
+ is_keyframe_true,
+ is_header_flac,
+ packet_duration_flac,
+ NULL,
+ extract_tags_flac
+ },
+ {
+ "AnxData", 7, 0,
+ "application/octet-stream",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ "CELT ", 8, 0,
+ "audio/x-celt",
+ setup_celt_mapper,
+ granulepos_to_granule_default,
+ granule_to_granulepos_default,
+ NULL,
+ is_header_count,
+ packet_duration_constant,
+ NULL,
+ extract_tags_count
+ },
+ {
+ "\200kate\0\0\0", 8, 0,
+ "text/x-kate",
+ setup_kate_mapper,
+ granulepos_to_granule_default,
+ granule_to_granulepos_default,
+ NULL,
+ is_header_count,
+ packet_duration_kate,
+ NULL,
+ extract_tags_kate
+ },
+ {
+ "BBCD\0", 5, 13,
+ "video/x-dirac",
+ setup_dirac_mapper,
+ granulepos_to_granule_dirac,
+ granule_to_granulepos_dirac,
+ is_keyframe_dirac,
+ is_header_count,
+ packet_duration_constant,
+ granulepos_to_key_granule_dirac,
+ NULL
+ },
+ {
+ "OVP80\1\1", 7, 4,
+ "video/x-vp8",
+ setup_vp8_mapper,
+ granulepos_to_granule_vp8,
+ granule_to_granulepos_vp8,
+ is_keyframe_vp8,
+ is_header_vp8,
+ packet_duration_vp8,
+ granulepos_to_key_granule_vp8,
+ extract_tags_vp8
+ },
+ {
+ "\001audio\0\0\0", 9, 53,
+ "application/x-ogm-audio",
+ setup_ogmaudio_mapper,
+ granulepos_to_granule_default,
+ granule_to_granulepos_default,
+ is_keyframe_true,
+ is_header_ogm,
+ packet_duration_ogm,
+ NULL,
+ NULL
+ },
+ {
+ "\001video\0\0\0", 9, 53,
+ "application/x-ogm-video",
+ setup_ogmvideo_mapper,
+ granulepos_to_granule_default,
+ granule_to_granulepos_default,
+ NULL,
+ is_header_ogm,
+ packet_duration_constant,
+ NULL,
+ NULL
+ },
+ {
+ "\001text\0\0\0", 9, 9,
+ "application/x-ogm-text",
+ setup_ogmtext_mapper,
+ granulepos_to_granule_default,
+ granule_to_granulepos_default,
+ is_keyframe_true,
+ is_header_ogm,
+ packet_duration_ogm,
+ NULL,
+ extract_tags_ogm
+ }
+};
+/* *INDENT-ON* */
+
+gboolean
+gst_ogg_stream_setup_map (GstOggStream * pad, ogg_packet * packet)
+{
+ int i;
+ gboolean ret;
+
+ for (i = 0; i < G_N_ELEMENTS (mappers); i++) {
+ if (packet->bytes >= mappers[i].min_packet_size &&
+ packet->bytes >= mappers[i].id_length &&
+ memcmp (packet->packet, mappers[i].id, mappers[i].id_length) == 0) {
+
+ GST_DEBUG ("found mapper for '%s'", mappers[i].id);
+
+ if (mappers[i].setup_func)
+ ret = mappers[i].setup_func (pad, packet);
+ else
+ continue;
+
+ if (ret) {
+ GST_DEBUG ("got stream type %" GST_PTR_FORMAT, pad->caps);
+ pad->map = i;
+ return TRUE;
+ } else {
+ GST_WARNING ("mapper '%s' did not accept setup header",
+ mappers[i].media_type);
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+gboolean
+gst_ogg_stream_setup_map_from_caps_headers (GstOggStream * pad,
+ const GstCaps * caps)
+{
+ const GstStructure *structure;
+ const GstBuffer *buf;
+ const GValue *streamheader;
+ const GValue *first_element;
+ ogg_packet packet;
+
+ GST_INFO ("Checking streamheader on caps %" GST_PTR_FORMAT, caps);
+
+ if (caps == NULL)
+ return FALSE;
+
+ structure = gst_caps_get_structure (caps, 0);
+ streamheader = gst_structure_get_value (structure, "streamheader");
+
+ if (streamheader == NULL) {
+ GST_LOG ("no streamheader field in caps %" GST_PTR_FORMAT, caps);
+ return FALSE;
+ }
+
+ if (!GST_VALUE_HOLDS_ARRAY (streamheader)) {
+ GST_ERROR ("streamheader field not an array, caps: %" GST_PTR_FORMAT, caps);
+ return FALSE;
+ }
+
+ if (gst_value_array_get_size (streamheader) == 0) {
+ GST_ERROR ("empty streamheader field in caps %" GST_PTR_FORMAT, caps);
+ return FALSE;
+ }
+
+ first_element = gst_value_array_get_value (streamheader, 0);
+
+ if (!GST_VALUE_HOLDS_BUFFER (first_element)) {
+ GST_ERROR ("first streamheader not a buffer, caps: %" GST_PTR_FORMAT, caps);
+ return FALSE;
+ }
+
+ buf = gst_value_get_buffer (first_element);
+ if (buf == NULL || GST_BUFFER_SIZE (buf) == 0) {
+ GST_ERROR ("invalid first streamheader buffer");
+ return FALSE;
+ }
+
+ GST_MEMDUMP ("streamheader", GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
+
+ packet.packet = GST_BUFFER_DATA (buf);
+ packet.bytes = GST_BUFFER_SIZE (buf);
+
+ GST_INFO ("Found headers on caps, using those to determine type");
+ return gst_ogg_stream_setup_map (pad, &packet);
+}