gstreamer/gst/matroska/matroska-mux.c
Tim-Philipp Müller 7d9dd13c6e gst/matroska/: Set timestamps on outgoing ebml headers as well, so that the element after matroskamux can get the tim...
Original commit message from CVS:
Reviewed by: Tim-Philipp Müller  <tim at centricular dot net>
* gst/matroska/ebml-write.c: (gst_ebml_write_new),
(gst_ebml_write_reset), (gst_ebml_write_element_new):
* gst/matroska/ebml-write.h:
* gst/matroska/matroska-mux.c: (gst_matroska_mux_write_data):
Set timestamps on outgoing ebml headers as well, so that the
element after matroskamux can get the timestamp already when
reading the first ebml element and doesn't have to wait for
the actual data buffer for that (#320308).
2005-11-01 12:35:39 +00:00

1603 lines
48 KiB
C

/* GStreamer Matroska muxer/demuxer
* (c) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
* (c) 2005 Michal Benes <michal.benes@xeris.cz>
*
* matroska-mux.c: matroska file/stream muxer
*
* 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 <math.h>
#include <string.h>
#include "matroska-mux.h"
#include "matroska-ids.h"
GST_DEBUG_CATEGORY_STATIC (matroskamux_debug);
#define GST_CAT_DEFAULT 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",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/x-matroska")
);
#define COMMON_VIDEO_CAPS \
"width = (int) [ 16, 4096 ], " \
"height = (int) [ 16, 4096 ], " \
"framerate = (double) [ 0, MAX ]"
static GstStaticPadTemplate videosink_templ =
GST_STATIC_PAD_TEMPLATE ("video_%d",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS ("video/mpeg, "
"mpegversion = (int) { 1, 2, 4 }, "
"systemstream = (boolean) false, "
COMMON_VIDEO_CAPS "; "
"video/x-h264, "
COMMON_VIDEO_CAPS "; "
"video/x-divx, "
COMMON_VIDEO_CAPS "; "
"video/x-xvid, "
COMMON_VIDEO_CAPS "; "
"video/x-msmpeg, "
COMMON_VIDEO_CAPS "; "
"image/jpeg, "
COMMON_VIDEO_CAPS "; "
"video/x-raw-yuv, "
"format = (fourcc) { YUY2, I420 }, " COMMON_VIDEO_CAPS)
);
#define COMMON_AUDIO_CAPS \
"channels = (int) [ 1, 8 ], " \
"rate = (int) [ 8000, 96000 ]"
/* FIXME:
* * audio/x-raw-float: endianness needs defining.
*/
static GstStaticPadTemplate audiosink_templ =
GST_STATIC_PAD_TEMPLATE ("audio_%d",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS ("audio/mpeg, "
"mpegversion = (int) 1, "
"layer = (int) [ 1, 3 ], "
COMMON_AUDIO_CAPS "; "
"audio/mpeg, "
"mpegversion = (int) { 2, 4 }, "
COMMON_AUDIO_CAPS "; "
"audio/x-ac3, "
COMMON_AUDIO_CAPS "; "
"audio/x-vorbis, "
COMMON_AUDIO_CAPS "; "
"audio/x-raw-int, "
"width = (int) { 8, 16, 24 }, "
"depth = (int) { 8, 16, 24 }, "
"endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, "
"signed = (boolean) { true, false }, "
COMMON_AUDIO_CAPS ";"
"audio/x-tta, "
"width = (int) { 8, 16, 24 }, "
"channels = (int) { 1, 2 }, " "rate = (int) [ 8000, 96000 ]")
);
static GstStaticPadTemplate subtitlesink_templ =
GST_STATIC_PAD_TEMPLATE ("subtitle_%d",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS_ANY);
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);
/* Matroska muxer destructor */
static void gst_matroska_mux_finalize (GObject * object);
/* Pads collected callback */
static GstFlowReturn
gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data);
/* pad functions */
static gboolean gst_matroska_mux_handle_src_event (GstPad * pad,
GstEvent * event);
static GstPad *gst_matroska_mux_request_new_pad (GstElement * element,
GstPadTemplate * templ, const gchar * name);
/* gst internal change state handler */
static GstStateChangeReturn
gst_matroska_mux_change_state (GstElement * element, GstStateChange transition);
/* gobject bla bla */
static void gst_matroska_mux_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_matroska_mux_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
/* reset muxer */
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 void
gst_matroska_mux_base_init (gpointer g_class)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
static GstElementDetails gst_matroska_mux_details = {
"Matroska muxer",
"Codec/Muxer",
"Muxes video/audio/subtitle streams into a matroska stream",
"Ronald Bultje <rbultje@ronald.bitfreak.net>"
};
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&videosink_templ));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&audiosink_templ));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&subtitlesink_templ));
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);
}
static void
gst_matroska_mux_class_init (GstMatroskaMuxClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_matroska_mux_finalize);
gobject_class->get_property = gst_matroska_mux_get_property;
gobject_class->set_property = gst_matroska_mux_set_property;
g_object_class_install_property (gobject_class, ARG_WRITING_APP,
g_param_spec_string ("writing-app", "Writing application.",
"The name the application that creates the matroska file.",
NULL, G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, ARG_MATROSKA_VERSION,
g_param_spec_int ("version", "Matroska version",
"This parameter determines what matroska features can be used.",
1, 2, 1, G_PARAM_READWRITE));
gstelement_class->change_state = gst_matroska_mux_change_state;
gstelement_class->request_new_pad = gst_matroska_mux_request_new_pad;
}
/**
* gst_matroska_mux_init:
* @mux: #GstMatroskaMux that should be initialized.
* @g_class: Class of the muxer.
*
* Matroska muxer constructor.
*/
static void
gst_matroska_mux_init (GstMatroskaMux * mux, GstMatroskaMuxClass * g_class)
{
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class);
mux->srcpad =
gst_pad_new_from_template (gst_element_class_get_pad_template
(gstelement_class, "src"), "src");
gst_pad_set_event_function (mux->srcpad, gst_matroska_mux_handle_src_event);
gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad);
mux->collect = gst_collectpads_new ();
gst_collectpads_set_function (mux->collect,
(GstCollectPadsFunction) gst_matroska_mux_collected, mux);
mux->ebml_write = gst_ebml_write_new (mux->srcpad);
/* initialize internal variables */
mux->index = NULL;
mux->matroska_version = 1;
/* Initialize all variables */
gst_matroska_mux_reset (GST_ELEMENT (mux));
}
/**
* gst_matroska_mux_finalize:
* @object: #GstMatroskaMux that should be finalized.
*
* Finalize matroska muxer.
*/
static void
gst_matroska_mux_finalize (GObject * object)
{
GstMatroskaMux *mux = GST_MATROSKA_MUX (object);
gst_object_unref (mux->collect);
gst_object_unref (mux->ebml_write);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
/**
* gst_matroska_mux_create_uid:
*
* Generate new unused track UID.
*
* Returns: New track UID.
*/
static guint32
gst_matroska_mux_create_uid (void)
{
guint32 uid = 0;
GRand *rand = g_rand_new ();
while (!uid) {
guint i;
uid = g_rand_int (rand);
for (i = 0; i < used_uids->len; i++) {
if (g_array_index (used_uids, guint32, i) == uid) {
uid = 0;
break;
}
}
g_array_append_val (used_uids, uid);
}
g_free (rand);
return uid;
}
/**
* gst_matroska_mux_reset:
* @element: #GstMatroskaMux that should be reseted.
*
* Reset matroska muxer back to initial state.
*/
static void
gst_matroska_mux_reset (GstElement * element)
{
GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
GSList *walk;
/* reset EBML write */
gst_ebml_write_reset (mux->ebml_write);
/* reset input */
mux->state = GST_MATROSKA_MUX_STATE_START;
/* clean up existing streams */
while ((walk = mux->collect->data) != NULL) {
GstMatroskaPad *collect_pad;
GstPad *thepad;
collect_pad = (GstMatroskaPad *) walk->data;
thepad = collect_pad->collect.pad;
/* free track information */
if (collect_pad->track != NULL) {
g_free (collect_pad->track->codec_id);
g_free (collect_pad->track->codec_name);
g_free (collect_pad->track->name);
g_free (collect_pad->track->language);
g_free (collect_pad->track->codec_priv);
g_free (collect_pad->track);
collect_pad->track = NULL;
}
/* free cached buffer */
if (collect_pad->buffer != NULL) {
gst_buffer_unref (collect_pad->buffer);
collect_pad->buffer = NULL;
}
/* remove from collectpads */
gst_collectpads_remove_pad (mux->collect, thepad);
}
mux->num_streams = 0;
mux->num_a_streams = 0;
mux->num_t_streams = 0;
mux->num_v_streams = 0;
/* reset writing_app */
if (mux->writing_app) {
free (mux->writing_app);
}
mux->writing_app = g_strdup ("GStreamer Matroska muxer");
/* reset indexes */
mux->num_indexes = 0;
g_free (mux->index);
mux->index = NULL;
/* reset timers */
mux->time_scale = 1000000;
mux->duration = 0;
/* reset uid array */
if (used_uids) {
g_array_free (used_uids, TRUE);
}
/* arbitrary size, 10 should be enough in most cases */
used_uids = g_array_sized_new (FALSE, FALSE, sizeof (guint32), 10);
/* reset cluster */
mux->cluster = 0;
mux->cluster_time = 0;
mux->cluster_pos = 0;
/* reset meta-seek index */
mux->num_meta_indexes = 0;
g_free (mux->meta_index);
mux->meta_index = NULL;
}
/**
* gst_matroska_mux_handle_src_event:
* @pad: Pad which received the event.
* @event: Received event.
*
* handle events - copied from oggmux without understanding
*
* Returns: #TRUE on success.
*/
static gboolean
gst_matroska_mux_handle_src_event (GstPad * pad, GstEvent * event)
{
GstMatroskaMux *mux;
GstEventType type;
mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad));
type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN;
switch (type) {
case GST_EVENT_SEEK:
/* disable seeking for now */
return FALSE;
default:
break;
}
return gst_pad_event_default (pad, event);
}
/**
* gst_matroska_mux_video_pad_setcaps:
* @pad: Pad which got the caps.
* @caps: New caps.
*
* Setcaps function for video sink pad.
*
* Returns: #TRUE on success.
*/
static gboolean
gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps)
{
GstMatroskaTrackContext *context = NULL;
GstMatroskaTrackVideoContext *videocontext;
GstMatroskaPad *collect_pad;
const gchar *mimetype;
gint width, height, pixel_width, pixel_height;
gdouble framerate;
GstStructure *structure;
gboolean ret;
/* find context */
collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
g_assert (collect_pad);
context = collect_pad->track;
g_assert (context);
g_assert (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO);
videocontext = (GstMatroskaTrackVideoContext *) context;
/* gst -> matroska ID'ing */
structure = gst_caps_get_structure (caps, 0);
mimetype = gst_structure_get_name (structure);
/* get general properties */
gst_structure_get_int (structure, "width", &width);
gst_structure_get_int (structure, "height", &height);
gst_structure_get_double (structure, "framerate", &framerate);
videocontext->pixel_width = width;
videocontext->pixel_height = height;
context->default_duration = GST_SECOND / framerate;
ret = gst_structure_get_int (structure, "pixel_width", &pixel_width);
ret &= gst_structure_get_int (structure, "pixel_height", &pixel_height);
if (ret) {
if (pixel_width > pixel_height) {
videocontext->display_width = width * pixel_width / pixel_height;
videocontext->display_height = height;
} else if (pixel_width < pixel_height) {
videocontext->display_width = width;
videocontext->display_height = height * pixel_height / pixel_width;
} else {
videocontext->display_width = 0;
videocontext->display_height = 0;
}
} else {
videocontext->display_width = 0;
videocontext->display_height = 0;
}
videocontext->asr_mode = GST_MATROSKA_ASPECT_RATIO_MODE_FREE;
videocontext->eye_mode = GST_MATROSKA_EYE_MODE_MONO;
videocontext->fourcc = 0;
/* find type */
if (!strcmp (mimetype, "video/x-raw-yuv")) {
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED);
gst_structure_get_fourcc (structure, "format", &videocontext->fourcc);
return TRUE;
} else if (!strcmp (mimetype, "image/jpeg")) {
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MJPEG);
return TRUE;
} else if (!strcmp (mimetype, "video/x-divx")) {
gint divxversion;
BITMAPINFOHEADER *bih;
bih = (BITMAPINFOHEADER *) g_malloc0 (sizeof (BITMAPINFOHEADER));
GST_WRITE_UINT32_LE (&bih->bi_size, sizeof (BITMAPINFOHEADER));
GST_WRITE_UINT32_LE (&bih->bi_width, videocontext->pixel_width);
GST_WRITE_UINT32_LE (&bih->bi_height, videocontext->pixel_height);
GST_WRITE_UINT16_LE (&bih->bi_planes, (guint16) 1);
GST_WRITE_UINT16_LE (&bih->bi_bit_count, (guint16) 24);
GST_WRITE_UINT32_LE (&bih->bi_size_image, videocontext->pixel_width *
videocontext->pixel_height * 3);
gst_structure_get_int (structure, "divxversion", &divxversion);
switch (divxversion) {
case 3:
GST_WRITE_UINT32_LE (&bih->bi_compression, GST_STR_FOURCC ("DIV3"));
break;
case 4:
GST_WRITE_UINT32_LE (&bih->bi_compression, GST_STR_FOURCC ("DIVX"));
break;
case 5:
GST_WRITE_UINT32_LE (&bih->bi_compression, GST_STR_FOURCC ("DX50"));
break;
}
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC);
context->codec_priv = (gpointer) bih;
context->codec_priv_size = sizeof (BITMAPINFOHEADER);
return TRUE;
} else if (!strcmp (mimetype, "video/x-xvid")) {
BITMAPINFOHEADER *bih;
bih = (BITMAPINFOHEADER *) g_malloc0 (sizeof (BITMAPINFOHEADER));
GST_WRITE_UINT32_LE (&bih->bi_size, sizeof (BITMAPINFOHEADER));
GST_WRITE_UINT32_LE (&bih->bi_width, videocontext->pixel_width);
GST_WRITE_UINT32_LE (&bih->bi_height, videocontext->pixel_height);
GST_WRITE_UINT16_LE (&bih->bi_planes, (guint16) 1);
GST_WRITE_UINT16_LE (&bih->bi_bit_count, (guint16) 24);
GST_WRITE_UINT32_LE (&bih->bi_compression, GST_STR_FOURCC ("XVID"));
GST_WRITE_UINT32_LE (&bih->bi_size_image, videocontext->pixel_width *
videocontext->pixel_height * 3);
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC);
context->codec_priv = (gpointer) bih;
context->codec_priv_size = sizeof (BITMAPINFOHEADER);
return TRUE;
} else if (!strcmp (mimetype, "video/x-h264")) {
const GValue *codec_data;
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC);
if (context->codec_priv != NULL) {
g_free (context->codec_priv);
context->codec_priv = NULL;
context->codec_priv_size = 0;
}
/* Create avcC header */
codec_data = gst_structure_get_value (structure, "codec_data");
if (codec_data != NULL) {
guint8 *priv_data = NULL;
guint priv_data_size = 0;
GstBuffer *codec_data_buf = g_value_peek_pointer (codec_data);
priv_data_size = GST_BUFFER_SIZE (codec_data_buf);
priv_data = g_malloc0 (priv_data_size);
memcpy (priv_data, GST_BUFFER_DATA (codec_data_buf), priv_data_size);
context->codec_priv = priv_data;
context->codec_priv_size = priv_data_size;
}
return TRUE;
} else if (!strcmp (mimetype, "video/mpeg")) {
gint mpegversion;
gst_structure_get_int (structure, "mpegversion", &mpegversion);
switch (mpegversion) {
case 1:
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG1);
break;
case 2:
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG2);
break;
case 4:
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP);
break;
default:
return FALSE;
}
return TRUE;
} else if (!strcmp (mimetype, "video/x-msmpeg")) {
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3);
return TRUE;
}
return FALSE;
}
/**
* gst_matroska_mux_audio_pad_setcaps:
* @pad: Pad which got the caps.
* @caps: New caps.
*
* Setcaps function for audio sink pad.
*
* Returns: #TRUE on success.
*/
static gboolean
gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
{
GstMatroskaTrackContext *context = NULL;
GstMatroskaTrackAudioContext *audiocontext;
GstMatroskaPad *collect_pad;
GstMatroskaMux *mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad));
const gchar *mimetype;
gint samplerate = 0, channels = 0;
GstStructure *structure;
/* find context */
collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
g_assert (collect_pad);
context = collect_pad->track;
g_assert (context);
g_assert (context->type == GST_MATROSKA_TRACK_TYPE_AUDIO);
audiocontext = (GstMatroskaTrackAudioContext *) context;
structure = gst_caps_get_structure (caps, 0);
mimetype = gst_structure_get_name (structure);
/* general setup */
gst_structure_get_int (structure, "rate", &samplerate);
gst_structure_get_int (structure, "channels", &channels);
audiocontext->samplerate = samplerate;
audiocontext->channels = channels;
audiocontext->bitdepth = 0;
context->default_duration = 0;
if (!strcmp (mimetype, "audio/mpeg")) {
gint mpegversion = 0;
gst_structure_get_int (structure, "mpegversion", &mpegversion);
switch (mpegversion) {
case 1:{
gint layer;
gst_structure_get_int (structure, "layer", &layer);
switch (layer) {
case 1:
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1);
context->default_duration =
384 * GST_SECOND / audiocontext->samplerate;
break;
case 2:
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2);
context->default_duration =
1152 * GST_SECOND / audiocontext->samplerate;
break;
case 3:
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3);
context->default_duration =
1152 * GST_SECOND / audiocontext->samplerate;
break;
default:
return FALSE;
}
break;
}
case 2:
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG2 "MAIN");
break;
case 4:
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG4 "MAIN");
break;
default:
return FALSE;
}
return TRUE;
} else if (!strcmp (mimetype, "audio/x-raw-int")) {
gint endianness, width, depth;
gboolean signedness;
if (!gst_structure_get_int (structure, "endianness", &endianness) ||
!gst_structure_get_int (structure, "width", &width) ||
!gst_structure_get_int (structure, "depth", &depth) ||
!gst_structure_get_int (structure, "signed", &signedness))
return FALSE;
if (width != depth ||
(width == 8 && signedness) || (width == 16 && !signedness))
return FALSE;
audiocontext->bitdepth = depth;
if (endianness == G_BIG_ENDIAN)
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE);
else
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE);
return TRUE;
} else if (!strcmp (mimetype, "audio/x-raw-float")) {
/* 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);
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 (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 (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_OBJECT (mux, "Vorbis header does not contain "
"three buffers (found %d buffers), Ignoring.", bufarr->len);
}
}
}
if (priv_data == NULL) {
GST_WARNING_OBJECT (mux,
"Could not write Vorbis header into codec private data. "
"You will probably not be able to play the stream.");
}
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);
return TRUE;
} else if (!strcmp (mimetype, "audio/x-tta")) {
gint width;
/* TTA frame duration */
context->default_duration = 1.04489795918367346939 * GST_SECOND;
gst_structure_get_int (structure, "width", &width);
audiocontext->bitdepth = width;
context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_TTA);
return TRUE;
}
return FALSE;
}
/**
* gst_matroska_mux_subtitle_pad_setcaps:
* @pad: Pad which got the caps.
* @caps: New caps.
*
* Setcaps function for subtitle sink pad.
*
* Returns: #TRUE on success.
*/
static gboolean
gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps)
{
/* Consider this as boilerplate code for now. There is
* no single subtitle creation element in GStreamer,
* neither do I know how subtitling works at all. */
return FALSE;
}
/**
* gst_matroska_mux_request_new_pad:
* @element: #GstMatroskaMux.
* @templ: #GstPadTemplate.
* @pad_name: New pad name.
*
* Request pad function for sink templates.
*
* Returns: New #GstPad.
*/
static GstPad *
gst_matroska_mux_request_new_pad (GstElement * element,
GstPadTemplate * templ, const gchar * pad_name)
{
GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
GstMatroskaPad *collect_pad;
GstPad *newpad = NULL;
gchar *name = NULL;
GstPadSetCapsFunction setcapsfunc = NULL;
GstMatroskaTrackContext *context = NULL;
if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
name = g_strdup_printf ("audio_%d", mux->num_a_streams++);
setcapsfunc = gst_matroska_mux_audio_pad_setcaps;
context = (GstMatroskaTrackContext *)
g_new0 (GstMatroskaTrackAudioContext, 1);
context->type = GST_MATROSKA_TRACK_TYPE_AUDIO;
context->name = g_strdup ("Audio");
} else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
name = g_strdup_printf ("video_%d", mux->num_v_streams++);
setcapsfunc = gst_matroska_mux_video_pad_setcaps;
context = (GstMatroskaTrackContext *)
g_new0 (GstMatroskaTrackVideoContext, 1);
context->type = GST_MATROSKA_TRACK_TYPE_VIDEO;
context->name = g_strdup ("Video");
} else if (templ == gst_element_class_get_pad_template (klass, "subtitle_%d")) {
name = g_strdup_printf ("subtitle_%d", mux->num_t_streams++);
setcapsfunc = gst_matroska_mux_subtitle_pad_setcaps;
context = (GstMatroskaTrackContext *)
g_new0 (GstMatroskaTrackSubtitleContext, 1);
context->type = GST_MATROSKA_TRACK_TYPE_SUBTITLE;
context->name = g_strdup ("Subtitle");
} else {
GST_WARNING_OBJECT (mux, "This is not our template!");
return NULL;
}
newpad = gst_pad_new_from_template (templ, name);
g_free (name);
collect_pad = (GstMatroskaPad *)
gst_collectpads_add_pad (mux->collect, newpad, sizeof (GstMatroskaPad));
context->flags = GST_MATROSKA_TRACK_ENABLED | GST_MATROSKA_TRACK_DEFAULT;
collect_pad->track = context;
collect_pad->buffer = NULL;
gst_pad_set_setcaps_function (newpad, setcapsfunc);
gst_element_add_pad (element, newpad);
return newpad;
}
/**
* gst_matroska_mux_track_header:
* @mux: #GstMatroskaMux
* @context: Tack context.
*
* Write a track header.
*/
static void
gst_matroska_mux_track_header (GstMatroskaMux * mux,
GstMatroskaTrackContext * context)
{
GstEbmlWrite *ebml = mux->ebml_write;
guint64 master;
/* track type goes before the type-specific stuff */
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKNUMBER, context->num);
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKTYPE, context->type);
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKUID,
gst_matroska_mux_create_uid ());
if (context->default_duration) {
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKDEFAULTDURATION,
context->default_duration);
}
/* type-specific stuff */
switch (context->type) {
case GST_MATROSKA_TRACK_TYPE_VIDEO:{
GstMatroskaTrackVideoContext *videocontext =
(GstMatroskaTrackVideoContext *) context;
master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKVIDEO);
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELWIDTH,
videocontext->pixel_width);
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELHEIGHT,
videocontext->pixel_height);
if (videocontext->display_width && videocontext->display_height) {
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYWIDTH,
videocontext->display_width);
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYHEIGHT,
videocontext->display_height);
}
if (context->flags & GST_MATROSKA_VIDEOTRACK_INTERLACED)
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOFLAGINTERLACED, 1);
if (videocontext->fourcc) {
guint32 fcc_le = GUINT32_TO_LE (videocontext->fourcc);
gst_ebml_write_binary (ebml, GST_MATROSKA_ID_VIDEOCOLOURSPACE,
(gpointer) & fcc_le, 4);
}
gst_ebml_write_master_finish (ebml, master);
break;
}
case GST_MATROSKA_TRACK_TYPE_AUDIO:{
GstMatroskaTrackAudioContext *audiocontext =
(GstMatroskaTrackAudioContext *) context;
master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKAUDIO);
if (audiocontext->samplerate != 8000)
gst_ebml_write_float (ebml, GST_MATROSKA_ID_AUDIOSAMPLINGFREQ,
audiocontext->samplerate);
if (audiocontext->channels != 1)
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOCHANNELS,
audiocontext->channels);
if (audiocontext->bitdepth) {
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOBITDEPTH,
audiocontext->bitdepth);
}
gst_ebml_write_master_finish (ebml, master);
break;
}
default:
/* doesn't need type-specific data */
break;
}
gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_CODECID, context->codec_id);
if (context->codec_priv)
gst_ebml_write_binary (ebml, GST_MATROSKA_ID_CODECPRIVATE,
context->codec_priv, context->codec_priv_size);
/* FIXME: until we have a nice way of getting the codecname
* out of the caps, I'm not going to enable this. Too much
* (useless, double, boring) work... */
/*gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CODECNAME,
context->codec_name); */
gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKNAME, context->name);
}
/**
* gst_matroska_mux_start:
* @mux: #GstMatroskaMux
*
* Start a new matroska file (write headers etc...)
*/
static void
gst_matroska_mux_start (GstMatroskaMux * mux)
{
GstEbmlWrite *ebml = mux->ebml_write;
guint32 seekhead_id[] = { GST_MATROSKA_ID_INFO,
GST_MATROSKA_ID_TRACKS,
GST_MATROSKA_ID_CUES,
GST_MATROSKA_ID_SEEKHEAD,
#if 0
GST_MATROSKA_ID_TAGS,
#endif
0
};
guint64 master, child;
GSList *collected;
int i;
guint tracknum = 1;
gdouble duration = 0;
guint32 *segment_uid = (guint32 *) g_malloc (16);
GRand *rand = g_rand_new ();
GTimeVal time = { 0, 0 };
/* we start with a EBML header */
gst_ebml_write_header (ebml, "matroska", mux->matroska_version);
/* start a segment */
mux->segment_pos =
gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENT);
mux->segment_master = ebml->pos;
/* the rest of the header is cached */
gst_ebml_write_set_cache (ebml, 0x1000);
/* seekhead (table of contents) - we set the positions later */
mux->seekhead_pos = ebml->pos;
master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKHEAD);
for (i = 0; seekhead_id[i] != 0; i++) {
child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKENTRY);
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKID, seekhead_id[i]);
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKPOSITION, -1);
gst_ebml_write_master_finish (ebml, child);
}
gst_ebml_write_master_finish (ebml, master);
/* segment info */
mux->info_pos = ebml->pos;
master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_INFO);
for (i = 0; i < 4; i++) {
segment_uid[i] = g_rand_int (rand);
}
g_free (rand);
gst_ebml_write_binary (ebml, GST_MATROSKA_ID_SEGMENTUID,
(guint8 *) segment_uid, 16);
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TIMECODESCALE, mux->time_scale);
mux->duration_pos = ebml->pos;
/* get duration */
for (collected = mux->collect->data; collected;
collected = g_slist_next (collected)) {
GstMatroskaPad *collect_pad;
GstFormat format = GST_FORMAT_TIME;
GstPad *thepad, *peerpad;
gint64 trackduration;
collect_pad = (GstMatroskaPad *) collected->data;
thepad = collect_pad->collect.pad;
/* Query the total length of the track. */
peerpad = gst_pad_get_peer (thepad);
GST_DEBUG ("Querying duration on pad %s:%s", GST_DEBUG_PAD_NAME (thepad));
if (gst_pad_query_duration (peerpad, &format, &trackduration)) {
GST_DEBUG ("%s:%s - duration: %" GST_TIME_FORMAT,
GST_DEBUG_PAD_NAME (thepad), GST_TIME_ARGS (trackduration));
if ((gdouble) trackduration > duration) {
duration = (gdouble) trackduration;
}
}
gst_object_unref (peerpad);
}
gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
duration / mux->time_scale);
gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_MUXINGAPP,
"GStreamer plugin version " GST_PLUGINS_GOOD_VERSION);
if (mux->writing_app && mux->writing_app[0]) {
gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_WRITINGAPP, mux->writing_app);
}
g_get_current_time (&time);
gst_ebml_write_date (ebml, GST_MATROSKA_ID_DATEUTC, time.tv_sec);
gst_ebml_write_master_finish (ebml, master);
/* tracks */
mux->tracks_pos = ebml->pos;
master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKS);
for (collected = mux->collect->data; collected;
collected = g_slist_next (collected)) {
GstMatroskaPad *collect_pad;
GstPad *thepad;
collect_pad = (GstMatroskaPad *) collected->data;
thepad = collect_pad->collect.pad;
if (GST_PAD_IS_USABLE (thepad)) {
collect_pad->track->num = tracknum++;
child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKENTRY);
gst_matroska_mux_track_header (mux, collect_pad->track);
gst_ebml_write_master_finish (ebml, child);
}
}
gst_ebml_write_master_finish (ebml, master);
/* lastly, flush the cache */
gst_ebml_write_flush_cache (ebml);
}
/**
* gst_matroska_mux_finish:
* @mux: #GstMatroskaMux
*
* Finish a new matroska file (write index etc...)
*/
static void
gst_matroska_mux_finish (GstMatroskaMux * mux)
{
GstEbmlWrite *ebml = mux->ebml_write;
guint64 pos;
guint64 duration = 0;
GSList *collected;
/* finish last cluster */
if (mux->cluster) {
gst_ebml_write_master_finish (ebml, mux->cluster);
}
/* cues */
if (mux->index != NULL) {
guint n;
guint64 master, pointentry_master, trackpos_master;
mux->cues_pos = ebml->pos;
gst_ebml_write_set_cache (ebml, 12 + 41 * mux->num_indexes);
master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CUES);
for (n = 0; n < mux->num_indexes; n++) {
GstMatroskaIndex *idx = &mux->index[n];
pointentry_master = gst_ebml_write_master_start (ebml,
GST_MATROSKA_ID_POINTENTRY);
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUETIME,
idx->time / mux->time_scale);
trackpos_master = gst_ebml_write_master_start (ebml,
GST_MATROSKA_ID_CUETRACKPOSITION);
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUETRACK, idx->track);
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUECLUSTERPOSITION,
idx->pos - mux->segment_master);
gst_ebml_write_master_finish (ebml, trackpos_master);
gst_ebml_write_master_finish (ebml, pointentry_master);
}
gst_ebml_write_master_finish (ebml, master);
gst_ebml_write_flush_cache (ebml);
}
if (mux->meta_index != NULL) {
guint n;
guint64 master, seekentry_master;
mux->meta_pos = ebml->pos;
gst_ebml_write_set_cache (ebml, 12 + 28 * mux->num_meta_indexes);
master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKHEAD);
for (n = 0; n < mux->num_meta_indexes; n++) {
GstMatroskaMetaSeekIndex *idx = &mux->meta_index[n];
seekentry_master = gst_ebml_write_master_start (ebml,
GST_MATROSKA_ID_SEEKENTRY);
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKID, idx->id);
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKPOSITION,
idx->pos - mux->segment_master);
gst_ebml_write_master_finish (ebml, seekentry_master);
}
gst_ebml_write_master_finish (ebml, master);
}
gst_ebml_write_flush_cache (ebml);
/* FIXME: tags */
/* update seekhead. We know that:
* - a seekhead contains 4 entries.
* - order of entries is as above.
* - a seekhead has a 4-byte header + 8-byte length
* - each entry is 2-byte master, 2-byte ID pointer,
* 2-byte length pointer, all 8/1-byte length, 4-
* byte ID and 8-byte length pointer, where the
* length pointer starts at 20.
* - all entries are local to the segment (so pos - segment_master).
* - so each entry is at 12 + 20 + num * 28. */
gst_ebml_replace_uint (ebml, mux->seekhead_pos + 32,
mux->info_pos - mux->segment_master);
gst_ebml_replace_uint (ebml, mux->seekhead_pos + 60,
mux->tracks_pos - mux->segment_master);
if (mux->index != NULL) {
gst_ebml_replace_uint (ebml, mux->seekhead_pos + 88,
mux->cues_pos - mux->segment_master);
} else {
/* void'ify */
guint64 my_pos = ebml->pos;
gst_ebml_write_seek (ebml, mux->seekhead_pos + 68);
gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
gst_ebml_write_seek (ebml, my_pos);
}
if (mux->meta_index != NULL) {
gst_ebml_replace_uint (ebml, mux->seekhead_pos + 116,
mux->meta_pos - mux->segment_master);
} else {
/* void'ify */
guint64 my_pos = ebml->pos;
gst_ebml_write_seek (ebml, mux->seekhead_pos + 96);
gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
gst_ebml_write_seek (ebml, my_pos);
}
#if 0
gst_ebml_replace_uint (ebml, mux->seekhead_pos + 116,
mux->tags_pos - mux->segment_master);
#endif
/* update duration */
/* first get the overall duration */
for (collected = mux->collect->data; collected;
collected = g_slist_next (collected)) {
GstMatroskaPad *collect_pad;
collect_pad = (GstMatroskaPad *) collected->data;
if (collect_pad->duration > duration)
duration = collect_pad->duration;
}
if (duration != 0) {
pos = mux->ebml_write->pos;
gst_ebml_write_seek (ebml, mux->duration_pos);
gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
(gdouble) duration / mux->time_scale);
gst_ebml_write_seek (ebml, pos);
}
/* finish segment - this also writes element length */
gst_ebml_write_master_finish (ebml, mux->segment_pos);
}
/**
* gst_matroska_mux_best_pad:
* @mux: #GstMatroskaMux
*
* Find a pad with the oldest data
* (data from this pad should be written first).
*
* Returns: Selected pad.
*/
static GstMatroskaPad *
gst_matroska_mux_best_pad (GstMatroskaMux * mux)
{
GSList *collected;
GstMatroskaPad *best = NULL;
for (collected = mux->collect->data; collected;
collected = g_slist_next (collected)) {
GstMatroskaPad *collect_pad;
collect_pad = (GstMatroskaPad *) collected->data;
/* fetch a new buffer if needed */
if (collect_pad->buffer == NULL) {
collect_pad->buffer = gst_collectpads_pop (mux->collect,
(GstCollectData *) collect_pad);
}
/* if we have a buffer check if it is better then the current best one */
if (collect_pad->buffer != NULL) {
if (best == NULL
|| GST_BUFFER_TIMESTAMP (collect_pad->buffer) <
GST_BUFFER_TIMESTAMP (best->buffer)) {
best = collect_pad;
}
}
}
return best;
}
/**
* gst_matroska_mux_buffer_header:
* @track: Track context.
* @relative_timestamp: relative timestamp of the buffer
* @flags: Buffer flags.
*
* Create a buffer containing buffer header.
*
* Returns: New buffer.
*/
GstBuffer *
gst_matroska_mux_create_buffer_header (GstMatroskaTrackContext * track,
guint16 relative_timestamp, int flags)
{
GstBuffer *hdr;
hdr = gst_buffer_new_and_alloc (4);
/* track num - FIXME: what if num >= 0x80 (unlikely)? */
GST_BUFFER_DATA (hdr)[0] = track->num | 0x80;
/* time relative to clustertime */
GST_WRITE_UINT16_BE (GST_BUFFER_DATA (hdr) + 1, relative_timestamp);
/* flags */
GST_BUFFER_DATA (hdr)[3] = flags;
return hdr;
}
/**
* gst_matroska_mux_write_data:
* @mux: #GstMatroskaMux
*
* Write collected data (called from gst_matroska_mux_collected).
*
* Returns: Result of the gst_pad_push issued to write the data.
*/
static GstFlowReturn
gst_matroska_mux_write_data (GstMatroskaMux * mux)
{
GstMatroskaPad *best;
GstEbmlWrite *ebml = mux->ebml_write;
GstBuffer *buf, *hdr;
guint64 cluster, blockgroup;
gboolean write_duration;
guint16 relative_timestamp;
/* which stream to write from? */
best = gst_matroska_mux_best_pad (mux);
/* if there is no best pad, we have reached EOS */
if (best == NULL) {
GST_DEBUG ("No best pad finishing...");
gst_matroska_mux_finish (mux);
gst_pad_push_event (mux->srcpad, gst_event_new_eos ());
return GST_FLOW_WRONG_STATE;
}
GST_DEBUG ("Best pad %s.", gst_pad_get_name (best->collect.pad));
/* write data */
buf = best->buffer;
best->buffer = NULL;
/* set the timestamp for outgoing buffers */
ebml->timestamp = GST_BUFFER_TIMESTAMP (buf);
if (mux->cluster) {
/* start a new cluster every two seconds */
if (mux->cluster_time + GST_SECOND * 2 < GST_BUFFER_TIMESTAMP (buf)) {
GstMatroskaMetaSeekIndex *idx;
gst_ebml_write_master_finish (ebml, mux->cluster);
mux->cluster_pos = ebml->pos;
mux->cluster =
gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER);
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE,
GST_BUFFER_TIMESTAMP (buf) / mux->time_scale);
mux->cluster_time = GST_BUFFER_TIMESTAMP (buf);
if (mux->num_meta_indexes % 32 == 0) {
mux->meta_index = g_renew (GstMatroskaMetaSeekIndex, mux->meta_index,
mux->num_meta_indexes + 32);
}
idx = &mux->meta_index[mux->num_meta_indexes++];
idx->id = GST_MATROSKA_ID_CLUSTER;
idx->pos = mux->cluster_pos;
}
} else {
/* first cluster */
GstMatroskaMetaSeekIndex *idx;
mux->cluster_pos = ebml->pos;
mux->cluster = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER);
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE,
GST_BUFFER_TIMESTAMP (buf) / mux->time_scale);
mux->cluster_time = GST_BUFFER_TIMESTAMP (buf);
if (mux->num_meta_indexes % 32 == 0) {
mux->meta_index = g_renew (GstMatroskaMetaSeekIndex, mux->meta_index,
mux->num_meta_indexes + 32);
}
idx = &mux->meta_index[mux->num_meta_indexes++];
idx->id = GST_MATROSKA_ID_CLUSTER;
idx->pos = mux->cluster_pos;
}
cluster = mux->cluster;
/* update duration of this track */
if (GST_BUFFER_DURATION_IS_VALID (buf))
best->duration += GST_BUFFER_DURATION (buf);
/* We currently write an index entry for each keyframe in a
* video track or one entry for each cluster in an audio track
* for audio only files. This can be largely improved, such as doing
* one for each keyframe or each second (for all-keyframe
* streams), only the *first* video track. But that'll come later... */
if (best->track->type == GST_MATROSKA_TRACK_TYPE_VIDEO &&
!GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
GstMatroskaIndex *idx;
if (mux->num_indexes % 32 == 0) {
mux->index = g_renew (GstMatroskaIndex, mux->index,
mux->num_indexes + 32);
}
idx = &mux->index[mux->num_indexes++];
idx->pos = mux->cluster_pos;
idx->time = GST_BUFFER_TIMESTAMP (buf);
idx->track = best->track->num;
} else if ((best->track->type == GST_MATROSKA_TRACK_TYPE_AUDIO) &&
(mux->num_streams == 1)) {
GstMatroskaIndex *idx;
if (mux->num_indexes % 32 == 0) {
mux->index = g_renew (GstMatroskaIndex, mux->index,
mux->num_indexes + 32);
}
idx = &mux->index[mux->num_indexes++];
idx->pos = mux->cluster_pos;
idx->time = GST_BUFFER_TIMESTAMP (buf);
idx->track = best->track->num;
}
/* Check if the duration differs from the default duration. */
write_duration = FALSE;
if (GST_BUFFER_DURATION_IS_VALID (buf)) {
guint64 block_duration = GST_BUFFER_DURATION (buf);
if (block_duration != best->track->default_duration) {
write_duration = TRUE;
}
}
/* write the block, for matroska v2 use SimpleBlock if possible
* one slice (*breath*).
* FIXME: lacing, etc. */
relative_timestamp =
(GST_BUFFER_TIMESTAMP (buf) - mux->cluster_time) / mux->time_scale;
if (mux->matroska_version > 1 && !write_duration) {
int flags =
GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT) ? 0 : 0x80;
hdr =
gst_matroska_mux_create_buffer_header (best->track, relative_timestamp,
flags);
gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_SIMPLEBLOCK,
GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr));
gst_ebml_write_buffer (ebml, hdr);
gst_ebml_write_buffer (ebml, buf);
return gst_ebml_last_write_result (ebml);
} else {
blockgroup = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_BLOCKGROUP);
hdr =
gst_matroska_mux_create_buffer_header (best->track, relative_timestamp,
0);
gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_BLOCK,
GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr));
gst_ebml_write_buffer (ebml, hdr);
gst_ebml_write_buffer (ebml, buf);
if (write_duration) {
guint64 block_duration = GST_BUFFER_DURATION (buf);
gst_ebml_write_uint (ebml, GST_MATROSKA_ID_BLOCKDURATION,
block_duration / mux->time_scale);
}
gst_ebml_write_master_finish (ebml, blockgroup);
return gst_ebml_last_write_result (ebml);
}
}
/**
* gst_matroska_mux_collected:
* @pads: #GstCollectPads
* @uuser_data: #GstMatroskaMux
*
* Collectpads callback.
*
* Returns: #GstFlowReturn
*/
static GstFlowReturn
gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data)
{
GstMatroskaMux *mux = GST_MATROSKA_MUX (user_data);
GST_DEBUG ("Collected pads");
/* start with a header */
if (mux->state == GST_MATROSKA_MUX_STATE_START) {
if (mux->collect->data == NULL) {
GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
("No input streams configured"));
return GST_FLOW_ERROR;
}
mux->state = GST_MATROSKA_MUX_STATE_HEADER;
gst_matroska_mux_start (mux);
mux->state = GST_MATROSKA_MUX_STATE_DATA;
}
/* do one single buffer */
return gst_matroska_mux_write_data (mux);
}
/**
* gst_matroska_mux_change_state:
* @element: #GstMatroskaMux
* @transition: State change transition.
*
* Change the muxer state.
*
* Returns: #GstStateChangeReturn
*/
static GstStateChangeReturn
gst_matroska_mux_change_state (GstElement * element, GstStateChange transition)
{
GstStateChangeReturn ret;
GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
gst_collectpads_start (mux->collect);
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
break;
default:
break;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
switch (transition) {
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
gst_collectpads_stop (mux->collect);
gst_matroska_mux_reset (GST_ELEMENT (mux));
break;
case GST_STATE_CHANGE_READY_TO_NULL:
break;
default:
break;
}
return ret;
}
static void
gst_matroska_mux_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec)
{
GstMatroskaMux *mux;
g_return_if_fail (GST_IS_MATROSKA_MUX (object));
mux = GST_MATROSKA_MUX (object);
switch (prop_id) {
case ARG_WRITING_APP:
if (!g_value_get_string (value)) {
GST_WARNING_OBJECT (mux, "writing-app property can not be NULL");
break;
}
g_free (mux->writing_app);
mux->writing_app = g_strdup (g_value_get_string (value));
break;
case ARG_MATROSKA_VERSION:
mux->matroska_version = g_value_get_int (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_matroska_mux_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec)
{
GstMatroskaMux *mux;
g_return_if_fail (GST_IS_MATROSKA_MUX (object));
mux = GST_MATROSKA_MUX (object);
switch (prop_id) {
case ARG_WRITING_APP:
g_value_set_string (value, mux->writing_app);
break;
case ARG_MATROSKA_VERSION:
g_value_set_int (value, mux->matroska_version);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
gboolean
gst_matroska_mux_plugin_init (GstPlugin * plugin)
{
return gst_element_register (plugin, "matroskamux",
GST_RANK_NONE, GST_TYPE_MATROSKA_MUX);
}