mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-28 19:20:35 +00:00
a12314291f
Original commit message from CVS: * gst/asfdemux/Makefile.am: * gst/asfdemux/asfheaders.c: * gst/asfdemux/asfheaders.h: * gst/asfdemux/gstasf.c: (plugin_init): * gst/asfdemux/gstasfdemux.c: (gst_asf_demux_get_type), (gst_asf_demux_base_init), (gst_asf_demux_process_comment), (gst_asf_demux_setup_pad): * gst/asfdemux/gstasfdemux.h: * gst/asfdemux/gstasfmux.c: * gst/asfdemux/gstasfmux.h: Add tagging support to demuxer, split out registration in its own file instead of in demux (hacky), and prevent having some tables in our memory multiple times (in asfheaders.h).
1289 lines
38 KiB
C
1289 lines
38 KiB
C
/* ASF muxer plugin for GStreamer
|
|
* Copyright (C) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/* based on:
|
|
* - ffmpeg ASF muxer
|
|
* - Owen's GStreamer ASF demuxer ("just reverse it"(tm)?)
|
|
* - Some own random bits and bytes and stuffies and more
|
|
* - Grolsch (oh, and Heineken) beer
|
|
* - Borrelnootjes (and chips, and stuff)
|
|
* - Why are you reading this?
|
|
*
|
|
* "The best code is written when you're drunk.
|
|
* You'll just never understand it, too."
|
|
* -- truth hurts.
|
|
*/
|
|
|
|
/* stream does NOT work on Windows Media Player (does work on
|
|
* other (Linux-based) players, because we do not specify bitrate
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <gst/video/video.h>
|
|
|
|
/* for audio codec IDs */
|
|
#include <gst/riff/riff-ids.h>
|
|
|
|
#include "asfheaders.h"
|
|
#include "gstasfmux.h"
|
|
|
|
/* elementfactory information */
|
|
static GstElementDetails gst_asfmux_details = {
|
|
"Asf multiplexer",
|
|
"Codec/Muxer",
|
|
"Muxes audio and video streams into an asf stream",
|
|
"Ronald Bultje <rbultje@ronald.bitfreak.net>",
|
|
};
|
|
|
|
/* AsfMux signals and args */
|
|
enum
|
|
{
|
|
/* FILL ME */
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
enum
|
|
{
|
|
ARG_0,
|
|
};
|
|
|
|
static GstStaticPadTemplate gst_asfmux_src_template =
|
|
GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("video/x-ms-asf")
|
|
);
|
|
|
|
static GstStaticPadTemplate gst_asfmux_videosink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("video_%d",
|
|
GST_PAD_SINK,
|
|
GST_PAD_REQUEST,
|
|
GST_STATIC_CAPS ("video/x-raw-yuv, "
|
|
"format = (fourcc) { YUY2, I420 }, "
|
|
"width = (int) [ 1, MAX], "
|
|
"height = (int) [ 1, MAX]; "
|
|
"video/x-jpeg, "
|
|
"width = (int) [ 1, MAX], "
|
|
"height = (int) [ 1, MAX]; "
|
|
"video/x-divx, "
|
|
"divxversion = (int) [ 3, 5 ], "
|
|
"width = (int) [ 1, MAX], "
|
|
"height = (int) [ 1, MAX]; "
|
|
"video/x-xvid, "
|
|
"width = (int) [ 1, MAX], "
|
|
"height = (int) [ 1, MAX]; "
|
|
"video/x-3ivx, "
|
|
"width = (int) [ 1, MAX], "
|
|
"height = (int) [ 1, MAX]; "
|
|
"video/x-msmpeg, "
|
|
"msmpegversion = (int) [ 41, 43 ], "
|
|
"width = (int) [ 1, MAX], "
|
|
"height = (int) [ 1, MAX]; "
|
|
"video/mpeg, "
|
|
"mpegversion = (int) 1,"
|
|
"systemstream = (boolean) false,"
|
|
"width = (int) [ 1, MAX], "
|
|
"height = (int) [ 1, MAX]; "
|
|
"video/x-h263, "
|
|
"width = (int) [ 1, MAX], "
|
|
"height = (int) [ 1, MAX]; "
|
|
"video/x-dv, "
|
|
"systemstream = (boolean) false,"
|
|
"width = (int) 720,"
|
|
"height = (int) { 576, 480 };"
|
|
"video/x-huffyuv, "
|
|
"width = (int) [ 1, MAX], " "height = (int) [ 1, MAX]")
|
|
);
|
|
|
|
static GstStaticPadTemplate gst_asfmux_audiosink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("audio_%d",
|
|
GST_PAD_SINK,
|
|
GST_PAD_REQUEST,
|
|
GST_STATIC_CAPS ("audio/x-raw-int, "
|
|
"endianness = (int) LITTLE_ENDIAN, "
|
|
"signed = (boolean) { true, false }, "
|
|
"width = (int) { 8, 16 }, "
|
|
"depth = (int) { 8, 16 }, "
|
|
"rate = (int) [ 1000, 96000 ], "
|
|
"channels = (int) [ 1, 2]; "
|
|
"audio/mpeg, "
|
|
"mpegversion = (int) 1, "
|
|
"layer = (int) { 1, 3 }, "
|
|
"rate = (int) [ 1000, 96000 ], "
|
|
"channels = (int) [ 1, 2]; "
|
|
"audio/x-vorbis, "
|
|
"rate = (int) [ 1000, 96000 ], "
|
|
"channels = (int) [ 1, 2]; "
|
|
"audio/x-ac3, "
|
|
"rate = (int) [ 1000, 96000 ], " "channels = (int) [ 1, 2]")
|
|
);
|
|
|
|
#define GST_ASF_PACKET_SIZE 3200
|
|
#define GST_ASF_PACKET_HEADER_SIZE 12
|
|
#define GST_ASF_FRAME_HEADER_SIZE 17
|
|
|
|
static void gst_asfmux_base_init (gpointer g_class);
|
|
static void gst_asfmux_class_init (GstAsfMuxClass * klass);
|
|
static void gst_asfmux_init (GstAsfMux * asfmux);
|
|
|
|
static void gst_asfmux_loop (GstElement * element);
|
|
static gboolean gst_asfmux_handle_event (GstPad * pad, GstEvent * event);
|
|
static GstPad *gst_asfmux_request_new_pad (GstElement * element,
|
|
GstPadTemplate * templ, const gchar * name);
|
|
static GstElementStateReturn gst_asfmux_change_state (GstElement * element);
|
|
|
|
static GstElementClass *parent_class = NULL;
|
|
|
|
/*static guint gst_asfmux_signals[LAST_SIGNAL] = { 0 }; */
|
|
|
|
GType
|
|
gst_asfmux_get_type (void)
|
|
{
|
|
static GType asfmux_type = 0;
|
|
|
|
if (!asfmux_type) {
|
|
static const GTypeInfo asfmux_info = {
|
|
sizeof (GstAsfMuxClass),
|
|
gst_asfmux_base_init,
|
|
NULL,
|
|
(GClassInitFunc) gst_asfmux_class_init,
|
|
NULL,
|
|
NULL,
|
|
sizeof (GstAsfMux),
|
|
0,
|
|
(GInstanceInitFunc) gst_asfmux_init,
|
|
};
|
|
|
|
asfmux_type = g_type_register_static (GST_TYPE_ELEMENT,
|
|
"GstAsfMux", &asfmux_info, 0);
|
|
}
|
|
return asfmux_type;
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_base_init (gpointer g_class)
|
|
{
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&gst_asfmux_src_template));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&gst_asfmux_videosink_template));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&gst_asfmux_audiosink_template));
|
|
gst_element_class_set_details (element_class, &gst_asfmux_details);
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_class_init (GstAsfMuxClass * klass)
|
|
{
|
|
GstElementClass *gstelement_class;
|
|
|
|
gstelement_class = (GstElementClass *) klass;
|
|
|
|
parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
|
|
|
|
gstelement_class->request_new_pad = gst_asfmux_request_new_pad;
|
|
gstelement_class->change_state = gst_asfmux_change_state;
|
|
}
|
|
|
|
static const GstEventMask *
|
|
gst_asfmux_get_event_masks (GstPad * pad)
|
|
{
|
|
static const GstEventMask gst_asfmux_sink_event_masks[] = {
|
|
{GST_EVENT_EOS, 0},
|
|
{0,}
|
|
};
|
|
|
|
return gst_asfmux_sink_event_masks;
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_init (GstAsfMux * asfmux)
|
|
{
|
|
gint n;
|
|
|
|
asfmux->srcpad =
|
|
gst_pad_new_from_template (gst_static_pad_template_get
|
|
(&gst_asfmux_src_template), "src");
|
|
gst_element_add_pad (GST_ELEMENT (asfmux), asfmux->srcpad);
|
|
|
|
GST_FLAG_SET (GST_ELEMENT (asfmux), GST_ELEMENT_EVENT_AWARE);
|
|
|
|
asfmux->num_outputs = asfmux->num_video = asfmux->num_audio = 0;
|
|
memset (&asfmux->output, 0, sizeof (asfmux->output));
|
|
for (n = 0; n < MAX_ASF_OUTPUTS; n++) {
|
|
asfmux->output[n].index = n;
|
|
asfmux->output[n].connected = FALSE;
|
|
}
|
|
asfmux->write_header = TRUE;
|
|
asfmux->packet = NULL;
|
|
asfmux->num_packets = 0;
|
|
asfmux->sequence = 0;
|
|
|
|
gst_element_set_loop_function (GST_ELEMENT (asfmux), gst_asfmux_loop);
|
|
}
|
|
|
|
static GstPadLinkReturn
|
|
gst_asfmux_vidsink_link (GstPad * pad, const GstCaps * caps)
|
|
{
|
|
GstAsfMux *asfmux;
|
|
GstAsfMuxStream *stream = NULL;
|
|
GstStructure *structure;
|
|
gint n;
|
|
const gchar *mimetype;
|
|
gint w, h;
|
|
gboolean ret;
|
|
|
|
asfmux = GST_ASFMUX (gst_pad_get_parent (pad));
|
|
|
|
for (n = 0; n < asfmux->num_outputs; n++) {
|
|
if (asfmux->output[n].pad == pad) {
|
|
stream = &asfmux->output[n];
|
|
break;
|
|
}
|
|
}
|
|
g_assert (n < asfmux->num_outputs);
|
|
g_assert (stream != NULL);
|
|
g_assert (stream->type == ASF_STREAM_VIDEO);
|
|
|
|
GST_DEBUG ("asfmux: video sinkconnect triggered on %s",
|
|
gst_pad_get_name (pad));
|
|
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
|
|
/* global */
|
|
ret = gst_structure_get_int (structure, "width", &w);
|
|
ret &= gst_structure_get_int (structure, "height", &h);
|
|
|
|
if (!ret)
|
|
return GST_PAD_LINK_REFUSED;
|
|
|
|
stream->header.video.stream.width = w;
|
|
stream->header.video.stream.height = h;
|
|
stream->header.video.stream.unknown = 2;
|
|
stream->header.video.stream.size = 40;
|
|
stream->bitrate = 0; /* TODO */
|
|
|
|
mimetype = gst_structure_get_name (structure);
|
|
if (!strcmp (mimetype, "video/x-raw-yuv")) {
|
|
guint32 format;
|
|
|
|
ret = gst_structure_get_fourcc (structure, "format", &format);
|
|
if (!ret)
|
|
return GST_PAD_LINK_REFUSED;
|
|
|
|
stream->header.video.format.tag = format;
|
|
switch (format) {
|
|
case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'):
|
|
stream->header.video.format.depth = 16;
|
|
stream->header.video.format.planes = 1;
|
|
break;
|
|
case GST_MAKE_FOURCC ('I', '4', '2', '0'):
|
|
stream->header.video.format.depth = 12;
|
|
stream->header.video.format.planes = 3;
|
|
break;
|
|
}
|
|
|
|
goto done;
|
|
} else {
|
|
stream->header.video.format.depth = 24;
|
|
stream->header.video.format.planes = 1;
|
|
stream->header.video.format.tag = 0;
|
|
|
|
/* find format */
|
|
if (!strcmp (mimetype, "video/x-huffyuv")) {
|
|
stream->header.video.format.tag = GST_MAKE_FOURCC ('H', 'F', 'Y', 'U');
|
|
} else if (!strcmp (mimetype, "video/x-jpeg")) {
|
|
stream->header.video.format.tag = GST_MAKE_FOURCC ('M', 'J', 'P', 'G');
|
|
} else if (!strcmp (mimetype, "video/x-divx")) {
|
|
gint divxversion;
|
|
|
|
gst_structure_get_int (structure, "divxversion", &divxversion);
|
|
switch (divxversion) {
|
|
case 3:
|
|
stream->header.video.format.tag =
|
|
GST_MAKE_FOURCC ('D', 'I', 'V', '3');
|
|
break;
|
|
case 4:
|
|
stream->header.video.format.tag =
|
|
GST_MAKE_FOURCC ('D', 'I', 'V', 'X');
|
|
break;
|
|
case 5:
|
|
stream->header.video.format.tag =
|
|
GST_MAKE_FOURCC ('D', 'X', '5', '0');
|
|
break;
|
|
}
|
|
} else if (!strcmp (mimetype, "video/x-xvid")) {
|
|
stream->header.video.format.tag = GST_MAKE_FOURCC ('X', 'V', 'I', 'D');
|
|
} else if (!strcmp (mimetype, "video/x-3ivx")) {
|
|
stream->header.video.format.tag = GST_MAKE_FOURCC ('3', 'I', 'V', '2');
|
|
} else if (!strcmp (mimetype, "video/x-msmpeg")) {
|
|
gint msmpegversion;
|
|
|
|
gst_structure_get_int (structure, "msmpegversion", &msmpegversion);
|
|
switch (msmpegversion) {
|
|
case 41:
|
|
stream->header.video.format.tag =
|
|
GST_MAKE_FOURCC ('M', 'P', 'G', '4');
|
|
break;
|
|
case 42:
|
|
stream->header.video.format.tag =
|
|
GST_MAKE_FOURCC ('M', 'P', '4', '2');
|
|
break;
|
|
case 43:
|
|
stream->header.video.format.tag =
|
|
GST_MAKE_FOURCC ('M', 'P', '4', '3');
|
|
break;
|
|
}
|
|
} else if (!strcmp (mimetype, "video/x-dv")) {
|
|
stream->header.video.format.tag = GST_MAKE_FOURCC ('D', 'V', 'S', 'D');
|
|
} else if (!strcmp (mimetype, "video/x-h263")) {
|
|
stream->header.video.format.tag = GST_MAKE_FOURCC ('H', '2', '6', '3');
|
|
} else if (!strcmp (mimetype, "video/mpeg")) {
|
|
stream->header.video.format.tag = GST_MAKE_FOURCC ('M', 'P', 'E', 'G');
|
|
}
|
|
|
|
if (!stream->header.video.format.tag) {
|
|
return GST_PAD_LINK_REFUSED;
|
|
}
|
|
|
|
goto done;
|
|
}
|
|
return GST_PAD_LINK_REFUSED;
|
|
|
|
done:
|
|
stream->bitrate = 1024 * 1024;
|
|
stream->header.video.format.size = stream->header.video.stream.size;
|
|
stream->header.video.format.width = stream->header.video.stream.width;
|
|
stream->header.video.format.height = stream->header.video.stream.height;
|
|
stream->header.video.format.image_size = stream->header.video.stream.width *
|
|
stream->header.video.stream.height;
|
|
stream->header.video.format.xpels_meter = 0;
|
|
stream->header.video.format.ypels_meter = 0;
|
|
stream->header.video.format.num_colors = 0;
|
|
stream->header.video.format.imp_colors = 0;
|
|
|
|
return GST_PAD_LINK_OK;
|
|
}
|
|
|
|
static GstPadLinkReturn
|
|
gst_asfmux_audsink_link (GstPad * pad, const GstCaps * caps)
|
|
{
|
|
GstAsfMux *asfmux;
|
|
GstAsfMuxStream *stream = NULL;
|
|
gint n;
|
|
const gchar *mimetype;
|
|
gint rate, channels;
|
|
gboolean ret;
|
|
GstStructure *structure;
|
|
|
|
asfmux = GST_ASFMUX (gst_pad_get_parent (pad));
|
|
|
|
for (n = 0; n < asfmux->num_outputs; n++) {
|
|
if (asfmux->output[n].pad == pad) {
|
|
stream = &asfmux->output[n];
|
|
break;
|
|
}
|
|
}
|
|
g_assert (n < asfmux->num_outputs);
|
|
g_assert (stream != NULL);
|
|
g_assert (stream->type == ASF_STREAM_AUDIO);
|
|
|
|
GST_DEBUG ("asfmux: audio sink_link triggered on %s", gst_pad_get_name (pad));
|
|
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
|
|
/* we want these for all */
|
|
ret = gst_structure_get_int (structure, "channels", &channels);
|
|
ret &= gst_structure_get_int (structure, "rate", &rate);
|
|
|
|
if (!ret)
|
|
return GST_PAD_LINK_REFUSED;
|
|
|
|
stream->header.audio.sample_rate = rate;
|
|
stream->header.audio.channels = channels;
|
|
|
|
mimetype = gst_structure_get_name (structure);
|
|
if (!strcmp (mimetype, "audio/x-raw-int")) {
|
|
gint block, size;
|
|
|
|
stream->header.audio.codec_tag = GST_RIFF_WAVE_FORMAT_PCM;
|
|
|
|
gst_structure_get_int (structure, "width", &block);
|
|
gst_structure_get_int (structure, "depth", &size);
|
|
|
|
stream->header.audio.block_align = block;
|
|
stream->header.audio.word_size = size;
|
|
stream->header.audio.size = 0;
|
|
|
|
/* set some more info straight */
|
|
stream->header.audio.block_align /= 8;
|
|
stream->header.audio.block_align *= stream->header.audio.channels;
|
|
stream->header.audio.byte_rate = stream->header.audio.block_align *
|
|
stream->header.audio.sample_rate;
|
|
goto done;
|
|
} else {
|
|
stream->header.audio.codec_tag = 0;
|
|
|
|
if (!strcmp (mimetype, "audio/mpeg")) {
|
|
gint layer = 3;
|
|
|
|
gst_structure_get_int (structure, "layer", &layer);
|
|
switch (layer) {
|
|
case 3:
|
|
stream->header.audio.codec_tag = GST_RIFF_WAVE_FORMAT_MPEGL3;
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
stream->header.audio.codec_tag = GST_RIFF_WAVE_FORMAT_MPEGL12;
|
|
break;
|
|
}
|
|
} else if (!strcmp (mimetype, "audio/x-vorbis")) {
|
|
stream->header.audio.codec_tag = GST_RIFF_WAVE_FORMAT_VORBIS3;
|
|
} else if (!strcmp (mimetype, "audio/x-ac3")) {
|
|
stream->header.audio.codec_tag = GST_RIFF_WAVE_FORMAT_A52;
|
|
}
|
|
|
|
stream->header.audio.block_align = 1;
|
|
stream->header.audio.byte_rate = 8 * 1024;
|
|
stream->header.audio.word_size = 16;
|
|
stream->header.audio.size = 0;
|
|
|
|
if (!stream->header.audio.codec_tag) {
|
|
return GST_PAD_LINK_REFUSED;
|
|
}
|
|
|
|
goto done;
|
|
}
|
|
return GST_PAD_LINK_REFUSED;
|
|
|
|
done:
|
|
stream->bitrate = stream->header.audio.byte_rate * 8;
|
|
return GST_PAD_LINK_OK;
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_pad_link (GstPad * pad, GstPad * peer, gpointer data)
|
|
{
|
|
GstAsfMux *asfmux;
|
|
GstAsfMuxStream *stream = NULL;
|
|
gint n;
|
|
|
|
asfmux = GST_ASFMUX (gst_pad_get_parent (pad));
|
|
|
|
for (n = 0; n < asfmux->num_outputs; n++) {
|
|
if (asfmux->output[n].pad == pad) {
|
|
stream = &asfmux->output[n];
|
|
break;
|
|
}
|
|
}
|
|
g_assert (n < asfmux->num_outputs);
|
|
g_assert (stream != NULL);
|
|
g_assert (stream->connected == FALSE);
|
|
|
|
stream->connected = TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_pad_unlink (GstPad * pad, GstPad * peer, gpointer data)
|
|
{
|
|
GstAsfMux *asfmux;
|
|
GstAsfMuxStream *stream = NULL;
|
|
gint n;
|
|
|
|
asfmux = GST_ASFMUX (gst_pad_get_parent (pad));
|
|
|
|
for (n = 0; n < asfmux->num_outputs; n++) {
|
|
if (asfmux->output[n].pad == pad) {
|
|
stream = &asfmux->output[n];
|
|
break;
|
|
}
|
|
}
|
|
g_assert (n < asfmux->num_outputs);
|
|
g_assert (stream != NULL);
|
|
g_assert (stream->connected == TRUE);
|
|
|
|
stream->connected = FALSE;
|
|
}
|
|
|
|
static GstPad *
|
|
gst_asfmux_request_new_pad (GstElement * element,
|
|
GstPadTemplate * templ, const gchar * req_name)
|
|
{
|
|
GstAsfMux *asfmux;
|
|
GstPad *newpad;
|
|
gchar *padname;
|
|
GstPadLinkFunction linkfunc;
|
|
GstAsfMuxStream *stream;
|
|
GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
|
|
|
|
g_return_val_if_fail (templ != NULL, NULL);
|
|
g_return_val_if_fail (templ->direction == GST_PAD_SINK, NULL);
|
|
g_return_val_if_fail (GST_IS_ASFMUX (element), NULL);
|
|
|
|
asfmux = GST_ASFMUX (element);
|
|
|
|
stream = &asfmux->output[asfmux->num_outputs++];
|
|
stream->queue = NULL;
|
|
stream->time = 0;
|
|
stream->connected = FALSE;
|
|
stream->eos = FALSE;
|
|
stream->seqnum = 0;
|
|
|
|
if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
|
|
padname = g_strdup_printf ("audio_%02d", asfmux->num_audio++);
|
|
stream->type = ASF_STREAM_AUDIO;
|
|
linkfunc = gst_asfmux_audsink_link;
|
|
} else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
|
|
padname = g_strdup_printf ("video_%02d", asfmux->num_video++);
|
|
stream->type = ASF_STREAM_VIDEO;
|
|
linkfunc = gst_asfmux_vidsink_link;
|
|
} else {
|
|
g_warning ("asfmux: this is not our template!\n");
|
|
return NULL;
|
|
}
|
|
|
|
newpad = gst_pad_new_from_template (templ, padname);
|
|
stream->pad = newpad;
|
|
g_free (padname);
|
|
|
|
g_signal_connect (newpad, "linked",
|
|
G_CALLBACK (gst_asfmux_pad_link), (gpointer) asfmux);
|
|
g_signal_connect (newpad, "unlinked",
|
|
G_CALLBACK (gst_asfmux_pad_unlink), (gpointer) asfmux);
|
|
gst_pad_set_link_function (newpad, linkfunc);
|
|
gst_element_add_pad (element, newpad);
|
|
gst_pad_set_event_function (newpad, gst_asfmux_handle_event);
|
|
gst_pad_set_event_mask_function (newpad, gst_asfmux_get_event_masks);
|
|
|
|
return newpad;
|
|
}
|
|
|
|
/* can we seek? If not, we assume we're streamable */
|
|
static gboolean
|
|
gst_asfmux_can_seek (GstAsfMux * asfmux)
|
|
{
|
|
#if 0
|
|
const GstEventMask *masks =
|
|
gst_pad_get_event_masks (GST_PAD_PEER (asfmux->srcpad));
|
|
|
|
/* this is for stream or file-storage */
|
|
while (masks != NULL && masks->type != 0) {
|
|
if (masks->type == GST_EVENT_SEEK) {
|
|
return TRUE;
|
|
} else {
|
|
masks++;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_asfmux_is_stream (GstAsfMux * asfmux)
|
|
{
|
|
/* this is for RTP */
|
|
return FALSE; /*!gst_asfmux_can_seek (asfmux) */
|
|
}
|
|
|
|
/* handle events (search) */
|
|
static gboolean
|
|
gst_asfmux_handle_event (GstPad * pad, GstEvent * event)
|
|
{
|
|
GstAsfMux *asfmux;
|
|
GstEventType type;
|
|
gint n;
|
|
|
|
asfmux = GST_ASFMUX (gst_pad_get_parent (pad));
|
|
|
|
type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN;
|
|
|
|
switch (type) {
|
|
case GST_EVENT_EOS:
|
|
/* is this allright? */
|
|
for (n = 0; n < asfmux->num_outputs; n++) {
|
|
if (asfmux->output[n].pad == pad) {
|
|
asfmux->output[n].eos = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (n == asfmux->num_outputs) {
|
|
g_warning ("Unknown pad for EOS!");
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* fill the internal queue for each available pad */
|
|
static void
|
|
gst_asfmux_fill_queue (GstAsfMux * asfmux)
|
|
{
|
|
GstBuffer *buffer;
|
|
gint n;
|
|
|
|
for (n = 0; n < asfmux->num_outputs; n++) {
|
|
GstAsfMuxStream *stream = &asfmux->output[n];
|
|
|
|
while (stream->queue == NULL &&
|
|
stream->pad != NULL &&
|
|
stream->connected == TRUE &&
|
|
GST_PAD_IS_USABLE (stream->pad) && stream->eos == FALSE) {
|
|
buffer = GST_BUFFER (gst_pad_pull (stream->pad));
|
|
if (GST_IS_EVENT (buffer)) {
|
|
gst_asfmux_handle_event (stream->pad, GST_EVENT (buffer));
|
|
} else {
|
|
stream->queue = buffer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static guint
|
|
gst_asfmux_packet_remaining (GstAsfMux * asfmux)
|
|
{
|
|
guint position;
|
|
|
|
if (asfmux->packet != NULL) {
|
|
position = GST_BUFFER_SIZE (asfmux->packet);
|
|
} else {
|
|
position = 0;
|
|
}
|
|
|
|
return GST_ASF_PACKET_SIZE - GST_ASF_PACKET_HEADER_SIZE - 2 - position;
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_put_buffer (GstBuffer * packet, guint8 * data, guint length)
|
|
{
|
|
if ((GST_BUFFER_MAXSIZE (packet) - GST_BUFFER_SIZE (packet)) >= length) {
|
|
guint8 *pos = GST_BUFFER_DATA (packet) + GST_BUFFER_SIZE (packet);
|
|
|
|
memcpy (pos, data, length);
|
|
GST_BUFFER_SIZE (packet) += length;
|
|
} else {
|
|
g_warning ("Buffer too small");
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_put_byte (GstBuffer * packet, guint8 data)
|
|
{
|
|
if ((GST_BUFFER_MAXSIZE (packet) - GST_BUFFER_SIZE (packet)) >= sizeof (data)) {
|
|
guint8 *pos = GST_BUFFER_DATA (packet) + GST_BUFFER_SIZE (packet);
|
|
|
|
*(guint8 *) pos = data;
|
|
GST_BUFFER_SIZE (packet) += 1;
|
|
} else {
|
|
g_warning ("Buffer too small");
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_put_le16 (GstBuffer * packet, guint16 data)
|
|
{
|
|
if ((GST_BUFFER_MAXSIZE (packet) - GST_BUFFER_SIZE (packet)) >= sizeof (data)) {
|
|
guint8 *pos = GST_BUFFER_DATA (packet) + GST_BUFFER_SIZE (packet);
|
|
|
|
*(guint16 *) pos = GUINT16_TO_LE (data);
|
|
GST_BUFFER_SIZE (packet) += 2;
|
|
} else {
|
|
g_warning ("Buffer too small");
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_put_le32 (GstBuffer * packet, guint32 data)
|
|
{
|
|
if ((GST_BUFFER_MAXSIZE (packet) - GST_BUFFER_SIZE (packet)) >= sizeof (data)) {
|
|
guint8 *pos = GST_BUFFER_DATA (packet) + GST_BUFFER_SIZE (packet);
|
|
|
|
*(guint32 *) pos = GUINT32_TO_LE (data);
|
|
GST_BUFFER_SIZE (packet) += 4;
|
|
} else {
|
|
g_warning ("Buffer too small");
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_put_le64 (GstBuffer * packet, guint64 data)
|
|
{
|
|
if ((GST_BUFFER_MAXSIZE (packet) - GST_BUFFER_SIZE (packet)) >= sizeof (data)) {
|
|
guint8 *pos = GST_BUFFER_DATA (packet) + GST_BUFFER_SIZE (packet);
|
|
|
|
*(guint64 *) pos = GUINT64_TO_LE (data);
|
|
GST_BUFFER_SIZE (packet) += 8;
|
|
} else {
|
|
g_warning ("Buffer too small");
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_put_time (GstBuffer * packet, guint64 time)
|
|
{
|
|
gst_asfmux_put_le64 (packet, time + 116444736000000000LLU);
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_put_guid (GstBuffer * packet, ASFGuidHash * hash, guint8 id)
|
|
{
|
|
gint n = 0;
|
|
ASFGuid *guid;
|
|
|
|
/* find GUID */
|
|
while (hash[n].obj_id != id && hash[n].obj_id != ASF_OBJ_UNDEFINED) {
|
|
n++;
|
|
}
|
|
guid = &hash[n].guid;
|
|
|
|
gst_asfmux_put_le32 (packet, guid->v1);
|
|
gst_asfmux_put_le32 (packet, guid->v2);
|
|
gst_asfmux_put_le32 (packet, guid->v3);
|
|
gst_asfmux_put_le32 (packet, guid->v4);
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_put_string (GstBuffer * packet, const gchar * str)
|
|
{
|
|
gunichar2 *utf16_str = g_utf8_to_utf16 (str, strlen (str), NULL, NULL, NULL);
|
|
gint i, len = strlen (str);
|
|
|
|
/* this is not an off-by-one-bug, we need the terminating /0 too */
|
|
for (i = 0; i <= len; i++) {
|
|
gst_asfmux_put_le16 (packet, utf16_str[i]);
|
|
}
|
|
|
|
g_free (utf16_str);
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_put_flush (GstAsfMux * asfmux)
|
|
{
|
|
gst_pad_push (asfmux->srcpad, GST_DATA (gst_event_new_flush ()));
|
|
}
|
|
|
|
/* write an asf chunk (only used in streaming case) */
|
|
static void
|
|
gst_asfmux_put_chunk (GstBuffer * packet,
|
|
GstAsfMux * asfmux, guint16 type, guint length, gint flags)
|
|
{
|
|
gst_asfmux_put_le16 (packet, type);
|
|
gst_asfmux_put_le16 (packet, length + 8);
|
|
gst_asfmux_put_le32 (packet, asfmux->sequence++);
|
|
gst_asfmux_put_le16 (packet, flags);
|
|
gst_asfmux_put_le16 (packet, length + 8);
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_put_wav_header (GstBuffer * packet, asf_stream_audio * hdr)
|
|
{
|
|
gst_asfmux_put_le16 (packet, hdr->codec_tag);
|
|
gst_asfmux_put_le16 (packet, hdr->channels);
|
|
gst_asfmux_put_le32 (packet, hdr->sample_rate);
|
|
gst_asfmux_put_le32 (packet, hdr->byte_rate);
|
|
gst_asfmux_put_le16 (packet, hdr->block_align);
|
|
gst_asfmux_put_le16 (packet, hdr->word_size);
|
|
gst_asfmux_put_le16 (packet, hdr->size);
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_put_vid_header (GstBuffer * packet, asf_stream_video * hdr)
|
|
{
|
|
gst_asfmux_put_le32 (packet, hdr->width);
|
|
gst_asfmux_put_le32 (packet, hdr->height);
|
|
gst_asfmux_put_byte (packet, hdr->unknown);
|
|
gst_asfmux_put_le16 (packet, hdr->size);
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_put_bmp_header (GstBuffer * packet, asf_stream_video_format * hdr)
|
|
{
|
|
gst_asfmux_put_le32 (packet, hdr->size);
|
|
gst_asfmux_put_le32 (packet, hdr->width);
|
|
gst_asfmux_put_le32 (packet, hdr->height);
|
|
gst_asfmux_put_le16 (packet, hdr->planes);
|
|
gst_asfmux_put_le16 (packet, hdr->depth);
|
|
gst_asfmux_put_le32 (packet, hdr->tag);
|
|
gst_asfmux_put_le32 (packet, hdr->image_size);
|
|
gst_asfmux_put_le32 (packet, hdr->xpels_meter);
|
|
gst_asfmux_put_le32 (packet, hdr->ypels_meter);
|
|
gst_asfmux_put_le32 (packet, hdr->num_colors);
|
|
gst_asfmux_put_le32 (packet, hdr->imp_colors);
|
|
}
|
|
|
|
/* init header */
|
|
static guint
|
|
gst_asfmux_put_header (GstBuffer * packet, ASFGuidHash * hash, guint8 id)
|
|
{
|
|
guint pos = GST_BUFFER_SIZE (packet);
|
|
|
|
gst_asfmux_put_guid (packet, hash, id);
|
|
gst_asfmux_put_le64 (packet, 24);
|
|
return pos;
|
|
}
|
|
|
|
/* update header size */
|
|
static void
|
|
gst_asfmux_end_header (GstBuffer * packet, guint pos)
|
|
{
|
|
guint cur = GST_BUFFER_SIZE (packet);
|
|
|
|
GST_BUFFER_SIZE (packet) = pos + sizeof (ASFGuid);
|
|
gst_asfmux_put_le64 (packet, cur - pos);
|
|
GST_BUFFER_SIZE (packet) = cur;
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_file_start (GstAsfMux * asfmux, guint64 file_size, guint64 data_size)
|
|
{
|
|
GstBuffer *header = gst_buffer_new_and_alloc (4096);
|
|
guint bitrate;
|
|
guint header_offset, header_pos, header_size;
|
|
gint n;
|
|
guint64 duration;
|
|
|
|
bitrate = 0;
|
|
for (n = 0; n < asfmux->num_outputs; n++) {
|
|
GstAsfMuxStream *stream = &asfmux->output[n];
|
|
|
|
bitrate += stream->bitrate;
|
|
}
|
|
|
|
GST_BUFFER_SIZE (header) = 0;
|
|
if (asfmux->packet != NULL) {
|
|
duration = GST_BUFFER_DURATION (asfmux->packet) +
|
|
GST_BUFFER_TIMESTAMP (asfmux->packet);
|
|
} else {
|
|
duration = 0;
|
|
}
|
|
|
|
if (gst_asfmux_is_stream (asfmux)) {
|
|
/* start of stream (length will be patched later) */
|
|
gst_asfmux_put_chunk (header, asfmux, 0x4824, 0, 0xc00);
|
|
}
|
|
|
|
gst_asfmux_put_guid (header, asf_object_guids, ASF_OBJ_HEADER);
|
|
/* header length, will be patched after */
|
|
gst_asfmux_put_le64 (header, ~0);
|
|
/* number of chunks in header */
|
|
gst_asfmux_put_le32 (header, 3 + /*has_title + */ asfmux->num_outputs);
|
|
gst_asfmux_put_byte (header, 1); /* ??? */
|
|
gst_asfmux_put_byte (header, 2); /* ??? */
|
|
|
|
/* file header */
|
|
header_offset = GST_BUFFER_SIZE (header);
|
|
header_pos = gst_asfmux_put_header (header, asf_object_guids, ASF_OBJ_FILE);
|
|
gst_asfmux_put_guid (header, asf_object_guids, ASF_OBJ_UNDEFINED);
|
|
gst_asfmux_put_le64 (header, file_size);
|
|
gst_asfmux_put_time (header, 0);
|
|
gst_asfmux_put_le64 (header, asfmux->num_packets); /* number of packets */
|
|
gst_asfmux_put_le64 (header, duration / (GST_SECOND / 10000000)); /* end time stamp (in 100ns units) */
|
|
gst_asfmux_put_le64 (header, duration / (GST_SECOND / 10000000)); /* duration (in 100ns units) */
|
|
gst_asfmux_put_le64 (header, 0); /* start time stamp */
|
|
gst_asfmux_put_le32 (header, gst_asfmux_can_seek (asfmux) ? 0x02 : 0x01); /* seekable or streamable */
|
|
gst_asfmux_put_le32 (header, GST_ASF_PACKET_SIZE); /* packet size */
|
|
gst_asfmux_put_le32 (header, GST_ASF_PACKET_SIZE); /* packet size */
|
|
gst_asfmux_put_le32 (header, bitrate); /* Nominal data rate in bps */
|
|
gst_asfmux_end_header (header, header_pos);
|
|
|
|
/* unknown headers */
|
|
header_pos = gst_asfmux_put_header (header, asf_object_guids, ASF_OBJ_HEAD1);
|
|
gst_asfmux_put_guid (header, asf_object_guids, ASF_OBJ_HEAD2);
|
|
gst_asfmux_put_le32 (header, 6);
|
|
gst_asfmux_put_le16 (header, 0);
|
|
gst_asfmux_end_header (header, header_pos);
|
|
|
|
/* title and other infos */
|
|
#if 0
|
|
if (has_title) {
|
|
header_pos =
|
|
gst_asfmux_put_header (header, asf_object_guids, ASF_OBJ_COMMENT);
|
|
gst_asfmux_put_le16 (header, 2 * (strlen (title) + 1));
|
|
gst_asfmux_put_le16 (header, 2 * (strlen (author) + 1));
|
|
gst_asfmux_put_le16 (header, 2 * (strlen (copyright) + 1));
|
|
gst_asfmux_put_le16 (header, 2 * (strlen (comment) + 1));
|
|
gst_asfmux_put_le16 (header, 0); /* rating */
|
|
gst_asfmux_put_string (header, title);
|
|
gst_asfmux_put_string (header, author);
|
|
gst_asfmux_put_string (header, copyright);
|
|
gst_asfmux_put_string (header, comment);
|
|
gst_asfmux_end_header (header, header_pos);
|
|
}
|
|
#endif
|
|
|
|
/* stream headers */
|
|
for (n = 0; n < asfmux->num_outputs; n++) {
|
|
GstAsfMuxStream *stream = &asfmux->output[n];
|
|
guint obj_size = 0;
|
|
|
|
stream->seqnum = 0;
|
|
header_pos =
|
|
gst_asfmux_put_header (header, asf_object_guids, ASF_OBJ_STREAM);
|
|
|
|
switch (stream->type) {
|
|
case ASF_STREAM_AUDIO:
|
|
obj_size = 18;
|
|
gst_asfmux_put_guid (header, asf_stream_guids, ASF_STREAM_AUDIO);
|
|
gst_asfmux_put_guid (header, asf_correction_guids, ASF_CORRECTION_OFF);
|
|
break;
|
|
case ASF_STREAM_VIDEO:
|
|
obj_size = 11 + 40;
|
|
gst_asfmux_put_guid (header, asf_stream_guids, ASF_STREAM_VIDEO);
|
|
gst_asfmux_put_guid (header, asf_correction_guids, ASF_CORRECTION_OFF);
|
|
break;
|
|
default:
|
|
g_assert (0);
|
|
}
|
|
|
|
gst_asfmux_put_le64 (header, 0); /* offset */
|
|
gst_asfmux_put_le32 (header, obj_size); /* wav header len */
|
|
gst_asfmux_put_le32 (header, 0); /* additional data len */
|
|
gst_asfmux_put_le16 (header, n + 1); /* stream number */
|
|
gst_asfmux_put_le32 (header, 0); /* ??? */
|
|
|
|
switch (stream->type) {
|
|
case ASF_STREAM_AUDIO:
|
|
gst_asfmux_put_wav_header (header, &stream->header.audio);
|
|
break;
|
|
case ASF_STREAM_VIDEO:
|
|
gst_asfmux_put_vid_header (header, &stream->header.video.stream);
|
|
gst_asfmux_put_bmp_header (header, &stream->header.video.format);
|
|
break;
|
|
}
|
|
|
|
gst_asfmux_end_header (header, header_pos);
|
|
}
|
|
|
|
/* media comments */
|
|
header_pos =
|
|
gst_asfmux_put_header (header, asf_object_guids, ASF_OBJ_CODEC_COMMENT);
|
|
gst_asfmux_put_guid (header, asf_object_guids, ASF_OBJ_CODEC_COMMENT1);
|
|
gst_asfmux_put_le32 (header, asfmux->num_outputs);
|
|
for (n = 0; n < asfmux->num_outputs; n++) {
|
|
GstAsfMuxStream *stream = &asfmux->output[n];
|
|
const char *codec = "Unknown codec";
|
|
|
|
gst_asfmux_put_le16 (header, stream->index + 1);
|
|
/* Isn't this wrong? This is UTF16! */
|
|
gst_asfmux_put_le16 (header, strlen (codec) + 1);
|
|
gst_asfmux_put_string (header, codec);
|
|
gst_asfmux_put_le16 (header, 0); /* no parameters */
|
|
|
|
/* id */
|
|
switch (stream->type) {
|
|
case ASF_STREAM_AUDIO:
|
|
gst_asfmux_put_le16 (header, 2);
|
|
gst_asfmux_put_le16 (header, stream->header.audio.codec_tag);
|
|
break;
|
|
case ASF_STREAM_VIDEO:
|
|
gst_asfmux_put_le16 (header, 4);
|
|
gst_asfmux_put_le32 (header, stream->header.video.format.tag);
|
|
break;
|
|
default:
|
|
g_assert (0);
|
|
}
|
|
}
|
|
gst_asfmux_end_header (header, header_pos);
|
|
|
|
/* patch the header size fields */
|
|
header_pos = GST_BUFFER_SIZE (header);
|
|
header_size = header_pos - header_offset;
|
|
if (gst_asfmux_is_stream (asfmux)) {
|
|
header_size += 8 + 30 + 50;
|
|
|
|
GST_BUFFER_SIZE (header) = header_offset - 10 - 30;
|
|
gst_asfmux_put_le16 (header, header_size);
|
|
GST_BUFFER_SIZE (header) = header_offset - 2 - 30;
|
|
gst_asfmux_put_le16 (header, header_size);
|
|
|
|
header_size -= 8 + 30 + 50;
|
|
}
|
|
header_size += 24 + 6;
|
|
GST_BUFFER_SIZE (header) = header_offset - 14;
|
|
gst_asfmux_put_le64 (header, header_size);
|
|
GST_BUFFER_SIZE (header) = header_pos;
|
|
|
|
/* movie chunk, followed by packets of packet_size */
|
|
asfmux->data_offset = GST_BUFFER_SIZE (header);
|
|
gst_asfmux_put_guid (header, asf_object_guids, ASF_OBJ_DATA);
|
|
gst_asfmux_put_le64 (header, data_size);
|
|
gst_asfmux_put_guid (header, asf_object_guids, ASF_OBJ_UNDEFINED);
|
|
gst_asfmux_put_le64 (header, asfmux->num_packets); /* nb packets */
|
|
gst_asfmux_put_byte (header, 1); /* ??? */
|
|
gst_asfmux_put_byte (header, 1); /* ??? */
|
|
|
|
gst_pad_push (asfmux->srcpad, GST_DATA (header));
|
|
asfmux->write_header = FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_file_stop (GstAsfMux * asfmux)
|
|
{
|
|
if (gst_asfmux_is_stream (asfmux)) {
|
|
/* send EOS chunk */
|
|
GstBuffer *footer = gst_buffer_new_and_alloc (16);
|
|
|
|
GST_BUFFER_SIZE (footer) = 0;
|
|
gst_asfmux_put_chunk (footer, asfmux, 0x4524, 0, 0); /* end of stream */
|
|
gst_pad_push (asfmux->srcpad, GST_DATA (footer));
|
|
} else if (gst_asfmux_can_seek (asfmux)) {
|
|
/* rewrite an updated header */
|
|
guint64 filesize;
|
|
GstFormat fmt = GST_FORMAT_BYTES;
|
|
GstEvent *event;
|
|
|
|
gst_pad_query (asfmux->srcpad, GST_QUERY_POSITION, &fmt, &filesize);
|
|
event = gst_event_new_seek (GST_SEEK_METHOD_SET | GST_FORMAT_BYTES, 0);
|
|
gst_pad_push (asfmux->srcpad, GST_DATA (event));
|
|
gst_asfmux_file_start (asfmux, filesize, filesize - asfmux->data_offset);
|
|
event =
|
|
gst_event_new_seek (GST_SEEK_METHOD_SET | GST_FORMAT_BYTES, filesize);
|
|
gst_pad_push (asfmux->srcpad, GST_DATA (event));
|
|
}
|
|
|
|
gst_asfmux_put_flush (asfmux);
|
|
}
|
|
|
|
static GstBuffer *
|
|
gst_asfmux_packet_header (GstAsfMux * asfmux)
|
|
{
|
|
GstBuffer *packet = asfmux->packet, *header;
|
|
guint flags, padsize = gst_asfmux_packet_remaining (asfmux);
|
|
|
|
header = gst_buffer_new_and_alloc (GST_ASF_PACKET_HEADER_SIZE + 2 + 12);
|
|
GST_BUFFER_SIZE (header) = 0;
|
|
|
|
if (gst_asfmux_is_stream (asfmux)) {
|
|
gst_asfmux_put_chunk (header, asfmux, 0x4424, GST_ASF_PACKET_SIZE, 0);
|
|
}
|
|
|
|
gst_asfmux_put_byte (header, 0x82);
|
|
gst_asfmux_put_le16 (header, 0);
|
|
|
|
flags = 0x01; /* nb segments present */
|
|
if (padsize > 0) {
|
|
if (padsize < 256) {
|
|
flags |= 0x08;
|
|
} else {
|
|
flags |= 0x10;
|
|
}
|
|
}
|
|
gst_asfmux_put_byte (header, flags); /* flags */
|
|
gst_asfmux_put_byte (header, 0x5d);
|
|
if (flags & 0x10) {
|
|
gst_asfmux_put_le16 (header, padsize - 2);
|
|
} else if (flags & 0x08) {
|
|
gst_asfmux_put_byte (header, padsize - 1);
|
|
}
|
|
gst_asfmux_put_le32 (header,
|
|
GST_BUFFER_TIMESTAMP (packet) / (GST_SECOND / 1000));
|
|
gst_asfmux_put_le16 (header,
|
|
GST_BUFFER_DURATION (packet) / (GST_SECOND / 1000));
|
|
gst_asfmux_put_byte (header, asfmux->packet_frames | 0x80);
|
|
|
|
return header;
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_frame_header (GstAsfMux * asfmux,
|
|
GstAsfMuxStream * stream,
|
|
guint position, guint length, guint total, guint64 time, gboolean key)
|
|
{
|
|
/* fill in some values for the packet */
|
|
if (!GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (asfmux->packet))) {
|
|
GST_BUFFER_TIMESTAMP (asfmux->packet) = time;
|
|
}
|
|
GST_BUFFER_DURATION (asfmux->packet) =
|
|
time - GST_BUFFER_TIMESTAMP (asfmux->packet);
|
|
|
|
gst_asfmux_put_byte (asfmux->packet, (stream->index + 1) | 0x80); //(key ? 0x80 : 0));
|
|
gst_asfmux_put_byte (asfmux->packet, stream->seqnum);
|
|
gst_asfmux_put_le32 (asfmux->packet, position);
|
|
gst_asfmux_put_byte (asfmux->packet, 0x08);
|
|
gst_asfmux_put_le32 (asfmux->packet, total);
|
|
gst_asfmux_put_le32 (asfmux->packet, time / (GST_SECOND / 1000)); /* time in ms */
|
|
gst_asfmux_put_le16 (asfmux->packet, length);
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_frame_buffer (GstAsfMux * asfmux, guint8 * data, guint length)
|
|
{
|
|
gst_asfmux_put_buffer (asfmux->packet, data, length);
|
|
asfmux->packet_frames++;
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_packet_flush (GstAsfMux * asfmux)
|
|
{
|
|
GstBuffer *header, *packet = asfmux->packet;
|
|
guint header_size;
|
|
|
|
/* packet header */
|
|
header = gst_asfmux_packet_header (asfmux);
|
|
header_size = GST_BUFFER_SIZE (header);
|
|
if (!gst_asfmux_can_seek (asfmux)) {
|
|
header_size -= 12; /* hack... bah */
|
|
}
|
|
|
|
/* Clear out the padding bytes */
|
|
memset (GST_BUFFER_DATA (packet) + GST_BUFFER_SIZE (packet), 0,
|
|
GST_BUFFER_MAXSIZE (packet) - GST_BUFFER_SIZE (packet));
|
|
GST_BUFFER_SIZE (packet) = GST_ASF_PACKET_SIZE - header_size;
|
|
|
|
/* send packet over */
|
|
gst_pad_push (asfmux->srcpad, GST_DATA (header));
|
|
gst_pad_push (asfmux->srcpad, GST_DATA (packet));
|
|
gst_asfmux_put_flush (asfmux);
|
|
asfmux->num_packets++;
|
|
asfmux->packet_frames = 0;
|
|
|
|
/* reset packet */
|
|
asfmux->packet = NULL;
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_write_buffer (GstAsfMux * asfmux,
|
|
GstAsfMuxStream * stream, GstBuffer * buffer)
|
|
{
|
|
guint position = 0, to_write, size = GST_BUFFER_SIZE (buffer), remaining;
|
|
|
|
while (position < size) {
|
|
remaining = gst_asfmux_packet_remaining (asfmux);
|
|
if (remaining <= GST_ASF_FRAME_HEADER_SIZE) {
|
|
gst_asfmux_packet_flush (asfmux);
|
|
continue;
|
|
} else if (remaining >= (GST_ASF_FRAME_HEADER_SIZE + size - position)) {
|
|
to_write = size - position;
|
|
} else {
|
|
to_write = remaining - GST_ASF_FRAME_HEADER_SIZE;
|
|
}
|
|
|
|
if (asfmux->packet == NULL) {
|
|
asfmux->packet_frames = 0;
|
|
asfmux->packet = gst_buffer_new_and_alloc (GST_ASF_PACKET_SIZE);
|
|
GST_BUFFER_SIZE (asfmux->packet) = 0;
|
|
}
|
|
|
|
/* write frame header plus data in this packet */
|
|
gst_asfmux_frame_header (asfmux, stream, position, to_write, size,
|
|
GST_BUFFER_TIMESTAMP (buffer),
|
|
GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_KEY_UNIT));
|
|
gst_asfmux_frame_buffer (asfmux, GST_BUFFER_DATA (buffer) + position,
|
|
to_write);
|
|
|
|
position += to_write;
|
|
}
|
|
|
|
stream->seqnum++;
|
|
}
|
|
|
|
/* take the oldest buffer in our internal queue and push-it */
|
|
static gboolean
|
|
gst_asfmux_do_one_buffer (GstAsfMux * asfmux)
|
|
{
|
|
gint n, chosen = -1;
|
|
|
|
/* find the earliest buffer */
|
|
for (n = 0; n < asfmux->num_outputs; n++) {
|
|
if (asfmux->output[n].queue != NULL) {
|
|
if (chosen == -1 ||
|
|
GST_BUFFER_TIMESTAMP (asfmux->output[n].queue) <
|
|
GST_BUFFER_TIMESTAMP (asfmux->output[chosen].queue)) {
|
|
chosen = n;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (chosen == -1) {
|
|
/* simply finish off the file and send EOS */
|
|
gst_asfmux_file_stop (asfmux);
|
|
gst_pad_push (asfmux->srcpad, GST_DATA (gst_event_new (GST_EVENT_EOS)));
|
|
gst_element_set_eos (GST_ELEMENT (asfmux));
|
|
return FALSE;
|
|
}
|
|
|
|
/* do this buffer */
|
|
gst_asfmux_write_buffer (asfmux, &asfmux->output[chosen],
|
|
asfmux->output[chosen].queue);
|
|
|
|
/* update stream info after buffer push */
|
|
gst_buffer_unref (asfmux->output[chosen].queue);
|
|
asfmux->output[chosen].time =
|
|
GST_BUFFER_TIMESTAMP (asfmux->output[chosen].queue);
|
|
asfmux->output[chosen].queue = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_asfmux_loop (GstElement * element)
|
|
{
|
|
GstAsfMux *asfmux;
|
|
|
|
asfmux = GST_ASFMUX (element);
|
|
|
|
/* first fill queue (some elements only set caps when
|
|
* flowing data), then write header */
|
|
gst_asfmux_fill_queue (asfmux);
|
|
|
|
if (asfmux->write_header == TRUE) {
|
|
/* indeed, these are fake values. We need this so that
|
|
* players will read the file. Without these fake values,
|
|
* the players will mark the file as invalid and stop */
|
|
gst_asfmux_file_start (asfmux, 0xFFFFFFFF, 0xFFFFFFFF);
|
|
}
|
|
|
|
gst_asfmux_do_one_buffer (asfmux);
|
|
}
|
|
|
|
static GstElementStateReturn
|
|
gst_asfmux_change_state (GstElement * element)
|
|
{
|
|
GstAsfMux *asfmux;
|
|
gint transition = GST_STATE_TRANSITION (element), n;
|
|
|
|
g_return_val_if_fail (GST_IS_ASFMUX (element), GST_STATE_FAILURE);
|
|
|
|
asfmux = GST_ASFMUX (element);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_PAUSED_TO_PLAYING:
|
|
for (n = 0; n < asfmux->num_outputs; n++) {
|
|
asfmux->output[n].eos = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (GST_ELEMENT_CLASS (parent_class)->change_state)
|
|
return GST_ELEMENT_CLASS (parent_class)->change_state (element);
|
|
|
|
return GST_STATE_SUCCESS;
|
|
}
|