summaryrefslogtreecommitdiffstats
path: root/gst/matroska/matroska-mux.c
diff options
context:
space:
mode:
authorTim-Philipp Müller <tim@centricular.net>2006-05-23 13:44:11 +0000
committerTim-Philipp Müller <tim@centricular.net>2006-05-23 13:44:11 +0000
commit4e012bac91ced714fee133f527a802acb9ce4486 (patch)
tree8bbe658f8ea9c5086f711da9b0232fe6c78ad282 /gst/matroska/matroska-mux.c
parent3ba744c2aa53e7b4e17d8e27c9bc6398fd36341c (diff)
gst/matroska/: Add support for muxing/demuxing theora video (#342448; too bad none of the usual linux players can act...
Original commit message from CVS: * gst/matroska/matroska-demux.c: (gst_matroska_demux_add_stream), (gst_matroska_demux_push_xiph_codec_priv_data), (gst_matroska_demux_parse_blockgroup_or_simpleblock), (gst_matroska_demux_video_caps), (gst_matroska_demux_audio_caps): * gst/matroska/matroska-ids.h: * gst/matroska/matroska-mux.c: (gst_matroska_mux_base_init), (gst_matroska_mux_video_pad_setcaps), (xiph3_streamheader_to_codecdata), (vorbis_streamheader_to_codecdata), (theora_streamheader_to_codecdata), (gst_matroska_mux_audio_pad_setcaps), (gst_matroska_mux_write_data): Add support for muxing/demuxing theora video (#342448; too bad none of the usual linux players can actually play this). Playback in GStreamer will require additional changes to theoradec in -base. Refactor streamheaders <=> CodecPrivateData code a bit; some small cleanups.
Diffstat (limited to 'gst/matroska/matroska-mux.c')
-rw-r--r--gst/matroska/matroska-mux.c349
1 files changed, 226 insertions, 123 deletions
diff --git a/gst/matroska/matroska-mux.c b/gst/matroska/matroska-mux.c
index 96d2e1a6..2b76df07 100644
--- a/gst/matroska/matroska-mux.c
+++ b/gst/matroska/matroska-mux.c
@@ -35,16 +35,9 @@ GST_DEBUG_CATEGORY_STATIC (matroskamux_debug);
enum
{
- /* FILL ME */
- LAST_SIGNAL
-};
-
-enum
-{
ARG_0,
ARG_WRITING_APP,
ARG_MATROSKA_VERSION
- /* FILL ME */
};
static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
@@ -82,6 +75,7 @@ static GstStaticPadTemplate videosink_templ =
COMMON_VIDEO_CAPS "; "
"image/jpeg, "
COMMON_VIDEO_CAPS "; "
+ "video/x-theora; "
"video/x-raw-yuv, "
"format = (fourcc) { YUY2, I420 }, " COMMON_VIDEO_CAPS)
);
@@ -127,15 +121,8 @@ GST_STATIC_PAD_TEMPLATE ("subtitle_%d",
static GArray *used_uids;
-static void
-_do_init (GType matroskamux_type)
-{
- GST_DEBUG_CATEGORY_INIT (matroskamux_debug, "matroskamux", 0,
- "Matroska muxer");
-}
-
-GST_BOILERPLATE_FULL (GstMatroskaMux, gst_matroska_mux, GstElement,
- GST_TYPE_ELEMENT, _do_init);
+GST_BOILERPLATE (GstMatroskaMux, gst_matroska_mux, GstElement,
+ GST_TYPE_ELEMENT);
/* Matroska muxer destructor */
static void gst_matroska_mux_finalize (GObject * object);
@@ -166,7 +153,10 @@ static void gst_matroska_mux_reset (GstElement * element);
/* uid generation */
static guint32 gst_matroska_mux_create_uid ();
-/*static guint gst_matroska_mux_signals[LAST_SIGNAL] = { 0 };*/
+static gboolean theora_streamheader_to_codecdata (const GValue * streamheader,
+ GstMatroskaTrackContext * context);
+static gboolean vorbis_streamheader_to_codecdata (const GValue * streamheader,
+ GstMatroskaTrackContext * context);
static void
gst_matroska_mux_base_init (gpointer g_class)
@@ -187,6 +177,9 @@ gst_matroska_mux_base_init (gpointer g_class)
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&src_templ));
gst_element_class_set_details (element_class, &gst_matroska_mux_details);
+
+ GST_DEBUG_CATEGORY_INIT (matroskamux_debug, "matroskamux", 0,
+ "Matroska muxer");
}
static void
@@ -427,12 +420,15 @@ gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps)
{
GstMatroskaTrackContext *context = NULL;
GstMatroskaTrackVideoContext *videocontext;
+ GstMatroskaMux *mux;
GstMatroskaPad *collect_pad;
const gchar *mimetype;
gint width, height, pixel_width, pixel_height;
const GValue *framerate;
GstStructure *structure;
+ mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
+
/* find context */
collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
g_assert (collect_pad);
@@ -446,6 +442,11 @@ gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps)
mimetype = gst_structure_get_name (structure);
+ if (!strcmp (mimetype, "video/x-theora")) {
+ /* we'll extract the details later from the theora identification header */
+ goto skip_details;
+ }
+
/* get general properties */
gst_structure_get_int (structure, "width", &width);
gst_structure_get_int (structure, "height", &height);
@@ -476,6 +477,8 @@ gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps)
videocontext->display_height = 0;
}
+skip_details:
+
videocontext->asr_mode = GST_MATROSKA_ASPECT_RATIO_MODE_FREE;
videocontext->eye_mode = GST_MATROSKA_EYE_MODE_MONO;
videocontext->fourcc = 0;
@@ -582,6 +585,24 @@ gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps)
}
return TRUE;
+ } else if (!strcmp (mimetype, "video/x-theora")) {
+ const GValue *streamheader;
+
+ context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_THEORA);
+
+ if (context->codec_priv != NULL) {
+ g_free (context->codec_priv);
+ context->codec_priv = NULL;
+ context->codec_priv_size = 0;
+ }
+
+ streamheader = gst_structure_get_value (structure, "streamheader");
+ if (!theora_streamheader_to_codecdata (streamheader, context)) {
+ GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
+ ("theora stream headers missing or malformed"));
+ return FALSE;
+ }
+ return TRUE;
} else if (!strcmp (mimetype, "video/mpeg")) {
gint mpegversion;
@@ -610,6 +631,182 @@ gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps)
return FALSE;
}
+static gboolean
+xiph3_streamheader_to_codecdata (const GValue * streamheader,
+ GstMatroskaTrackContext * context, GstBuffer ** p_buf0)
+{
+ GstBuffer *buf[3];
+ GArray *bufarr;
+ guint8 *priv_data;
+ guint i, offset, priv_data_size;
+
+ if (streamheader == NULL)
+ goto no_stream_headers;
+
+ if (G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY)
+ goto wrong_type;
+
+ bufarr = g_value_peek_pointer (streamheader);
+ if (bufarr->len != 3)
+ goto wrong_count;
+
+ context->xiph_headers_to_skip = bufarr->len;
+
+ for (i = 0; i < 3; i++) {
+ GValue *bufval = &g_array_index (bufarr, GValue, i);
+
+ if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER)
+ goto wrong_content_type;
+
+ buf[i] = g_value_peek_pointer (bufval);
+ }
+
+ priv_data_size = 1;
+ priv_data_size += GST_BUFFER_SIZE (buf[0]) / 0xff + 1;
+ priv_data_size += GST_BUFFER_SIZE (buf[1]) / 0xff + 1;
+
+ for (i = 0; i < 3; ++i) {
+ priv_data_size += GST_BUFFER_SIZE (buf[i]);
+ }
+
+ priv_data = g_malloc0 (priv_data_size);
+
+ priv_data[0] = 2;
+ offset = 1;
+
+ for (i = 0; i < GST_BUFFER_SIZE (buf[0]) / 0xff; ++i) {
+ priv_data[offset++] = 0xff;
+ }
+ priv_data[offset++] = GST_BUFFER_SIZE (buf[0]) % 0xff;
+
+ for (i = 0; i < GST_BUFFER_SIZE (buf[1]) / 0xff; ++i) {
+ priv_data[offset++] = 0xff;
+ }
+ priv_data[offset++] = GST_BUFFER_SIZE (buf[1]) % 0xff;
+
+ for (i = 0; i < 3; ++i) {
+ memcpy (priv_data + offset, GST_BUFFER_DATA (buf[i]),
+ GST_BUFFER_SIZE (buf[i]));
+ offset += GST_BUFFER_SIZE (buf[i]);
+ }
+
+ context->codec_priv = priv_data;
+ context->codec_priv_size = priv_data_size;
+
+ if (p_buf0)
+ *p_buf0 = gst_buffer_ref (buf[0]);
+
+ return TRUE;
+
+/* ERRORS */
+no_stream_headers:
+ {
+ GST_WARNING ("required streamheaders missing in sink caps!");
+ return FALSE;
+ }
+wrong_type:
+ {
+ GST_WARNING ("streamheaders are not a GST_TYPE_ARRAY, but a %s",
+ G_VALUE_TYPE_NAME (streamheader));
+ return FALSE;
+ }
+wrong_count:
+ {
+ GST_WARNING ("got %u streamheaders, not 3 as expected", bufarr->len);
+ return FALSE;
+ }
+wrong_content_type:
+ {
+ GST_WARNING ("streamheaders array does not contain GstBuffers");
+ return FALSE;
+ }
+}
+
+static gboolean
+vorbis_streamheader_to_codecdata (const GValue * streamheader,
+ GstMatroskaTrackContext * context)
+{
+ GstBuffer *buf0 = NULL;
+
+ if (!xiph3_streamheader_to_codecdata (streamheader, context, &buf0))
+ return FALSE;
+
+ if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 4) {
+ GST_WARNING ("First vorbis header too small, ignoring");
+ } else {
+ if (memcmp (GST_BUFFER_DATA (buf0) + 1, "vorbis", 6) == 0) {
+ GstMatroskaTrackAudioContext *audiocontext;
+ guint8 *hdr;
+
+ hdr = GST_BUFFER_DATA (buf0) + 1 + 6 + 4;
+ audiocontext = (GstMatroskaTrackAudioContext *) context;
+ audiocontext->channels = GST_READ_UINT8 (hdr);
+ audiocontext->samplerate = GST_READ_UINT32_LE (hdr + 1);
+ }
+ }
+
+ if (buf0)
+ gst_buffer_unref (buf0);
+
+ return TRUE;
+}
+
+static gboolean
+theora_streamheader_to_codecdata (const GValue * streamheader,
+ GstMatroskaTrackContext * context)
+{
+ GstBuffer *buf0 = NULL;
+
+ if (!xiph3_streamheader_to_codecdata (streamheader, context, &buf0))
+ return FALSE;
+
+ if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 26) {
+ GST_WARNING ("First theora header too small, ignoring");
+ } else if (memcmp (GST_BUFFER_DATA (buf0), "\200theora\003\002", 9) != 0) {
+ GST_WARNING ("First header not a theora identification header, ignoring");
+ } else {
+ GstMatroskaTrackVideoContext *videocontext;
+ guint fps_num, fps_denom, par_num, par_denom;
+ guint8 *hdr;
+
+ hdr = GST_BUFFER_DATA (buf0) + 1 + 6 + 3 + 2 + 2;
+
+ videocontext = (GstMatroskaTrackVideoContext *) context;
+ videocontext->pixel_width = GST_READ_UINT32_BE (hdr) >> 8;
+ videocontext->pixel_height = GST_READ_UINT32_BE (hdr + 3) >> 8;
+ hdr += 3 + 3 + 1 + 1;
+ fps_num = GST_READ_UINT32_BE (hdr);
+ fps_denom = GST_READ_UINT32_BE (hdr + 4);
+ context->default_duration = gst_util_uint64_scale_int (GST_SECOND,
+ fps_denom, fps_num);
+ hdr += 4 + 4;
+ par_num = GST_READ_UINT32_BE (hdr) >> 8;
+ par_denom = GST_READ_UINT32_BE (hdr + 3) >> 8;
+ if (par_num > 0 && par_num > 0) {
+ if (par_num > par_denom) {
+ videocontext->display_width =
+ videocontext->pixel_width * par_num / par_denom;
+ videocontext->display_height = videocontext->pixel_height;
+ } else if (par_num < par_denom) {
+ videocontext->display_width = videocontext->pixel_width;
+ videocontext->display_height =
+ videocontext->pixel_height * par_denom / par_num;
+ } else {
+ videocontext->display_width = 0;
+ videocontext->display_height = 0;
+ }
+ } else {
+ videocontext->display_width = 0;
+ videocontext->display_height = 0;
+ }
+ hdr += 3 + 3;
+ }
+
+ if (buf0)
+ gst_buffer_unref (buf0);
+
+ return TRUE;
+}
/**
* gst_matroska_mux_audio_pad_setcaps:
@@ -625,11 +822,14 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
{
GstMatroskaTrackContext *context = NULL;
GstMatroskaTrackAudioContext *audiocontext;
+ GstMatroskaMux *mux;
GstMatroskaPad *collect_pad;
const gchar *mimetype;
gint samplerate = 0, channels = 0;
GstStructure *structure;
+ mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
+
/* find context */
collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
g_assert (collect_pad);
@@ -716,8 +916,6 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
/* FIXME: endianness is undefined */
} else if (!strcmp (mimetype, "audio/x-vorbis")) {
const GValue *streamheader;
- guint8 *priv_data = NULL;
- guint priv_data_size = 0;
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_VORBIS);
@@ -726,81 +924,14 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
context->codec_priv = NULL;
context->codec_priv_size = 0;
}
- streamheader = gst_structure_get_value (structure, "streamheader");
-
- if (streamheader != NULL) {
- if (G_VALUE_TYPE (streamheader) == GST_TYPE_ARRAY) {
- GArray *bufarr = g_value_peek_pointer (streamheader);
- gint i;
- gint offset;
-
- if (bufarr->len == 3) {
- GstBuffer *buf[3];
-
- for (i = 0; i < bufarr->len; i++) {
- GValue *bufval = &g_array_index (bufarr, GValue, i);
-
- if (G_VALUE_TYPE (bufval) == GST_TYPE_BUFFER) {
- buf[i] = g_value_peek_pointer (bufval);
- }
- }
-
- priv_data_size = 1;
- priv_data_size += GST_BUFFER_SIZE (buf[0]) / 0xff + 1;
- priv_data_size += GST_BUFFER_SIZE (buf[1]) / 0xff + 1;
-
- for (i = 0; i < 3; ++i) {
- priv_data_size += GST_BUFFER_SIZE (buf[i]);
- }
-
- priv_data = g_malloc0 (priv_data_size);
-
- priv_data[0] = 2;
- offset = 1;
-
- for (i = 0; i < GST_BUFFER_SIZE (buf[0]) / 0xff; ++i) {
- priv_data[offset++] = 0xff;
- }
- priv_data[offset++] = GST_BUFFER_SIZE (buf[0]) % 0xff;
-
- for (i = 0; i < GST_BUFFER_SIZE (buf[1]) / 0xff; ++i) {
- priv_data[offset++] = 0xff;
- }
- priv_data[offset++] = GST_BUFFER_SIZE (buf[1]) % 0xff;
-
- for (i = 0; i < 3; ++i) {
- memcpy (priv_data + offset, GST_BUFFER_DATA (buf[i]),
- GST_BUFFER_SIZE (buf[i]));
- offset += GST_BUFFER_SIZE (buf[i]);
- }
-
- if (GST_BUFFER_SIZE (buf[0]) < 1 + 6 + 4) {
- GST_WARNING ("First vorbis header too small, ignoring");
- } else {
- if (memcmp (GST_BUFFER_DATA (buf[0]) + 1, "vorbis", 6) == 0) {
- guint8 *hdr = GST_BUFFER_DATA (buf[0]) + 1 + 6 + 4;
-
- audiocontext->channels = GST_READ_UINT8 (hdr);
- audiocontext->samplerate = GST_READ_UINT32_LE (hdr + 1);
- }
- }
- } else {
- GST_WARNING ("Vorbis header does not contain "
- "three buffers (found %d buffers), Ignoring.", bufarr->len);
- }
- }
- }
- if (priv_data == NULL) {
- GST_WARNING ("Could not write Vorbis header into codec private data. "
- "You will probably not be able to play the stream.");
+ streamheader = gst_structure_get_value (structure, "streamheader");
+ if (!vorbis_streamheader_to_codecdata (streamheader, context)) {
+ GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
+ ("vorbis stream headers missing or malformed"));
+ return FALSE;
}
-
- context->codec_priv_size = priv_data_size;
- context->codec_priv = (gpointer) priv_data;
-
return TRUE;
-
} else if (!strcmp (mimetype, "audio/x-ac3")) {
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_AC3);
@@ -1304,35 +1435,6 @@ gst_matroska_mux_best_pad (GstMatroskaMux * mux, gboolean * popped)
return best;
}
-static gboolean
-gst_matroska_mux_stream_is_vorbis_header (GstMatroskaMux * mux,
- GstMatroskaPad * collect_pad)
-{
- GstMatroskaTrackAudioContext *audio_ctx;
-
- audio_ctx = (GstMatroskaTrackAudioContext *) collect_pad->track;
-
- if (collect_pad->track->type != GST_MATROSKA_TRACK_TYPE_AUDIO)
- return FALSE;
-
- if (audio_ctx->first_frame != FALSE)
- return FALSE;
-
- if (collect_pad->track->codec_id == NULL ||
- strcmp (collect_pad->track->codec_id, GST_MATROSKA_CODEC_ID_AUDIO_VORBIS))
- return FALSE;
-
- /* HACK: three frame headers are counted using pos */
- if (++collect_pad->track->pos <= 3)
- return TRUE;
-
- /* 4th vorbis packet => skipped all headers */
- collect_pad->track->pos = 0;
- audio_ctx->first_frame = TRUE;
- return FALSE;
-}
-
-
/**
* gst_matroska_mux_buffer_header:
* @track: Track context.
@@ -1386,10 +1488,11 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
buf = collect_pad->buffer;
collect_pad->buffer = NULL;
- /* vorbis header are retrieved from caps and placed in CodecPrivate */
- if (gst_matroska_mux_stream_is_vorbis_header (mux, collect_pad)) {
- GST_LOG_OBJECT (collect_pad->collect.pad, "dropping vorbis header buffer");
+ /* vorbis/theora headers are retrieved from caps and put in CodecPrivate */
+ if (collect_pad->track->xiph_headers_to_skip > 0) {
+ GST_LOG_OBJECT (collect_pad->collect.pad, "dropping streamheader buffer");
gst_buffer_unref (buf);
+ --collect_pad->track->xiph_headers_to_skip;
return GST_FLOW_OK;
}