mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-28 03:00:35 +00:00
c956c5fd00
The codec setup headers are a lot more likely to have correct information, especially as it's easy to remux a skeleton in a file where streams don't have the same parameters (I've even seen a file with two skeletons). Still, this is useful in the case we have a codec we can't decode, so we can at least (theoretically) convert granpos to time, so we discard this information if the codec setup has already provided it. This fixes playback on (at lesat) the original archive.org encoding of "The Night of the Living Dead" (now replaced by another encoding). https://bugzilla.gnome.org/show_bug.cgi?id=612443
2143 lines
55 KiB
C
2143 lines
55 KiB
C
/* 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 %s granulepos to granule",
|
|
gst_ogg_stream_get_media_type (pad));
|
|
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 %s granule to granulepos",
|
|
gst_ogg_stream_get_media_type (pad));
|
|
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 keyframeness for %s granulepos",
|
|
gst_ogg_stream_get_media_type (pad));
|
|
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 headerness of %s packet",
|
|
gst_ogg_stream_get_media_type (pad));
|
|
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 %s packet duration",
|
|
gst_ogg_stream_get_media_type (pad));
|
|
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);
|
|
}
|
|
|
|
const char *
|
|
gst_ogg_stream_get_media_type (GstOggStream * pad)
|
|
{
|
|
const GstCaps *caps = pad->caps;
|
|
const GstStructure *structure;
|
|
if (!caps)
|
|
return NULL;
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
if (!structure)
|
|
return NULL;
|
|
return gst_structure_get_name (structure);
|
|
}
|
|
|
|
/* 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->always_flush_page = 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->preroll = 2;
|
|
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->bytes == 0 || 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 ("application/x-ogg-skeleton", 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 != 0 && size < SKELETON_FISBONE_MIN_SIZE) {
|
|
GST_WARNING ("small fisbone packet of size %d, ignoring", size);
|
|
return FALSE;
|
|
}
|
|
|
|
if (size == 0) {
|
|
/* Skeleton EOS packet is zero bytes */
|
|
return FALSE;
|
|
} else 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 don't overwrite whatever was set before by the format-specific
|
|
setup: skeleton contains wrong information sometimes, and the codec
|
|
headers are authoritative.
|
|
So we only gather information that was not already filled out by
|
|
the mapper setup. This should hopefully allow handling unknown
|
|
streams a bit better, while not trashing correct setup from bad
|
|
skeleton data. */
|
|
if (pad->granulerate_n == 0 || pad->granulerate_d == 0) {
|
|
pad->granulerate_n = GST_READ_UINT64_LE (data);
|
|
pad->granulerate_d = GST_READ_UINT64_LE (data + 8);
|
|
}
|
|
if (pad->granuleshift < 0) {
|
|
pad->granuleshift = GST_READ_UINT8 (data + 28);
|
|
}
|
|
|
|
start_granule = GST_READ_UINT64_LE (data + 16);
|
|
pad->preroll = GST_READ_UINT32_LE (data + 24);
|
|
|
|
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->always_flush_page = TRUE;
|
|
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;
|
|
pad->always_flush_page = 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);
|
|
}
|