mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-26 18:20:44 +00:00
0f9162395a
Original commit message from CVS: local include fixes Fix some 64 bits constants to be glib friendly issue for a vararg macro with MSVC
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 + G_GINT64_CONSTANT (116444736000000000));
|
|
}
|
|
|
|
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 */
|
|
gint64 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;
|
|
}
|