mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-23 16:50:47 +00:00
c83615fca9
asfmux was messing up with the GstCollectData list of its GstCollectPads when sorting the list inplace. This patch makes it copy the list before sorting it, leaving the GstCollectPads list as is. Fixes #591610
2282 lines
73 KiB
C
2282 lines
73 KiB
C
/* ASF muxer plugin for GStreamer
|
|
* Copyright (C) 2009 Thiago Santos <thiagoss@embedded.ufcg.edu.br>
|
|
*
|
|
* 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:
|
|
* - avimux (by Ronald Bultje and Mark Nauwelaerts)
|
|
* - qtmux (by Thiago Santos and Mark Nauwelaerts)
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-asfmux
|
|
*
|
|
* Muxes media into an ASF file/stream.
|
|
*
|
|
* Pad names are either video_xx or audio_xx, where 'xx' is the
|
|
* stream number of the stream that goes through that pad. Stream numbers
|
|
* are assigned sequentially, starting from 1.
|
|
*
|
|
* <refsect2>
|
|
* <title>Example launch lines</title>
|
|
* <para>(write everything in one line, without the backslash characters)</para>
|
|
* |[
|
|
* gst-launch videotestsrc num-buffers=250 \
|
|
* ! "video/x-raw-yuv,format=(fourcc)I420,framerate=(fraction)25/1" ! ffenc_wmv2 \
|
|
* ! asfmux name=mux ! filesink location=test.asf \
|
|
* audiotestsrc num-buffers=440 ! audioconvert \
|
|
* ! "audio/x-raw-int,rate=44100" ! ffenc_wmav2 ! mux.
|
|
* ]| This creates an ASF file containing an WMV video stream
|
|
* with a test picture and WMA audio stream of a test sound.
|
|
*
|
|
* <title>Live streaming</title>
|
|
* asfmux and rtpasfpay are capable of generating a live asf stream.
|
|
* asfmux has to set its 'is-live' property to true, because in this
|
|
* mode it won't try to seek back to the start of the file to replace
|
|
* some fields that couldn't be known at the file start. In this mode,
|
|
* it won't also send indexes at the end of the data packets (the actual
|
|
* media content)
|
|
* the following pipelines are an example of this usage.
|
|
* <para>(write everything in one line, without the backslash characters)</para>
|
|
* Server (sender)
|
|
* |[
|
|
* gst-launch -ve videotestsrc ! ffenc_wmv2 ! asfmux name=mux is-live=true \
|
|
* ! rtpasfpay ! udpsink host=127.0.0.1 port=3333 \
|
|
* audiotestsrc ! ffenc_wmav2 ! mux.
|
|
* ]|
|
|
* Client (receiver)
|
|
* |[
|
|
* gst-launch udpsrc port=3333 ! "caps_from_rtpasfpay_at_sender" \
|
|
* ! rtpasfdepay ! decodebin2 name=d ! queue \
|
|
* ! ffmpegcolorspace ! autovideosink \
|
|
* d. ! queue ! audioconvert ! autoaudiosink
|
|
* ]|
|
|
* </refsect2>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include "gstasfmux.h"
|
|
|
|
#define DEFAULT_SIMPLE_INDEX_TIME_INTERVAL G_GUINT64_CONSTANT (10000000)
|
|
#define MAX_PAYLOADS_IN_A_PACKET 63
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (asfmux_debug);
|
|
#define GST_CAT_DEFAULT asfmux_debug
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_PACKET_SIZE,
|
|
PROP_PREROLL,
|
|
PROP_MERGE_STREAM_TAGS,
|
|
PROP_PADDING,
|
|
PROP_IS_LIVE
|
|
};
|
|
|
|
/* Stores a tag list for the available/known tags
|
|
* in an ASF file
|
|
* Also stores the sizes those entries would use in a
|
|
* content description object and extended content
|
|
* description object
|
|
*/
|
|
typedef struct
|
|
{
|
|
GstTagList *tags;
|
|
guint64 cont_desc_size;
|
|
guint64 ext_cont_desc_size;
|
|
} GstAsfTags;
|
|
|
|
/* Helper struct to be used as user data
|
|
* in gst_tag_foreach function for writing
|
|
* each tag for the metadata objects
|
|
*
|
|
* stream_num is used only for stream dependent tags
|
|
*/
|
|
typedef struct
|
|
{
|
|
GstAsfMux *asfmux;
|
|
guint8 *buf;
|
|
guint16 count;
|
|
guint64 size;
|
|
guint16 stream_num;
|
|
} GstAsfExtContDescData;
|
|
|
|
typedef GstAsfExtContDescData GstAsfMetadataObjData;
|
|
|
|
#define DEFAULT_PACKET_SIZE 4800
|
|
#define DEFAULT_PREROLL 5000
|
|
#define DEFAULT_MERGE_STREAM_TAGS TRUE
|
|
#define DEFAULT_PADDING 0
|
|
#define DEFAULT_IS_LIVE FALSE
|
|
|
|
static const GstElementDetails gst_asf_mux_details =
|
|
GST_ELEMENT_DETAILS ("ASF muxer",
|
|
"Codec/Muxer",
|
|
"Muxes audio and video into an ASF stream",
|
|
"Thiago Santos <thiagoss@embedded.ufcg.edu.br>");
|
|
|
|
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("video/x-ms-asf, " "parsed = (boolean) true")
|
|
);
|
|
|
|
static GstStaticPadTemplate video_sink_factory =
|
|
GST_STATIC_PAD_TEMPLATE ("video_%d",
|
|
GST_PAD_SINK,
|
|
GST_PAD_REQUEST,
|
|
GST_STATIC_CAPS ("video/x-wmv, wmvversion = (int) [1,3]"));
|
|
|
|
static GstStaticPadTemplate audio_sink_factory =
|
|
GST_STATIC_PAD_TEMPLATE ("audio_%d",
|
|
GST_PAD_SINK,
|
|
GST_PAD_REQUEST,
|
|
GST_STATIC_CAPS ("audio/x-wma, wmaversion = (int) [1,3]; "
|
|
"audio/mpeg, layer = (int) 3, mpegversion = (int) 1, "
|
|
"channels = (int) [1,2], rate = (int) [8000,96000]"));
|
|
|
|
static void gst_asf_mux_base_init (gpointer g_class);
|
|
static void gst_asf_mux_class_init (GstAsfMuxClass * klass);
|
|
static void gst_asf_mux_init (GstAsfMux * asfmux);
|
|
|
|
static GstPad *gst_asf_mux_request_new_pad (GstElement * element,
|
|
GstPadTemplate * templ, const gchar * name);
|
|
static void gst_asf_mux_set_property (GObject * object,
|
|
guint prop_id, const GValue * value, GParamSpec * pspec);
|
|
static void gst_asf_mux_get_property (GObject * object,
|
|
guint prop_id, GValue * value, GParamSpec * pspec);
|
|
static GstStateChangeReturn gst_asf_mux_change_state (GstElement * element,
|
|
GstStateChange transition);
|
|
|
|
static gboolean gst_asf_mux_sink_event (GstPad * pad, GstEvent * event);
|
|
|
|
static void gst_asf_mux_pad_reset (GstAsfPad * data);
|
|
static GstFlowReturn gst_asf_mux_collected (GstCollectPads * collect,
|
|
gpointer data);
|
|
|
|
static GstElementClass *parent_class = NULL;
|
|
|
|
GType
|
|
gst_asf_mux_get_type (void)
|
|
{
|
|
static GType asfmux_type = 0;
|
|
|
|
if (!asfmux_type) {
|
|
static const GTypeInfo asfmux_info = {
|
|
sizeof (GstAsfMuxClass),
|
|
gst_asf_mux_base_init,
|
|
NULL,
|
|
(GClassInitFunc) gst_asf_mux_class_init,
|
|
NULL,
|
|
NULL,
|
|
sizeof (GstAsfMux),
|
|
0,
|
|
(GInstanceInitFunc) gst_asf_mux_init,
|
|
};
|
|
static const GInterfaceInfo tag_setter_info = {
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
asfmux_type =
|
|
g_type_register_static (GST_TYPE_ELEMENT, "GstAsfMux", &asfmux_info, 0);
|
|
g_type_add_interface_static (asfmux_type, GST_TYPE_TAG_SETTER,
|
|
&tag_setter_info);
|
|
}
|
|
return asfmux_type;
|
|
}
|
|
|
|
static void
|
|
gst_asf_mux_reset (GstAsfMux * asfmux)
|
|
{
|
|
asfmux->state = GST_ASF_MUX_STATE_NONE;
|
|
asfmux->stream_number = 0;
|
|
asfmux->data_object_size = 0;
|
|
asfmux->data_object_position = 0;
|
|
asfmux->file_properties_object_position = 0;
|
|
asfmux->total_data_packets = 0;
|
|
asfmux->file_size = 0;
|
|
asfmux->packet_size = 0;
|
|
|
|
if (asfmux->payloads) {
|
|
GSList *walk;
|
|
for (walk = asfmux->payloads; walk; walk = g_slist_next (walk)) {
|
|
gst_asf_payload_free ((AsfPayload *) walk->data);
|
|
walk->data = NULL;
|
|
}
|
|
g_slist_free (asfmux->payloads);
|
|
}
|
|
asfmux->payloads = NULL;
|
|
asfmux->payload_data_size = 0;
|
|
|
|
asfmux->file_id.v1 = 0;
|
|
asfmux->file_id.v2 = 0;
|
|
asfmux->file_id.v3 = 0;
|
|
asfmux->file_id.v4 = 0;
|
|
|
|
gst_tag_setter_reset_tags (GST_TAG_SETTER (asfmux));
|
|
}
|
|
|
|
static void
|
|
gst_asf_mux_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 (&src_factory));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&audio_sink_factory));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&video_sink_factory));
|
|
|
|
gst_element_class_set_details (element_class, &gst_asf_mux_details);
|
|
|
|
GST_DEBUG_CATEGORY_INIT (asfmux_debug, "asfmux", 0, "Muxer for ASF streams");
|
|
}
|
|
|
|
static void
|
|
gst_asf_mux_finalize (GObject * object)
|
|
{
|
|
GstAsfMux *asfmux;
|
|
|
|
asfmux = GST_ASF_MUX (object);
|
|
|
|
gst_asf_mux_reset (asfmux);
|
|
gst_object_unref (asfmux->collect);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_asf_mux_class_init (GstAsfMuxClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *gstelement_class;
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
gstelement_class = (GstElementClass *) klass;
|
|
|
|
parent_class = g_type_class_peek_parent (klass);
|
|
|
|
gobject_class->get_property = gst_asf_mux_get_property;
|
|
gobject_class->set_property = gst_asf_mux_set_property;
|
|
gobject_class->finalize = gst_asf_mux_finalize;
|
|
|
|
g_object_class_install_property (gobject_class, PROP_PACKET_SIZE,
|
|
g_param_spec_uint ("packet-size", "Packet size",
|
|
"The ASF packets size (bytes)",
|
|
ASF_MULTIPLE_PAYLOAD_HEADER_SIZE + 1, G_MAXUINT32,
|
|
DEFAULT_PACKET_SIZE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
|
|
g_object_class_install_property (gobject_class, PROP_PREROLL,
|
|
g_param_spec_uint64 ("preroll", "Preroll",
|
|
"The preroll time (milisecs)",
|
|
0, G_MAXUINT64,
|
|
DEFAULT_PREROLL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
|
|
g_object_class_install_property (gobject_class, PROP_MERGE_STREAM_TAGS,
|
|
g_param_spec_boolean ("merge-stream-tags", "Merge Stream Tags",
|
|
"If the stream metadata (received as events in the sink) should be "
|
|
"merged to the main file metadata.",
|
|
DEFAULT_MERGE_STREAM_TAGS, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
|
|
g_object_class_install_property (gobject_class, PROP_PADDING,
|
|
g_param_spec_uint64 ("padding", "Padding",
|
|
"Size of the padding object to be added to the end of the header. "
|
|
"If this less than 24 (the smaller size of an ASF object), "
|
|
"no padding is added.",
|
|
0, G_MAXUINT64,
|
|
DEFAULT_PADDING, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
|
|
g_object_class_install_property (gobject_class, PROP_IS_LIVE,
|
|
g_param_spec_boolean ("is-live", "Is Live",
|
|
"If this stream should be threated as a live, meaning that it "
|
|
"doesn't need indexes nor late update of headers.",
|
|
DEFAULT_IS_LIVE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
|
|
|
|
gstelement_class->request_new_pad =
|
|
GST_DEBUG_FUNCPTR (gst_asf_mux_request_new_pad);
|
|
gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_asf_mux_change_state);
|
|
}
|
|
|
|
static void
|
|
gst_asf_mux_init (GstAsfMux * asfmux)
|
|
{
|
|
GstCaps *caps;
|
|
|
|
asfmux->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
|
|
caps = gst_caps_copy (gst_pad_get_pad_template_caps (asfmux->srcpad));
|
|
gst_pad_set_caps (asfmux->srcpad, caps);
|
|
gst_caps_unref (caps);
|
|
gst_pad_use_fixed_caps (asfmux->srcpad);
|
|
gst_element_add_pad (GST_ELEMENT (asfmux), asfmux->srcpad);
|
|
|
|
asfmux->collect = gst_collect_pads_new ();
|
|
gst_collect_pads_set_function (asfmux->collect,
|
|
(GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_asf_mux_collected),
|
|
asfmux);
|
|
|
|
asfmux->payloads = NULL;
|
|
asfmux->prop_packet_size = DEFAULT_PACKET_SIZE;
|
|
asfmux->prop_preroll = DEFAULT_PREROLL;
|
|
asfmux->prop_merge_stream_tags = DEFAULT_MERGE_STREAM_TAGS;
|
|
asfmux->prop_padding = DEFAULT_PADDING;
|
|
asfmux->prop_is_live = DEFAULT_IS_LIVE;
|
|
gst_asf_mux_reset (asfmux);
|
|
}
|
|
|
|
static gboolean
|
|
gst_asf_mux_sink_event (GstPad * pad, GstEvent * event)
|
|
{
|
|
gboolean ret;
|
|
GstAsfMux *asfmux;
|
|
GstAsfPad *asfpad = (GstAsfPad *) gst_pad_get_element_private (pad);
|
|
|
|
asfmux = GST_ASF_MUX_CAST (gst_pad_get_parent (pad));
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_TAG:{
|
|
GST_DEBUG_OBJECT (asfmux, "received tag event");
|
|
/* we discard tag events that come after we started
|
|
* writing the headers, because tags are to be in
|
|
* the headers
|
|
*/
|
|
if (asfmux->state == GST_ASF_MUX_STATE_NONE) {
|
|
GstTagList *list = NULL;
|
|
gst_event_parse_tag (event, &list);
|
|
if (asfmux->merge_stream_tags) {
|
|
GstTagSetter *setter = GST_TAG_SETTER (asfmux);
|
|
const GstTagMergeMode mode =
|
|
gst_tag_setter_get_tag_merge_mode (setter);
|
|
gst_tag_setter_merge_tags (setter, list, mode);
|
|
} else {
|
|
if (asfpad->taglist == NULL) {
|
|
asfpad->taglist = gst_tag_list_new ();
|
|
}
|
|
gst_tag_list_insert (asfpad->taglist, list, GST_TAG_MERGE_REPLACE);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = asfmux->collect_event (pad, event);
|
|
gst_object_unref (asfmux);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* gst_asf_mux_push_buffer:
|
|
* @asfmux: #GstAsfMux that should push the buffer
|
|
* @buf: #GstBuffer to be pushed
|
|
*
|
|
* Pushes a buffer downstream and adds its size to the total file size
|
|
*
|
|
* Returns: the result of #gst_pad_push on the buffer
|
|
*/
|
|
static GstFlowReturn
|
|
gst_asf_mux_push_buffer (GstAsfMux * asfmux, GstBuffer * buf)
|
|
{
|
|
GstFlowReturn ret;
|
|
gst_buffer_set_caps (buf, GST_PAD_CAPS (asfmux->srcpad));
|
|
ret = gst_pad_push (asfmux->srcpad, buf);
|
|
if (ret == GST_FLOW_OK)
|
|
asfmux->file_size += GST_BUFFER_SIZE (buf);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* content_description_calc_size_for_tag:
|
|
* @taglist: the #GstTagList that contains the tag
|
|
* @tag: the tag's name
|
|
* @user_data: a #GstAsfTags struct for putting the results
|
|
*
|
|
* Function that has the #GstTagForEach signature and
|
|
* is used to calculate the size in bytes for each tag
|
|
* that can be contained in asf's content description object
|
|
* and extended content description object. This size is added
|
|
* to the total size for each of that objects in the #GstAsfTags
|
|
* struct passed in the user_data pointer.
|
|
*/
|
|
static void
|
|
content_description_calc_size_for_tag (const GstTagList * taglist,
|
|
const gchar * tag, gpointer user_data)
|
|
{
|
|
const gchar *asftag = gst_asf_get_asf_tag (tag);
|
|
GValue value = { 0 };
|
|
guint type;
|
|
GstAsfTags *asftags = (GstAsfTags *) user_data;
|
|
guint content_size;
|
|
|
|
if (asftag == NULL)
|
|
return;
|
|
|
|
if (!gst_tag_list_copy_value (&value, taglist, tag)) {
|
|
return;
|
|
}
|
|
type = gst_asf_get_tag_field_type (&value);
|
|
switch (type) {
|
|
case ASF_TAG_TYPE_UNICODE_STR:
|
|
{
|
|
const gchar *text;
|
|
|
|
text = g_value_get_string (&value);
|
|
/* +1 -> because of the \0 at the end
|
|
* 2* -> because we have uft8, and asf demands utf16
|
|
*/
|
|
content_size = 2 * (1 + g_utf8_strlen (text, -1));
|
|
|
|
if (gst_asf_tag_present_in_content_description (tag)) {
|
|
asftags->cont_desc_size += content_size;
|
|
}
|
|
}
|
|
break;
|
|
case ASF_TAG_TYPE_DWORD:
|
|
content_size = 4;
|
|
break;
|
|
default:
|
|
GST_WARNING ("Unhandled asf tag field type %u for tag %s", type, tag);
|
|
g_value_reset (&value);
|
|
return;
|
|
}
|
|
if (asftag) {
|
|
/* size of the tag content in utf16 +
|
|
* size of the tag name +
|
|
* 3 uint16 (size of the tag name string,
|
|
* size of the tag content string and
|
|
* type of content
|
|
*/
|
|
asftags->ext_cont_desc_size += content_size +
|
|
(g_utf8_strlen (asftag, -1) + 1) * 2 + 6;
|
|
}
|
|
gst_tag_list_add_value (asftags->tags, GST_TAG_MERGE_REPLACE, tag, &value);
|
|
g_value_reset (&value);
|
|
}
|
|
|
|
/* FIXME
|
|
* it is awful to keep track of the size here
|
|
* and get the same tags in the writing function */
|
|
/**
|
|
* gst_asf_mux_get_content_description_tags:
|
|
* @asfmux: #GstAsfMux to have its tags proccessed
|
|
* @asftags: #GstAsfTags to hold the results
|
|
*
|
|
* Inspects the tags received by the GstTagSetter interface
|
|
* or possibly by sink tag events and calculates the total
|
|
* size needed for the default and extended content description objects.
|
|
* This results and a copy of the #GstTagList
|
|
* are stored in the #GstAsfTags. We store a copy so that
|
|
* the sizes estimated here mantain the same until they are
|
|
* written to the asf file.
|
|
*/
|
|
static void
|
|
gst_asf_mux_get_content_description_tags (GstAsfMux * asfmux,
|
|
GstAsfTags * asftags)
|
|
{
|
|
const GstTagList *tags;
|
|
GstTagList *taglist = NULL;
|
|
|
|
tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (asfmux));
|
|
if (tags && !gst_tag_list_is_empty (tags)) {
|
|
if (asftags->tags != NULL) {
|
|
gst_tag_list_free (asftags->tags);
|
|
}
|
|
asftags->tags = gst_tag_list_new ();
|
|
asftags->cont_desc_size = 0;
|
|
asftags->ext_cont_desc_size = 0;
|
|
taglist = asftags->tags;
|
|
|
|
GST_DEBUG_OBJECT (asfmux, "Processing tags");
|
|
gst_tag_list_foreach (tags, content_description_calc_size_for_tag, asftags);
|
|
} else {
|
|
GST_DEBUG_OBJECT (asfmux, "No tags received");
|
|
}
|
|
|
|
if (asftags->cont_desc_size > 0) {
|
|
asftags->cont_desc_size += ASF_CONTENT_DESCRIPTION_OBJECT_SIZE;
|
|
}
|
|
if (asftags->ext_cont_desc_size > 0) {
|
|
asftags->ext_cont_desc_size += ASF_EXT_CONTENT_DESCRIPTION_OBJECT_SIZE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* add_metadata_tag_size:
|
|
* @taglist: #GstTagList
|
|
* @tag: tag name
|
|
* @user_data: pointer to a guint to store the result
|
|
*
|
|
* GstTagForeachFunc implementation that accounts the size of
|
|
* each tag in the taglist and adds them to the guint pointed
|
|
* by the user_data
|
|
*/
|
|
static void
|
|
add_metadata_tag_size (const GstTagList * taglist, const gchar * tag,
|
|
gpointer user_data)
|
|
{
|
|
const gchar *asftag = gst_asf_get_asf_tag (tag);
|
|
GValue value = { 0 };
|
|
guint type;
|
|
guint content_size;
|
|
guint *total_size = (guint *) user_data;
|
|
|
|
if (asftag == NULL)
|
|
return;
|
|
|
|
if (!gst_tag_list_copy_value (&value, taglist, tag)) {
|
|
return;
|
|
}
|
|
type = gst_asf_get_tag_field_type (&value);
|
|
switch (type) {
|
|
case ASF_TAG_TYPE_UNICODE_STR:
|
|
{
|
|
const gchar *text;
|
|
|
|
text = g_value_get_string (&value);
|
|
/* +1 -> because of the \0 at the end
|
|
* 2* -> because we have uft8, and asf demands utf16
|
|
*/
|
|
content_size = 2 * (1 + g_utf8_strlen (text, -1));
|
|
}
|
|
break;
|
|
case ASF_TAG_TYPE_DWORD:
|
|
content_size = 4;
|
|
break;
|
|
default:
|
|
GST_WARNING ("Unhandled asf tag field type %u for tag %s", type, tag);
|
|
g_value_reset (&value);
|
|
return;
|
|
}
|
|
/* size of reserved (2) +
|
|
* size of stream number (2) +
|
|
* size of the tag content in utf16 +
|
|
* size of the tag name +
|
|
* 2 uint16 (size of the tag name string and type of content) +
|
|
* 1 uint32 (size of the data)
|
|
*/
|
|
*total_size +=
|
|
4 + content_size + (g_utf8_strlen (asftag, -1) + 1) * 2 + 4 + 4;
|
|
g_value_reset (&value);
|
|
}
|
|
|
|
/**
|
|
* gst_asf_mux_get_metadata_object_size:
|
|
* @asfmux: #GstAsfMux
|
|
* @asfpad: pad for which the metadata object size should be calculated
|
|
*
|
|
* Calculates the size of the metadata object for the tags of the stream
|
|
* handled by the asfpad in the parameter
|
|
*
|
|
* Returns: The size calculated
|
|
*/
|
|
static guint
|
|
gst_asf_mux_get_metadata_object_size (GstAsfMux * asfmux, GstAsfPad * asfpad)
|
|
{
|
|
guint size = ASF_METADATA_OBJECT_SIZE;
|
|
if (asfpad->taglist == NULL || gst_tag_list_is_empty (asfpad->taglist))
|
|
return 0;
|
|
|
|
gst_tag_list_foreach (asfpad->taglist, add_metadata_tag_size, &size);
|
|
return size;
|
|
}
|
|
|
|
/**
|
|
* gst_asf_mux_get_headers_size:
|
|
* @asfmux: #GstAsfMux
|
|
*
|
|
* Calculates the size of the headers of the asf stream
|
|
* to be generated by this #GstAsfMux.
|
|
* Its used for determining the size of the buffer to allocate
|
|
* to exactly fit the headers in.
|
|
* Padding and metadata objects sizes are not included.
|
|
*
|
|
* Returns: the calculated size
|
|
*/
|
|
static guint
|
|
gst_asf_mux_get_headers_size (GstAsfMux * asfmux)
|
|
{
|
|
GSList *walk;
|
|
gint stream_num = 0;
|
|
guint size = ASF_HEADER_OBJECT_SIZE +
|
|
ASF_FILE_PROPERTIES_OBJECT_SIZE + ASF_HEADER_EXTENSION_OBJECT_SIZE;
|
|
|
|
/* per stream data */
|
|
for (walk = asfmux->collect->data; walk; walk = g_slist_next (walk)) {
|
|
GstAsfPad *asfpad = (GstAsfPad *) walk->data;
|
|
|
|
if (asfpad->is_audio)
|
|
size += ASF_AUDIO_SPECIFIC_DATA_SIZE;
|
|
else
|
|
size += ASF_VIDEO_SPECIFIC_DATA_SIZE;
|
|
|
|
if (asfpad->codec_data)
|
|
size += GST_BUFFER_SIZE (asfpad->codec_data);
|
|
|
|
stream_num++;
|
|
}
|
|
size += stream_num * (ASF_STREAM_PROPERTIES_OBJECT_SIZE +
|
|
ASF_EXTENDED_STREAM_PROPERTIES_OBJECT_SIZE);
|
|
|
|
return size;
|
|
}
|
|
|
|
/**
|
|
* gst_asf_mux_write_header_object:
|
|
* @asfmux:
|
|
* @buf: pointer to the data pointer
|
|
* @size: size of the header object
|
|
* @child_objects: number of children objects inside the main header object
|
|
*
|
|
* Writes the main asf header object start. The buffer pointer
|
|
* is incremented to the next writing position.
|
|
*/
|
|
static void
|
|
gst_asf_mux_write_header_object (GstAsfMux * asfmux, guint8 ** buf,
|
|
guint64 size, guint32 child_objects)
|
|
{
|
|
gst_asf_put_guid (*buf, guids[ASF_HEADER_OBJECT_INDEX]);
|
|
GST_WRITE_UINT64_LE (*buf + 16, size); /* object size */
|
|
GST_WRITE_UINT32_LE (*buf + 24, child_objects); /* # of child objects */
|
|
GST_WRITE_UINT8 (*buf + 28, 0x01); /* reserved */
|
|
GST_WRITE_UINT8 (*buf + 29, 0x02); /* reserved */
|
|
*buf += ASF_HEADER_OBJECT_SIZE;
|
|
}
|
|
|
|
/**
|
|
* gst_asf_mux_write_file_properties:
|
|
* @asfmux:
|
|
* @buf: pointer to the data pointer
|
|
*
|
|
* Writes the file properties object to the buffer. The buffer pointer
|
|
* is incremented to the next writing position.
|
|
*/
|
|
static void
|
|
gst_asf_mux_write_file_properties (GstAsfMux * asfmux, guint8 ** buf)
|
|
{
|
|
gst_asf_put_guid (*buf, guids[ASF_FILE_PROPERTIES_OBJECT_INDEX]);
|
|
GST_WRITE_UINT64_LE (*buf + 16, ASF_FILE_PROPERTIES_OBJECT_SIZE); /* object size */
|
|
gst_asf_put_guid (*buf + 24, asfmux->file_id);
|
|
GST_WRITE_UINT64_LE (*buf + 40, 0); /* file size - needs update */
|
|
gst_asf_put_time (*buf + 48, gst_asf_get_current_time ()); /* creation time */
|
|
GST_WRITE_UINT64_LE (*buf + 56, 0); /* data packets - needs update */
|
|
GST_WRITE_UINT64_LE (*buf + 64, 0); /* play duration - needs update */
|
|
GST_WRITE_UINT64_LE (*buf + 72, 0); /* send duration - needs update */
|
|
GST_WRITE_UINT64_LE (*buf + 80, 0); /* preroll */
|
|
GST_WRITE_UINT32_LE (*buf + 88, 0x1); /* flags - broadcast on */
|
|
GST_WRITE_UINT32_LE (*buf + 92, asfmux->packet_size); /* minimum data packet size */
|
|
GST_WRITE_UINT32_LE (*buf + 96, asfmux->packet_size); /* maximum data packet size */
|
|
GST_WRITE_UINT32_LE (*buf + 100, 0); /* maximum bitrate TODO */
|
|
|
|
*buf += ASF_FILE_PROPERTIES_OBJECT_SIZE;
|
|
}
|
|
|
|
/**
|
|
* gst_asf_mux_write_stream_properties:
|
|
* @param asfmux:
|
|
* @param buf: pointer to the data pointer
|
|
* @param asfpad: Pad that handles the stream
|
|
*
|
|
* Writes the stream properties object in the buffer
|
|
* for the stream handled by the #GstAsfPad passed.
|
|
* The pointer is incremented to the next writing position
|
|
*/
|
|
static void
|
|
gst_asf_mux_write_stream_properties (GstAsfMux * asfmux, guint8 ** buf,
|
|
GstAsfPad * asfpad)
|
|
{
|
|
guint32 codec_data_length = 0;
|
|
guint32 media_specific_data_length = 0;
|
|
guint16 flags = 0;
|
|
|
|
/* codec specific data length */
|
|
if (asfpad->codec_data)
|
|
codec_data_length = GST_BUFFER_SIZE (asfpad->codec_data);
|
|
if (asfpad->is_audio)
|
|
media_specific_data_length = ASF_AUDIO_SPECIFIC_DATA_SIZE;
|
|
else
|
|
media_specific_data_length = ASF_VIDEO_SPECIFIC_DATA_SIZE;
|
|
|
|
GST_DEBUG_OBJECT (asfmux, "Stream %" G_GUINT16_FORMAT " codec data length: %"
|
|
G_GUINT32_FORMAT ", media specific data length: %" G_GUINT32_FORMAT,
|
|
(guint16) asfpad->stream_number, codec_data_length,
|
|
media_specific_data_length);
|
|
|
|
gst_asf_put_guid (*buf, guids[ASF_STREAM_PROPERTIES_OBJECT_INDEX]);
|
|
GST_WRITE_UINT64_LE (*buf + 16, ASF_STREAM_PROPERTIES_OBJECT_SIZE + codec_data_length + media_specific_data_length); /* object size */
|
|
|
|
/* stream type */
|
|
if (asfpad->is_audio)
|
|
gst_asf_put_guid (*buf + 24, guids[ASF_AUDIO_MEDIA_INDEX]);
|
|
else
|
|
gst_asf_put_guid (*buf + 24, guids[ASF_VIDEO_MEDIA_INDEX]);
|
|
/* error correction */
|
|
if (asfpad->is_audio) {
|
|
gst_asf_put_guid (*buf + 40, guids[ASF_NO_ERROR_CORRECTION_INDEX]);
|
|
} else {
|
|
gst_asf_put_guid (*buf + 40, guids[ASF_NO_ERROR_CORRECTION_INDEX]);
|
|
}
|
|
GST_WRITE_UINT64_LE (*buf + 56, 0); /* time offset */
|
|
|
|
GST_WRITE_UINT32_LE (*buf + 64, codec_data_length + media_specific_data_length); /* type specific data length */
|
|
GST_WRITE_UINT32_LE (*buf + 68, 0); /* error correction data length */
|
|
|
|
flags = (asfpad->stream_number & 0x7F);
|
|
GST_WRITE_UINT16_LE (*buf + 72, flags);
|
|
GST_WRITE_UINT32_LE (*buf + 74, 0); /* reserved */
|
|
|
|
*buf += ASF_STREAM_PROPERTIES_OBJECT_SIZE;
|
|
/* audio specific data */
|
|
if (asfpad->is_audio) {
|
|
GstAsfAudioPad *audiopad = (GstAsfAudioPad *) asfpad;
|
|
GST_WRITE_UINT16_LE (*buf, audiopad->audioinfo.format);
|
|
GST_WRITE_UINT16_LE (*buf + 2, audiopad->audioinfo.channels);
|
|
GST_WRITE_UINT32_LE (*buf + 4, audiopad->audioinfo.rate);
|
|
GST_WRITE_UINT32_LE (*buf + 8, audiopad->audioinfo.av_bps);
|
|
GST_WRITE_UINT16_LE (*buf + 12, audiopad->audioinfo.blockalign);
|
|
GST_WRITE_UINT16_LE (*buf + 14, audiopad->audioinfo.size);
|
|
GST_WRITE_UINT16_LE (*buf + 16, codec_data_length);
|
|
|
|
GST_DEBUG_OBJECT (asfmux,
|
|
"wave formatex values: codec_id=%" G_GUINT16_FORMAT ", channels=%"
|
|
G_GUINT16_FORMAT ", rate=%" G_GUINT32_FORMAT ", bytes_per_sec=%"
|
|
G_GUINT32_FORMAT ", block_alignment=%" G_GUINT16_FORMAT
|
|
", bits_per_sample=%" G_GUINT16_FORMAT ", codec_data_length=%"
|
|
G_GUINT16_FORMAT, audiopad->audioinfo.format,
|
|
audiopad->audioinfo.channels, audiopad->audioinfo.rate,
|
|
audiopad->audioinfo.av_bps, audiopad->audioinfo.blockalign,
|
|
audiopad->audioinfo.size, codec_data_length);
|
|
|
|
|
|
*buf += ASF_AUDIO_SPECIFIC_DATA_SIZE;
|
|
} else {
|
|
GstAsfVideoPad *videopad = (GstAsfVideoPad *) asfpad;
|
|
GST_WRITE_UINT32_LE (*buf, (guint32) videopad->vidinfo.width);
|
|
GST_WRITE_UINT32_LE (*buf + 4, (guint32) videopad->vidinfo.height);
|
|
GST_WRITE_UINT8 (*buf + 8, 2);
|
|
|
|
/* the BITMAPINFOHEADER size + codec_data size */
|
|
GST_WRITE_UINT16_LE (*buf + 9,
|
|
ASF_VIDEO_SPECIFIC_DATA_SIZE + codec_data_length - 11);
|
|
|
|
/* BITMAPINFOHEADER */
|
|
GST_WRITE_UINT32_LE (*buf + 11,
|
|
ASF_VIDEO_SPECIFIC_DATA_SIZE + codec_data_length - 11);
|
|
gst_asf_put_i32 (*buf + 15, videopad->vidinfo.width);
|
|
gst_asf_put_i32 (*buf + 19, videopad->vidinfo.height);
|
|
GST_WRITE_UINT16_LE (*buf + 23, 1); /* reserved */
|
|
GST_WRITE_UINT16_LE (*buf + 25, videopad->vidinfo.bit_cnt);
|
|
GST_WRITE_UINT32_LE (*buf + 27, videopad->vidinfo.compression);
|
|
GST_WRITE_UINT32_LE (*buf + 31, videopad->vidinfo.width *
|
|
videopad->vidinfo.height * videopad->vidinfo.bit_cnt);
|
|
GST_WRITE_UINT32_LE (*buf + 35, videopad->vidinfo.xpels_meter);
|
|
GST_WRITE_UINT32_LE (*buf + 39, videopad->vidinfo.ypels_meter);
|
|
GST_WRITE_UINT32_LE (*buf + 43, videopad->vidinfo.num_colors);
|
|
GST_WRITE_UINT32_LE (*buf + 47, videopad->vidinfo.imp_colors);
|
|
|
|
*buf += ASF_VIDEO_SPECIFIC_DATA_SIZE;
|
|
}
|
|
|
|
if (codec_data_length > 0)
|
|
memcpy (*buf, GST_BUFFER_DATA (asfpad->codec_data), codec_data_length);
|
|
|
|
*buf += codec_data_length;
|
|
}
|
|
|
|
/**
|
|
* gst_asf_mux_write_header_extension:
|
|
* @asfmux:
|
|
* @buf: pointer to the buffer pointer
|
|
* @extension_size: size of the extensions
|
|
*
|
|
* Writes the header of the header extension object. The buffer pointer
|
|
* is incremented to the next writing position (the header extension object
|
|
* childs should be writen from that point)
|
|
*/
|
|
static void
|
|
gst_asf_mux_write_header_extension (GstAsfMux * asfmux, guint8 ** buf,
|
|
guint64 extension_size)
|
|
{
|
|
gst_asf_put_guid (*buf, guids[ASF_HEADER_EXTENSION_OBJECT_INDEX]);
|
|
GST_WRITE_UINT64_LE (*buf + 16, ASF_HEADER_EXTENSION_OBJECT_SIZE + extension_size); /* object size */
|
|
gst_asf_put_guid (*buf + 24, guids[ASF_RESERVED_1_INDEX]); /* reserved */
|
|
GST_WRITE_UINT16_LE (*buf + 40, 6); /* reserved */
|
|
GST_WRITE_UINT32_LE (*buf + 42, extension_size); /* header extension data size */
|
|
*buf += ASF_HEADER_EXTENSION_OBJECT_SIZE;
|
|
}
|
|
|
|
/**
|
|
* gst_asf_mux_write_extended_stream_properties:
|
|
* @asfmux:
|
|
* @buf: pointer to the buffer pointer
|
|
* @asfpad: Pad that handles the stream of the properties to be writen
|
|
*
|
|
* Writes the extended stream properties object (that is part of the
|
|
* header extension objects) for the stream handled by asfpad
|
|
*/
|
|
static void
|
|
gst_asf_mux_write_extended_stream_properties (GstAsfMux * asfmux, guint8 ** buf,
|
|
GstAsfPad * asfpad)
|
|
{
|
|
gst_asf_put_guid (*buf, guids[ASF_EXTENDED_STREAM_PROPERTIES_OBJECT_INDEX]);
|
|
GST_WRITE_UINT64_LE (*buf + 16, ASF_EXTENDED_STREAM_PROPERTIES_OBJECT_SIZE);
|
|
GST_WRITE_UINT64_LE (*buf + 24, 0); /* start time */
|
|
GST_WRITE_UINT64_LE (*buf + 32, 0); /* end time */
|
|
GST_WRITE_UINT32_LE (*buf + 40, asfpad->bitrate); /* bitrate */
|
|
GST_WRITE_UINT32_LE (*buf + 44, 0); /* buffer size */
|
|
GST_WRITE_UINT32_LE (*buf + 48, 0); /* initial buffer fullness */
|
|
GST_WRITE_UINT32_LE (*buf + 52, asfpad->bitrate); /* alternate data bitrate */
|
|
GST_WRITE_UINT32_LE (*buf + 56, 0); /* alternate buffer size */
|
|
GST_WRITE_UINT32_LE (*buf + 60, 0); /* alternate initial buffer fullness */
|
|
GST_WRITE_UINT32_LE (*buf + 64, 0); /* maximum object size */
|
|
|
|
/* flags */
|
|
if (asfpad->is_audio) {
|
|
/* TODO check if audio is seekable */
|
|
GST_WRITE_UINT32_LE (*buf + 68, 0x0);
|
|
} else {
|
|
/* video has indexes, so it is seekable */
|
|
GST_WRITE_UINT32_LE (*buf + 68, 0x2);
|
|
}
|
|
|
|
GST_WRITE_UINT16_LE (*buf + 72, asfpad->stream_number);
|
|
GST_WRITE_UINT16_LE (*buf + 74, 0); /* language index */
|
|
GST_WRITE_UINT64_LE (*buf + 76, 0); /* avg time per frame */
|
|
GST_WRITE_UINT16_LE (*buf + 84, 0); /* stream name count */
|
|
GST_WRITE_UINT16_LE (*buf + 86, 0); /* payload extension count */
|
|
|
|
*buf += ASF_EXTENDED_STREAM_PROPERTIES_OBJECT_SIZE;
|
|
}
|
|
|
|
/**
|
|
* gst_asf_mux_write_string_with_size:
|
|
* @asfmux:
|
|
* @size_buf: pointer to the memory position to write the size of the string
|
|
* @str_buf: pointer to the memory position to write the string
|
|
* @str: the string to be writen (in UTF-8)
|
|
* @use32: if the string size should be writen with 32 bits (if true)
|
|
* or with 16 (if false)
|
|
*
|
|
* Writes a string with its size as it is needed in many asf objects.
|
|
* The size is writen to size_buf as a WORD field if use32 is false, and
|
|
* as a DWORD if use32 is true. The string is writen to str_buf in UTF16-LE.
|
|
* The string should be passed in UTF-8.
|
|
*
|
|
* The string size in UTF16-LE is returned.
|
|
*/
|
|
static guint64
|
|
gst_asf_mux_write_string_with_size (GstAsfMux * asfmux,
|
|
guint8 * size_buf, guint8 * str_buf, const gchar * str, gboolean use32)
|
|
{
|
|
GError *error = NULL;
|
|
gsize str_size = 0;
|
|
gchar *str_utf16 = NULL;
|
|
|
|
GST_LOG_OBJECT (asfmux, "Writing extended content description string: "
|
|
"%s", str);
|
|
|
|
/*
|
|
* Covert the string to utf16
|
|
* Also force the last bytes to null terminated,
|
|
* tags were with extra weird characters without it.
|
|
*/
|
|
str_utf16 = g_convert (str, -1, "UTF-16LE", "UTF-8", NULL, &str_size, &error);
|
|
str_utf16[str_size + 1] = '\0';
|
|
str_utf16[str_size + 2] = '\0';
|
|
|
|
/* sum up the null terminating char */
|
|
str_size += 2;
|
|
|
|
if (use32)
|
|
GST_WRITE_UINT32_LE (size_buf, str_size);
|
|
else
|
|
GST_WRITE_UINT16_LE (size_buf, str_size);
|
|
if (error) {
|
|
GST_WARNING_OBJECT (asfmux, "Error converting string "
|
|
"to UTF-16: %s - %s", str, error->message);
|
|
g_free (error);
|
|
memset (str_buf, 0, str_size);
|
|
} else {
|
|
memcpy (str_buf, str_utf16, str_size);
|
|
}
|
|
g_free (str_utf16);
|
|
return str_size;
|
|
}
|
|
|
|
/**
|
|
* gst_asf_mux_write_content_description_entry:
|
|
* @asfmux:
|
|
* @tags:
|
|
* @tagname:
|
|
* @size_buf:
|
|
* @data_buf:
|
|
*
|
|
* Checks if a string tag with tagname exists in the taglist. If it
|
|
* exists it is writen as an UTF-16LE to data_buf and its size in bytes
|
|
* is writen to size_buf. It is used for writing content description
|
|
* object fields.
|
|
*
|
|
* Returns: the size of the string
|
|
*/
|
|
static guint16
|
|
gst_asf_mux_write_content_description_entry (GstAsfMux * asfmux,
|
|
const GstTagList * tags, const gchar * tagname,
|
|
guint8 * size_buf, guint8 * data_buf)
|
|
{
|
|
gchar *text = NULL;
|
|
guint16 text_size = 0;
|
|
if (gst_tag_list_get_string (tags, tagname, &text)) {
|
|
text_size = gst_asf_mux_write_string_with_size (asfmux, size_buf,
|
|
data_buf, text, FALSE);
|
|
g_free (text);
|
|
} else {
|
|
GST_WRITE_UINT16_LE (size_buf, 0);
|
|
}
|
|
return text_size;
|
|
}
|
|
|
|
static guint64
|
|
gst_asf_mux_write_ext_content_description_dword_entry (GstAsfMux * asfmux,
|
|
guint8 * buf, const gchar * asf_tag, const guint32 value)
|
|
{
|
|
guint64 tag_size;
|
|
GST_DEBUG_OBJECT (asfmux, "Writing extended content description tag: "
|
|
"%s (%u)", asf_tag, value);
|
|
|
|
tag_size = gst_asf_mux_write_string_with_size (asfmux, buf, buf + 2,
|
|
asf_tag, FALSE);
|
|
buf += tag_size + 2;
|
|
GST_WRITE_UINT16_LE (buf, ASF_TAG_TYPE_DWORD);
|
|
GST_WRITE_UINT16_LE (buf + 2, 4);
|
|
GST_WRITE_UINT32_LE (buf + 4, value);
|
|
|
|
/* tagsize -> string size
|
|
* 2 -> string size field size
|
|
* 4 -> dword entry
|
|
* 4 -> type of entry + entry size
|
|
*/
|
|
return tag_size + 2 + 4 + 4;
|
|
}
|
|
|
|
static guint64
|
|
gst_asf_mux_write_ext_content_description_string_entry (GstAsfMux * asfmux,
|
|
guint8 * buf, const gchar * asf_tag, const gchar * text)
|
|
{
|
|
guint64 tag_size = 0;
|
|
guint64 text_size = 0;
|
|
|
|
GST_DEBUG_OBJECT (asfmux, "Writing extended content description tag: "
|
|
"%s (%s)", asf_tag, text);
|
|
|
|
tag_size = gst_asf_mux_write_string_with_size (asfmux,
|
|
buf, buf + 2, asf_tag, FALSE);
|
|
GST_WRITE_UINT16_LE (buf + tag_size + 2, ASF_TAG_TYPE_UNICODE_STR);
|
|
buf += tag_size + 2 + 2;
|
|
text_size = gst_asf_mux_write_string_with_size (asfmux,
|
|
buf, buf + 2, text, FALSE);
|
|
|
|
/* the size of the strings in utf16-le plus the 3 WORD fields */
|
|
return tag_size + text_size + 6;
|
|
}
|
|
|
|
static void
|
|
gst_asf_mux_write_content_description (GstAsfMux * asfmux, guint8 ** buf,
|
|
const GstTagList * tags)
|
|
{
|
|
guint8 *values = (*buf) + ASF_CONTENT_DESCRIPTION_OBJECT_SIZE;
|
|
guint64 size = 0;
|
|
|
|
GST_DEBUG_OBJECT (asfmux, "Writing content description object");
|
|
|
|
gst_asf_put_guid (*buf, guids[ASF_CONTENT_DESCRIPTION_INDEX]);
|
|
|
|
values += gst_asf_mux_write_content_description_entry (asfmux, tags,
|
|
GST_TAG_TITLE, *buf + 24, values);
|
|
values += gst_asf_mux_write_content_description_entry (asfmux, tags,
|
|
GST_TAG_ARTIST, *buf + 26, values);
|
|
values += gst_asf_mux_write_content_description_entry (asfmux, tags,
|
|
GST_TAG_COPYRIGHT, *buf + 28, values);
|
|
values += gst_asf_mux_write_content_description_entry (asfmux, tags,
|
|
GST_TAG_DESCRIPTION, *buf + 30, values);
|
|
|
|
/* rating is currently not present in gstreamer tags, so we put 0 */
|
|
GST_WRITE_UINT16_LE (*buf + 32, 0);
|
|
|
|
size += values - *buf;
|
|
GST_WRITE_UINT64_LE (*buf + 16, size);
|
|
*buf += size;
|
|
}
|
|
|
|
static void
|
|
write_ext_content_description_tag (const GstTagList * taglist,
|
|
const gchar * tag, gpointer user_data)
|
|
{
|
|
const gchar *asftag = gst_asf_get_asf_tag (tag);
|
|
GValue value = { 0 };
|
|
guint type;
|
|
GstAsfExtContDescData *data = (GstAsfExtContDescData *) user_data;
|
|
|
|
if (asftag == NULL)
|
|
return;
|
|
|
|
if (!gst_tag_list_copy_value (&value, taglist, tag)) {
|
|
return;
|
|
}
|
|
|
|
type = gst_asf_get_tag_field_type (&value);
|
|
switch (type) {
|
|
case ASF_TAG_TYPE_UNICODE_STR:
|
|
{
|
|
const gchar *text;
|
|
text = g_value_get_string (&value);
|
|
data->size +=
|
|
gst_asf_mux_write_ext_content_description_string_entry (data->asfmux,
|
|
data->buf + data->size, asftag, text);
|
|
}
|
|
break;
|
|
case ASF_TAG_TYPE_DWORD:
|
|
{
|
|
guint num = g_value_get_uint (&value);
|
|
data->size +=
|
|
gst_asf_mux_write_ext_content_description_dword_entry (data->asfmux,
|
|
data->buf + data->size, asftag, num);
|
|
}
|
|
break;
|
|
default:
|
|
GST_WARNING_OBJECT (data->asfmux,
|
|
"Unhandled asf tag field type %u for tag %s", type, tag);
|
|
g_value_reset (&value);
|
|
return;
|
|
}
|
|
data->count++;
|
|
g_value_reset (&value);
|
|
}
|
|
|
|
static void
|
|
gst_asf_mux_write_ext_content_description (GstAsfMux * asfmux, guint8 ** buf,
|
|
GstTagList * tags)
|
|
{
|
|
GstAsfExtContDescData extContDesc;
|
|
extContDesc.asfmux = asfmux;
|
|
extContDesc.buf = *buf;
|
|
extContDesc.count = 0;
|
|
extContDesc.size = ASF_EXT_CONTENT_DESCRIPTION_OBJECT_SIZE;
|
|
|
|
GST_DEBUG_OBJECT (asfmux, "Writing extended content description object");
|
|
gst_asf_put_guid (*buf, guids[ASF_EXT_CONTENT_DESCRIPTION_INDEX]);
|
|
|
|
gst_tag_list_foreach (tags, write_ext_content_description_tag, &extContDesc);
|
|
|
|
GST_WRITE_UINT64_LE (*buf + 16, extContDesc.size);
|
|
GST_WRITE_UINT16_LE (*buf + 24, extContDesc.count);
|
|
|
|
*buf += extContDesc.size;
|
|
}
|
|
|
|
static void
|
|
write_metadata_tag (const GstTagList * taglist, const gchar * tag,
|
|
gpointer user_data)
|
|
{
|
|
const gchar *asftag = gst_asf_get_asf_tag (tag);
|
|
GValue value = { 0 };
|
|
guint type;
|
|
GstAsfMetadataObjData *data = (GstAsfMetadataObjData *) user_data;
|
|
guint16 tag_size;
|
|
guint32 content_size;
|
|
|
|
if (asftag == NULL)
|
|
return;
|
|
|
|
if (!gst_tag_list_copy_value (&value, taglist, tag)) {
|
|
return;
|
|
}
|
|
|
|
type = gst_asf_get_tag_field_type (&value);
|
|
switch (type) {
|
|
case ASF_TAG_TYPE_UNICODE_STR:
|
|
{
|
|
const gchar *text;
|
|
text = g_value_get_string (&value);
|
|
GST_WRITE_UINT16_LE (data->buf + data->size, 0);
|
|
GST_WRITE_UINT16_LE (data->buf + data->size + 2, data->stream_num);
|
|
data->size += 4;
|
|
|
|
tag_size = gst_asf_mux_write_string_with_size (data->asfmux,
|
|
data->buf + data->size, data->buf + data->size + 8, asftag, FALSE);
|
|
data->size += 2;
|
|
|
|
GST_WRITE_UINT16_LE (data->buf + data->size, type);
|
|
data->size += 2;
|
|
|
|
content_size = gst_asf_mux_write_string_with_size (data->asfmux,
|
|
data->buf + data->size, data->buf + data->size + tag_size + 4, text,
|
|
TRUE);
|
|
data->size += tag_size + content_size + 4;
|
|
}
|
|
break;
|
|
case ASF_TAG_TYPE_DWORD:
|
|
{
|
|
guint num = g_value_get_uint (&value);
|
|
GST_WRITE_UINT16_LE (data->buf + data->size, 0);
|
|
GST_WRITE_UINT16_LE (data->buf + data->size + 2, data->stream_num);
|
|
data->size += 4;
|
|
|
|
tag_size = gst_asf_mux_write_string_with_size (data->asfmux,
|
|
data->buf + data->size, data->buf + data->size + 8, asftag, FALSE);
|
|
data->size += 2;
|
|
|
|
GST_WRITE_UINT16_LE (data->buf + data->size, type);
|
|
data->size += 2;
|
|
/* dword length */
|
|
GST_WRITE_UINT32_LE (data->buf + data->size, 4);
|
|
data->size += 4 + tag_size;
|
|
|
|
GST_WRITE_UINT32_LE (data->buf + data->size, num);
|
|
data->size += 4;
|
|
}
|
|
break;
|
|
default:
|
|
GST_WARNING_OBJECT (data->asfmux,
|
|
"Unhandled asf tag field type %u for tag %s", type, tag);
|
|
g_value_reset (&value);
|
|
return;
|
|
}
|
|
|
|
data->count++;
|
|
g_value_reset (&value);
|
|
}
|
|
|
|
static void
|
|
gst_asf_mux_write_metadata_object (GstAsfMux * asfmux, guint8 ** buf,
|
|
GstAsfPad * asfpad)
|
|
{
|
|
GstAsfMetadataObjData metaObjData;
|
|
metaObjData.asfmux = asfmux;
|
|
metaObjData.buf = *buf;
|
|
metaObjData.count = 0;
|
|
metaObjData.size = ASF_METADATA_OBJECT_SIZE;
|
|
metaObjData.stream_num = asfpad->stream_number;
|
|
|
|
if (asfpad->taglist == NULL || gst_tag_list_is_empty (asfpad->taglist))
|
|
return;
|
|
|
|
GST_DEBUG_OBJECT (asfmux, "Writing metadata object");
|
|
gst_asf_put_guid (*buf, guids[ASF_METADATA_OBJECT_INDEX]);
|
|
|
|
gst_tag_list_foreach (asfpad->taglist, write_metadata_tag, &metaObjData);
|
|
|
|
GST_WRITE_UINT64_LE (*buf + 16, metaObjData.size);
|
|
GST_WRITE_UINT16_LE (*buf + 24, metaObjData.count);
|
|
|
|
*buf += metaObjData.size;
|
|
}
|
|
|
|
static void
|
|
gst_asf_mux_write_padding_object (GstAsfMux * asfmux, guint8 ** buf,
|
|
guint64 padding)
|
|
{
|
|
if (padding < ASF_PADDING_OBJECT_SIZE) {
|
|
return;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (asfmux, "Writing padding object of size %" G_GUINT64_FORMAT,
|
|
padding);
|
|
gst_asf_put_guid (*buf, guids[ASF_PADDING_OBJECT_INDEX]);
|
|
GST_WRITE_UINT64_LE (*buf + 16, padding);
|
|
memset (*buf + 24, 0, padding - ASF_PADDING_OBJECT_SIZE);
|
|
*buf += padding;
|
|
}
|
|
|
|
static void
|
|
gst_asf_mux_write_data_object (GstAsfMux * asfmux, guint8 ** buf)
|
|
{
|
|
gst_asf_put_guid (*buf, guids[ASF_DATA_OBJECT_INDEX]);
|
|
GST_WRITE_UINT64_LE (*buf + 16, 0); /* object size - needs updating */
|
|
gst_asf_put_guid (*buf + 24, asfmux->file_id);
|
|
GST_WRITE_UINT64_LE (*buf + 40, 0); /* total data packets */
|
|
GST_WRITE_UINT16_LE (*buf + 48, 0x0101); /* reserved */
|
|
*buf += ASF_DATA_OBJECT_SIZE;
|
|
}
|
|
|
|
/**
|
|
* gst_asf_mux_start_file:
|
|
* @asfmux: #GstAsfMux
|
|
*
|
|
* Starts the asf file/stream by creating and pushing
|
|
* the headers downstream.
|
|
*/
|
|
static GstFlowReturn
|
|
gst_asf_mux_start_file (GstAsfMux * asfmux)
|
|
{
|
|
GstBuffer *buf = NULL;
|
|
guint8 *bufdata = NULL;
|
|
GSList *walk;
|
|
guint stream_num = g_slist_length (asfmux->collect->data);
|
|
guint metadata_obj_size = 0;
|
|
GstAsfTags *asftags;
|
|
guint64 padding = asfmux->prop_padding;
|
|
if (padding < ASF_PADDING_OBJECT_SIZE)
|
|
padding = 0;
|
|
|
|
/* from this point we started writing the headers */
|
|
GST_INFO_OBJECT (asfmux, "Writing headers");
|
|
asfmux->state = GST_ASF_MUX_STATE_HEADERS;
|
|
|
|
/* let downstream know we think in BYTES and expect to do seeking later */
|
|
gst_pad_push_event (asfmux->srcpad,
|
|
gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0));
|
|
|
|
asfmux->file_id = gst_asf_generate_file_id ();
|
|
|
|
/* Get the metadata for content description object.
|
|
* We store our own taglist because it might get changed from now
|
|
* to the time we actually add its contents to the file, changing
|
|
* the size of the data we already calculated here.
|
|
*/
|
|
asftags = g_new0 (GstAsfTags, 1);
|
|
gst_asf_mux_get_content_description_tags (asfmux, asftags);
|
|
|
|
/* get the total metadata objects size */
|
|
for (walk = asfmux->collect->data; walk; walk = g_slist_next (walk)) {
|
|
metadata_obj_size += gst_asf_mux_get_metadata_object_size (asfmux,
|
|
(GstAsfPad *) walk->data);
|
|
}
|
|
|
|
/* alloc a buffer for all header objects */
|
|
buf = gst_buffer_new_and_alloc (gst_asf_mux_get_headers_size (asfmux) +
|
|
asftags->cont_desc_size +
|
|
asftags->ext_cont_desc_size +
|
|
metadata_obj_size + padding + ASF_DATA_OBJECT_SIZE);
|
|
bufdata = GST_BUFFER_DATA (buf);
|
|
|
|
gst_asf_mux_write_header_object (asfmux, &bufdata, GST_BUFFER_SIZE (buf) -
|
|
ASF_DATA_OBJECT_SIZE, 2 + stream_num);
|
|
|
|
/* get the position of the file properties object for
|
|
* updating it in gst_asf_mux_stop_file */
|
|
asfmux->file_properties_object_position = bufdata - GST_BUFFER_DATA (buf);
|
|
gst_asf_mux_write_file_properties (asfmux, &bufdata);
|
|
|
|
for (walk = asfmux->collect->data; walk; walk = g_slist_next (walk)) {
|
|
gst_asf_mux_write_stream_properties (asfmux, &bufdata,
|
|
(GstAsfPad *) walk->data);
|
|
}
|
|
|
|
if (asftags->cont_desc_size) {
|
|
gst_asf_mux_write_content_description (asfmux, &bufdata, asftags->tags);
|
|
}
|
|
if (asftags->ext_cont_desc_size) {
|
|
gst_asf_mux_write_ext_content_description (asfmux, &bufdata, asftags->tags);
|
|
}
|
|
|
|
if (asftags) {
|
|
if (asftags->tags)
|
|
gst_tag_list_free (asftags->tags);
|
|
g_free (asftags);
|
|
}
|
|
|
|
/* writing header extension objects */
|
|
gst_asf_mux_write_header_extension (asfmux, &bufdata, stream_num *
|
|
ASF_EXTENDED_STREAM_PROPERTIES_OBJECT_SIZE + metadata_obj_size);
|
|
for (walk = asfmux->collect->data; walk; walk = g_slist_next (walk)) {
|
|
gst_asf_mux_write_extended_stream_properties (asfmux, &bufdata,
|
|
(GstAsfPad *) walk->data);
|
|
}
|
|
for (walk = asfmux->collect->data; walk; walk = g_slist_next (walk)) {
|
|
gst_asf_mux_write_metadata_object (asfmux, &bufdata,
|
|
(GstAsfPad *) walk->data);
|
|
}
|
|
|
|
gst_asf_mux_write_padding_object (asfmux, &bufdata, padding);
|
|
|
|
/* store data object position for later updating some fields */
|
|
asfmux->data_object_position = bufdata - GST_BUFFER_DATA (buf);
|
|
gst_asf_mux_write_data_object (asfmux, &bufdata);
|
|
|
|
g_assert (bufdata - GST_BUFFER_DATA (buf) == GST_BUFFER_SIZE (buf));
|
|
return gst_asf_mux_push_buffer (asfmux, buf);
|
|
}
|
|
|
|
/**
|
|
* gst_asf_mux_add_simple_index_entry:
|
|
* @asfmux:
|
|
* @videopad:
|
|
*
|
|
* Adds a new entry to the simple index of the stream handler by videopad.
|
|
* This functions doesn't check if the time ellapsed
|
|
* is larger than the established time interval between entries. The caller
|
|
* is responsible for verifying this.
|
|
*/
|
|
static void
|
|
gst_asf_mux_add_simple_index_entry (GstAsfMux * asfmux,
|
|
GstAsfVideoPad * videopad)
|
|
{
|
|
SimpleIndexEntry *entry = NULL;
|
|
GST_DEBUG_OBJECT (asfmux, "Adding new simple index entry "
|
|
"packet number:%" G_GUINT32_FORMAT ", "
|
|
"packet count:%" G_GUINT16_FORMAT,
|
|
videopad->last_keyframe_packet, videopad->last_keyframe_packet_count);
|
|
entry = g_malloc0 (sizeof (SimpleIndexEntry));
|
|
entry->packet_number = videopad->last_keyframe_packet;
|
|
entry->packet_count = videopad->last_keyframe_packet_count;
|
|
if (entry->packet_count > videopad->max_keyframe_packet_count)
|
|
videopad->max_keyframe_packet_count = entry->packet_count;
|
|
videopad->simple_index = g_slist_append (videopad->simple_index, entry);
|
|
}
|
|
|
|
/**
|
|
* gst_asf_mux_send_packet:
|
|
* @asfmux:
|
|
* @buf: The asf data packet
|
|
*
|
|
* Pushes an asf data packet downstream. The total number
|
|
* of packets and bytes of the stream are incremented.
|
|
*
|
|
* Returns: the result of pushing the buffer downstream
|
|
*/
|
|
static GstFlowReturn
|
|
gst_asf_mux_send_packet (GstAsfMux * asfmux, GstBuffer * buf)
|
|
{
|
|
g_assert (GST_BUFFER_SIZE (buf) == asfmux->packet_size);
|
|
asfmux->total_data_packets++;
|
|
GST_LOG_OBJECT (asfmux,
|
|
"Pushing a packet of size %u and timestamp %" G_GUINT64_FORMAT,
|
|
GST_BUFFER_SIZE (buf), GST_BUFFER_TIMESTAMP (buf));
|
|
return gst_asf_mux_push_buffer (asfmux, buf);
|
|
}
|
|
|
|
/**
|
|
* gst_asf_mux_flush_payloads:
|
|
* @asfmux: #GstAsfMux to flush the payloads from
|
|
*
|
|
* Fills an asf packet with asfmux queued payloads and
|
|
* pushes it downstream.
|
|
*
|
|
* Returns: The result of pushing the packet
|
|
*/
|
|
static GstFlowReturn
|
|
gst_asf_mux_flush_payloads (GstAsfMux * asfmux)
|
|
{
|
|
GstBuffer *buf;
|
|
guint8 payloads_count = 0; /* we only use 6 bits, max is 63 */
|
|
guint i;
|
|
GstClockTime send_ts = GST_CLOCK_TIME_NONE;
|
|
guint64 size_left;
|
|
guint8 *data;
|
|
GSList *walk;
|
|
GstAsfPad *pad;
|
|
gboolean has_keyframe;
|
|
AsfPayload *payload;
|
|
guint32 payload_size;
|
|
|
|
if (asfmux->payloads == NULL)
|
|
return GST_FLOW_OK; /* nothing to send is ok */
|
|
|
|
GST_DEBUG_OBJECT (asfmux, "Flushing payloads");
|
|
|
|
buf = gst_buffer_new_and_alloc (asfmux->packet_size);
|
|
memset (GST_BUFFER_DATA (buf), 0, asfmux->packet_size);
|
|
|
|
/* 1 for the multiple payload flags */
|
|
data = GST_BUFFER_DATA (buf) + ASF_PAYLOAD_PARSING_INFO_SIZE + 1;
|
|
size_left = asfmux->packet_size - ASF_PAYLOAD_PARSING_INFO_SIZE - 1;
|
|
|
|
has_keyframe = FALSE;
|
|
walk = asfmux->payloads;
|
|
while (walk && payloads_count < MAX_PAYLOADS_IN_A_PACKET) {
|
|
payload = (AsfPayload *) walk->data;
|
|
pad = (GstAsfPad *) payload->pad;
|
|
payload_size = gst_asf_payload_get_size (payload);
|
|
if (size_left < payload_size) {
|
|
break; /* next payload doesn't fit fully */
|
|
}
|
|
|
|
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (send_ts))) {
|
|
send_ts = GST_BUFFER_TIMESTAMP (payload->data);
|
|
}
|
|
|
|
/* adding new simple index entry (if needed) */
|
|
if (!pad->is_audio
|
|
&& GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (payload->data))) {
|
|
GstAsfVideoPad *videopad = (GstAsfVideoPad *) pad;
|
|
if (videopad->has_keyframe) {
|
|
for (; videopad->next_index_time <=
|
|
ASF_MILI_TO_100NANO (payload->presentation_time);
|
|
videopad->next_index_time += videopad->time_interval) {
|
|
gst_asf_mux_add_simple_index_entry (asfmux, videopad);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* serialize our payload */
|
|
GST_DEBUG_OBJECT (asfmux, "Serializing a payload into the packet. "
|
|
"Stream number:%" G_GUINT16_FORMAT
|
|
", media object number:%" G_GUINT16_FORMAT
|
|
", offset into media object:%" G_GUINT32_FORMAT
|
|
", replicated data length:%" G_GUINT16_FORMAT
|
|
", media object size:%" G_GUINT32_FORMAT
|
|
", presentation time:%" G_GUINT32_FORMAT
|
|
", payload size:%" G_GUINT16_FORMAT,
|
|
payload->stream_number & 0x7F,
|
|
(guint16) payload->media_obj_num, payload->offset_in_media_obj,
|
|
(guint16) payload->replicated_data_length,
|
|
payload->media_object_size,
|
|
payload->presentation_time, (guint16) GST_BUFFER_SIZE (payload->data));
|
|
gst_asf_put_payload (data, payload);
|
|
if (!payload->has_packet_info) {
|
|
payload->has_packet_info = TRUE;
|
|
payload->packet_number = asfmux->total_data_packets;
|
|
}
|
|
|
|
if (ASF_PAYLOAD_IS_KEYFRAME (payload)) {
|
|
has_keyframe = TRUE;
|
|
if (!pad->is_audio) {
|
|
GstAsfVideoPad *videopad = (GstAsfVideoPad *) pad;
|
|
videopad->last_keyframe_packet = payload->packet_number;
|
|
videopad->last_keyframe_packet_count = payload->packet_count;
|
|
videopad->has_keyframe = TRUE;
|
|
}
|
|
}
|
|
|
|
/* update our variables */
|
|
data += payload_size;
|
|
size_left -= payload_size;
|
|
payloads_count++;
|
|
walk = g_slist_next (walk);
|
|
}
|
|
|
|
/* remove flushed payloads */
|
|
GST_LOG_OBJECT (asfmux, "Freeing already used payloads");
|
|
for (i = 0; i < payloads_count; i++) {
|
|
GSList *aux = g_slist_nth (asfmux->payloads, 0);
|
|
AsfPayload *payload;
|
|
g_assert (aux);
|
|
payload = (AsfPayload *) aux->data;
|
|
asfmux->payloads = g_slist_remove (asfmux->payloads, payload);
|
|
asfmux->payload_data_size -=
|
|
(GST_BUFFER_SIZE (payload->data) + ASF_MULTIPLE_PAYLOAD_HEADER_SIZE);
|
|
gst_asf_payload_free (payload);
|
|
}
|
|
|
|
/* check if we can add part of the next payload */
|
|
if (asfmux->payloads && size_left > ASF_MULTIPLE_PAYLOAD_HEADER_SIZE) {
|
|
AsfPayload *payload =
|
|
(AsfPayload *) g_slist_nth (asfmux->payloads, 0)->data;
|
|
guint16 bytes_writen;
|
|
GST_DEBUG_OBJECT (asfmux, "Adding part of a payload to a packet");
|
|
|
|
if (ASF_PAYLOAD_IS_KEYFRAME (payload))
|
|
has_keyframe = TRUE;
|
|
|
|
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (send_ts))) {
|
|
send_ts = GST_BUFFER_TIMESTAMP (payload->data);
|
|
}
|
|
|
|
bytes_writen = gst_asf_put_subpayload (data, payload, size_left);
|
|
if (!payload->has_packet_info) {
|
|
payload->has_packet_info = TRUE;
|
|
payload->packet_number = asfmux->total_data_packets;
|
|
}
|
|
asfmux->payload_data_size -= bytes_writen;
|
|
size_left -= (bytes_writen + ASF_MULTIPLE_PAYLOAD_HEADER_SIZE);
|
|
payloads_count++;
|
|
}
|
|
|
|
GST_LOG_OBJECT (asfmux, "Payload data size: %" G_GUINT32_FORMAT,
|
|
asfmux->payload_data_size);
|
|
|
|
/* fill payload parsing info */
|
|
data = GST_BUFFER_DATA (buf);
|
|
/* flags */
|
|
GST_WRITE_UINT8 (data, (0x0 << 7) | /* no error correction */
|
|
(ASF_FIELD_TYPE_DWORD << 5) | /* packet length type */
|
|
(ASF_FIELD_TYPE_DWORD << 3) | /* padding length type */
|
|
(ASF_FIELD_TYPE_NONE << 1) | /* sequence type type */
|
|
0x1); /* multiple payloads */
|
|
|
|
/* property flags - according to the spec, this should not change */
|
|
GST_WRITE_UINT8 (data + 1, (ASF_FIELD_TYPE_BYTE << 6) | /* stream number length type */
|
|
(ASF_FIELD_TYPE_BYTE << 4) | /* media obj number length type */
|
|
(ASF_FIELD_TYPE_DWORD << 2) | /* offset info media object length type */
|
|
(ASF_FIELD_TYPE_BYTE)); /* replicated data length type */
|
|
|
|
GST_WRITE_UINT32_LE (data + 2, asfmux->packet_size);
|
|
GST_WRITE_UINT32_LE (data + 6, size_left); /* padding size */
|
|
|
|
/* packet send time */
|
|
if (GST_CLOCK_TIME_IS_VALID (send_ts)) {
|
|
GST_WRITE_UINT32_LE (data + 10, (send_ts / GST_MSECOND));
|
|
GST_BUFFER_TIMESTAMP (buf) = send_ts;
|
|
}
|
|
|
|
/* packet duration */
|
|
GST_WRITE_UINT16_LE (data + 14, 0); /* FIXME send duration needs to be estimated */
|
|
|
|
/* multiple payloads flags */
|
|
GST_WRITE_UINT8 (data + 16, 0x2 << 6 | payloads_count);
|
|
|
|
if (payloads_count == 0) {
|
|
GST_WARNING_OBJECT (asfmux, "Sending packet without any payload");
|
|
}
|
|
asfmux->data_object_size += GST_BUFFER_SIZE (buf);
|
|
if (!has_keyframe)
|
|
GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
|
|
return gst_asf_mux_send_packet (asfmux, buf);
|
|
}
|
|
|
|
/**
|
|
* stream_number_compare:
|
|
* @a: a #GstAsfPad
|
|
* @b: another #GstAsfPad
|
|
*
|
|
* Utility function to compare #GstAsfPad by their stream numbers
|
|
*
|
|
* Returns: The difference between their stream numbers
|
|
*/
|
|
static gint
|
|
stream_number_compare (gconstpointer a, gconstpointer b)
|
|
{
|
|
GstAsfPad *pad_a = (GstAsfPad *) a;
|
|
GstAsfPad *pad_b = (GstAsfPad *) b;
|
|
return pad_b->stream_number - pad_a->stream_number;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_asf_mux_push_simple_index (GstAsfMux * asfmux, GstAsfVideoPad * pad)
|
|
{
|
|
guint64 object_size = ASF_SIMPLE_INDEX_OBJECT_SIZE +
|
|
g_slist_length (pad->simple_index) * ASF_SIMPLE_INDEX_ENTRY_SIZE;
|
|
GstBuffer *buf = gst_buffer_new_and_alloc (object_size);
|
|
GSList *walk;
|
|
guint8 *data = GST_BUFFER_DATA (buf);
|
|
guint32 entries_count = g_slist_length (pad->simple_index);
|
|
|
|
gst_asf_put_guid (data, guids[ASF_SIMPLE_INDEX_OBJECT_INDEX]);
|
|
GST_WRITE_UINT64_LE (data + 16, object_size);
|
|
gst_asf_put_guid (data + 24, asfmux->file_id);
|
|
GST_WRITE_UINT64_LE (data + 40, pad->time_interval);
|
|
GST_WRITE_UINT32_LE (data + 48, pad->max_keyframe_packet_count);
|
|
GST_WRITE_UINT32_LE (data + 52, entries_count);
|
|
data += ASF_SIMPLE_INDEX_OBJECT_SIZE;
|
|
|
|
GST_DEBUG_OBJECT (asfmux,
|
|
"Simple index object values - size:%" G_GUINT64_FORMAT ", time interval:%"
|
|
G_GUINT64_FORMAT ", max packet count:%" G_GUINT32_FORMAT ", entries:%"
|
|
G_GUINT16_FORMAT, object_size, pad->time_interval,
|
|
pad->max_keyframe_packet_count, entries_count);
|
|
|
|
for (walk = pad->simple_index; walk; walk = g_slist_next (walk)) {
|
|
SimpleIndexEntry *entry = (SimpleIndexEntry *) walk->data;
|
|
GST_DEBUG_OBJECT (asfmux, "Simple index entry: packet_number:%"
|
|
G_GUINT32_FORMAT " packet_count:%" G_GUINT16_FORMAT,
|
|
entry->packet_number, entry->packet_count);
|
|
GST_WRITE_UINT32_LE (data, entry->packet_number);
|
|
GST_WRITE_UINT16_LE (data + 4, entry->packet_count);
|
|
data += ASF_SIMPLE_INDEX_ENTRY_SIZE;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (asfmux, "Pushing the simple index");
|
|
g_assert (data - GST_BUFFER_DATA (buf) == object_size);
|
|
return gst_asf_mux_push_buffer (asfmux, buf);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_asf_mux_write_indexes (GstAsfMux * asfmux)
|
|
{
|
|
GSList *ordered_pads;
|
|
GSList *walker;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
|
|
/* write simple indexes for video medias */
|
|
ordered_pads =
|
|
g_slist_sort (g_slist_copy (asfmux->collect->data),
|
|
(GCompareFunc) stream_number_compare);
|
|
for (walker = ordered_pads; walker; walker = g_slist_next (walker)) {
|
|
GstAsfPad *pad = (GstAsfPad *) walker->data;
|
|
if (!pad->is_audio) {
|
|
ret = gst_asf_mux_push_simple_index (asfmux, (GstAsfVideoPad *) pad);
|
|
if (ret != GST_FLOW_OK) {
|
|
GST_ERROR_OBJECT (asfmux, "Failed to write simple index for stream %"
|
|
G_GUINT16_FORMAT, (guint16) pad->stream_number);
|
|
goto cleanup_and_return;
|
|
}
|
|
}
|
|
}
|
|
cleanup_and_return:
|
|
g_slist_free (ordered_pads);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* gst_asf_mux_stop_file:
|
|
* @asfmux: #GstAsfMux
|
|
*
|
|
* Finalizes the asf stream by pushing the indexes after
|
|
* the data object. Also seeks back to the header positions
|
|
* to rewrite some fields such as the total number of bytes
|
|
* of the file, or any other that couldn't be predicted/known
|
|
* back on the header generation.
|
|
*
|
|
* Returns: GST_FLOW_OK on success
|
|
*/
|
|
static GstFlowReturn
|
|
gst_asf_mux_stop_file (GstAsfMux * asfmux)
|
|
{
|
|
GstEvent *event;
|
|
GstBuffer *buf;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
GSList *walk;
|
|
GstClockTime play_duration = 0;
|
|
guint32 bitrate = 0;
|
|
|
|
/* write indexes */
|
|
ret = gst_asf_mux_write_indexes (asfmux);
|
|
if (ret != GST_FLOW_OK) {
|
|
GST_ERROR_OBJECT (asfmux, "Failed to write indexes");
|
|
return ret;
|
|
}
|
|
|
|
/* find max stream duration and bitrate */
|
|
for (walk = asfmux->collect->data; walk; walk = g_slist_next (walk)) {
|
|
GstAsfPad *pad = (GstAsfPad *) walk->data;
|
|
bitrate += pad->bitrate;
|
|
if (pad->play_duration > play_duration)
|
|
play_duration = pad->play_duration;
|
|
}
|
|
|
|
/* going back to file properties object to fill in
|
|
* values we didn't know back then */
|
|
GST_DEBUG_OBJECT (asfmux,
|
|
"Sending new segment to file properties object position");
|
|
event =
|
|
gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES,
|
|
asfmux->file_properties_object_position + 40, GST_CLOCK_TIME_NONE, 0);
|
|
if (!gst_pad_push_event (asfmux->srcpad, event)) {
|
|
GST_ERROR_OBJECT (asfmux, "Failed to update file properties object");
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
/* All file properties fields except the first 40 bytes */
|
|
buf = gst_buffer_new_and_alloc (ASF_FILE_PROPERTIES_OBJECT_SIZE - 40);
|
|
|
|
GST_WRITE_UINT64_LE (GST_BUFFER_DATA (buf), asfmux->file_size);
|
|
gst_asf_put_time (GST_BUFFER_DATA (buf) + 8, gst_asf_get_current_time ());
|
|
GST_WRITE_UINT64_LE (GST_BUFFER_DATA (buf) + 16, asfmux->total_data_packets);
|
|
GST_WRITE_UINT64_LE (GST_BUFFER_DATA (buf) + 24, (play_duration / 100) +
|
|
ASF_MILI_TO_100NANO (asfmux->preroll));
|
|
GST_WRITE_UINT64_LE (GST_BUFFER_DATA (buf) + 32, (play_duration / 100)); /* TODO send duration */
|
|
GST_WRITE_UINT64_LE (GST_BUFFER_DATA (buf) + 40, asfmux->preroll);
|
|
GST_WRITE_UINT32_LE (GST_BUFFER_DATA (buf) + 48, 0x2); /* flags - seekable */
|
|
GST_WRITE_UINT32_LE (GST_BUFFER_DATA (buf) + 52, asfmux->packet_size);
|
|
GST_WRITE_UINT32_LE (GST_BUFFER_DATA (buf) + 56, asfmux->packet_size);
|
|
/* FIXME - we want the max instantaneous bitrate, for vbr streams, we can't
|
|
* get it this way, this would be the average, right? */
|
|
GST_WRITE_UINT32_LE (GST_BUFFER_DATA (buf) + 60, bitrate); /* max bitrate */
|
|
|
|
/* we don't use gst_asf_mux_push_buffer because we are overwriting
|
|
* already sent data */
|
|
ret = gst_pad_push (asfmux->srcpad, buf);
|
|
if (ret != GST_FLOW_OK) {
|
|
GST_ERROR_OBJECT (asfmux, "Failed to update file properties object");
|
|
return ret;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (asfmux, "Seeking back to data object");
|
|
|
|
/* seek back to the data object */
|
|
event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES,
|
|
asfmux->data_object_position + 16, GST_CLOCK_TIME_NONE, 0);
|
|
|
|
if (!gst_pad_push_event (asfmux->srcpad, event)) {
|
|
GST_ERROR_OBJECT (asfmux, "Seek to update data object failed");
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
buf = gst_buffer_new_and_alloc (32); /* qword+guid+qword */
|
|
GST_WRITE_UINT64_LE (GST_BUFFER_DATA (buf), asfmux->data_object_size +
|
|
ASF_DATA_OBJECT_SIZE);
|
|
gst_asf_put_guid (GST_BUFFER_DATA (buf) + 8, asfmux->file_id);
|
|
GST_WRITE_UINT64_LE (GST_BUFFER_DATA (buf) + 24, asfmux->total_data_packets);
|
|
|
|
return gst_pad_push (asfmux->srcpad, buf);
|
|
}
|
|
|
|
/**
|
|
* gst_asf_mux_process_buffer:
|
|
* @asfmux:
|
|
* @pad: stream of the buffer
|
|
* @buf: The buffer to be processed
|
|
*
|
|
* Processes the buffer by parsing it and
|
|
* queueing it up as an asf payload for later
|
|
* being added and pushed inside an asf packet.
|
|
*
|
|
* Returns: a #GstFlowReturn
|
|
*/
|
|
static GstFlowReturn
|
|
gst_asf_mux_process_buffer (GstAsfMux * asfmux, GstAsfPad * pad,
|
|
GstBuffer * buf)
|
|
{
|
|
guint8 keyframe;
|
|
AsfPayload *payload;
|
|
|
|
payload = g_malloc0 (sizeof (AsfPayload));
|
|
payload->pad = (GstCollectData *) pad;
|
|
payload->data = buf;
|
|
|
|
GST_LOG_OBJECT (asfmux,
|
|
"Processing payload data for stream number %" G_GUINT16_FORMAT,
|
|
pad->stream_number);
|
|
|
|
/* stream number */
|
|
if (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
|
|
keyframe = 0;
|
|
} else {
|
|
keyframe = 0x1 << 7;
|
|
}
|
|
payload->stream_number = keyframe | pad->stream_number;
|
|
|
|
payload->media_obj_num = pad->media_object_number;
|
|
payload->offset_in_media_obj = 0;
|
|
payload->replicated_data_length = 8;
|
|
|
|
/* replicated data - 1) media object size */
|
|
payload->media_object_size = GST_BUFFER_SIZE (buf);
|
|
/* replicated data - 2) presentation time */
|
|
if (!GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (buf))) {
|
|
GST_ERROR_OBJECT (asfmux, "Received buffer without timestamp");
|
|
gst_asf_payload_free (payload);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
payload->presentation_time = asfmux->preroll +
|
|
(GST_BUFFER_TIMESTAMP (buf) / GST_MSECOND);
|
|
|
|
/* update counting values */
|
|
pad->media_object_number = (pad->media_object_number + 1) % 256;
|
|
if (GST_BUFFER_DURATION (buf) != GST_CLOCK_TIME_NONE) {
|
|
pad->play_duration += GST_BUFFER_DURATION (buf);
|
|
} else {
|
|
GST_WARNING_OBJECT (asfmux, "Received buffer without duration, it will not "
|
|
"be accounted in the total file time");
|
|
}
|
|
|
|
asfmux->payloads = g_slist_append (asfmux->payloads, payload);
|
|
asfmux->payload_data_size +=
|
|
GST_BUFFER_SIZE (buf) + ASF_MULTIPLE_PAYLOAD_HEADER_SIZE;
|
|
GST_LOG_OBJECT (asfmux, "Payload data size: %" G_GUINT32_FORMAT,
|
|
asfmux->payload_data_size);
|
|
|
|
while (asfmux->payload_data_size + ASF_PAYLOAD_PARSING_INFO_SIZE >=
|
|
asfmux->packet_size) {
|
|
GstFlowReturn ret = gst_asf_mux_flush_payloads (asfmux);
|
|
if (ret != GST_FLOW_OK)
|
|
return ret;
|
|
}
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_asf_mux_collected (GstCollectPads * collect, gpointer data)
|
|
{
|
|
GstAsfMux *asfmux = GST_ASF_MUX_CAST (data);
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
GstAsfPad *best_pad = NULL;
|
|
GstClockTime best_time = GST_CLOCK_TIME_NONE;
|
|
GstBuffer *buf = NULL;
|
|
GSList *walk;
|
|
|
|
if (G_UNLIKELY (asfmux->state == GST_ASF_MUX_STATE_NONE)) {
|
|
ret = gst_asf_mux_start_file (asfmux);
|
|
if (ret != GST_FLOW_OK) {
|
|
GST_WARNING_OBJECT (asfmux, "Failed to send headers");
|
|
return ret;
|
|
} else {
|
|
asfmux->state = GST_ASF_MUX_STATE_DATA;
|
|
}
|
|
}
|
|
|
|
if (G_UNLIKELY (asfmux->state == GST_ASF_MUX_STATE_EOS))
|
|
return GST_FLOW_UNEXPECTED;
|
|
|
|
/* select the earliest buffer */
|
|
walk = asfmux->collect->data;
|
|
while (walk) {
|
|
GstAsfPad *pad;
|
|
GstCollectData *data;
|
|
GstClockTime time;
|
|
|
|
data = (GstCollectData *) walk->data;
|
|
pad = (GstAsfPad *) data;
|
|
|
|
walk = g_slist_next (walk);
|
|
|
|
buf = gst_collect_pads_peek (collect, data);
|
|
if (buf == NULL) {
|
|
GST_LOG_OBJECT (asfmux, "Pad %s has no buffers",
|
|
GST_PAD_NAME (pad->collect.pad));
|
|
continue;
|
|
}
|
|
time = GST_BUFFER_TIMESTAMP (buf);
|
|
gst_buffer_unref (buf);
|
|
|
|
if (best_pad == NULL || !GST_CLOCK_TIME_IS_VALID (time) ||
|
|
(GST_CLOCK_TIME_IS_VALID (best_time) && time < best_time)) {
|
|
best_pad = pad;
|
|
best_time = time;
|
|
}
|
|
}
|
|
|
|
if (best_pad != NULL) {
|
|
/* we have data */
|
|
GST_LOG_OBJECT (asfmux, "selected pad %s with time %" GST_TIME_FORMAT,
|
|
GST_PAD_NAME (best_pad->collect.pad), GST_TIME_ARGS (best_time));
|
|
buf = gst_collect_pads_pop (collect, &best_pad->collect);
|
|
ret = gst_asf_mux_process_buffer (asfmux, best_pad, buf);
|
|
} else {
|
|
/* no data, let's finish it up */
|
|
while (asfmux->payloads) {
|
|
ret = gst_asf_mux_flush_payloads (asfmux);
|
|
if (ret != GST_FLOW_OK) {
|
|
return ret;
|
|
}
|
|
}
|
|
g_assert (asfmux->payloads == NULL);
|
|
g_assert (asfmux->payload_data_size == 0);
|
|
/* in 'is-live' mode we don't need to push indexes
|
|
* or updating headers */
|
|
if (!asfmux->prop_is_live) {
|
|
ret = gst_asf_mux_stop_file (asfmux);
|
|
}
|
|
if (ret == GST_FLOW_OK) {
|
|
gst_pad_push_event (asfmux->srcpad, gst_event_new_eos ());
|
|
ret = GST_FLOW_UNEXPECTED;
|
|
}
|
|
asfmux->state = GST_ASF_MUX_STATE_EOS;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
gst_asf_mux_pad_reset (GstAsfPad * pad)
|
|
{
|
|
pad->stream_number = 0;
|
|
pad->media_object_number = 0;
|
|
pad->play_duration = (GstClockTime) 0;
|
|
pad->bitrate = 0;
|
|
if (pad->codec_data)
|
|
gst_buffer_unref (pad->codec_data);
|
|
pad->codec_data = NULL;
|
|
if (pad->taglist)
|
|
gst_tag_list_free (pad->taglist);
|
|
pad->taglist = NULL;
|
|
|
|
if (pad->is_audio) {
|
|
GstAsfAudioPad *audiopad = (GstAsfAudioPad *) pad;
|
|
audiopad->audioinfo.rate = 0;
|
|
audiopad->audioinfo.channels = 0;
|
|
audiopad->audioinfo.format = 0;
|
|
audiopad->audioinfo.av_bps = 0;
|
|
audiopad->audioinfo.blockalign = 0;
|
|
audiopad->audioinfo.size = 0;
|
|
} else {
|
|
GstAsfVideoPad *videopad = (GstAsfVideoPad *) pad;
|
|
videopad->vidinfo.size = 0;
|
|
videopad->vidinfo.width = 0;
|
|
videopad->vidinfo.height = 0;
|
|
videopad->vidinfo.planes = 1;
|
|
videopad->vidinfo.bit_cnt = 0;
|
|
videopad->vidinfo.compression = 0;
|
|
videopad->vidinfo.image_size = 0;
|
|
videopad->vidinfo.xpels_meter = 0;
|
|
videopad->vidinfo.ypels_meter = 0;
|
|
videopad->vidinfo.num_colors = 0;
|
|
videopad->vidinfo.imp_colors = 0;
|
|
|
|
videopad->last_keyframe_packet = 0;
|
|
videopad->has_keyframe = FALSE;
|
|
videopad->last_keyframe_packet_count = 0;
|
|
videopad->max_keyframe_packet_count = 0;
|
|
videopad->next_index_time = 0;
|
|
videopad->time_interval = DEFAULT_SIMPLE_INDEX_TIME_INTERVAL;
|
|
if (videopad->simple_index) {
|
|
GSList *walk;
|
|
for (walk = videopad->simple_index; walk; walk = g_slist_next (walk)) {
|
|
g_free (walk->data);
|
|
walk->data = NULL;
|
|
}
|
|
g_slist_free (videopad->simple_index);
|
|
}
|
|
videopad->simple_index = NULL;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_asf_mux_audio_set_caps (GstPad * pad, GstCaps * caps)
|
|
{
|
|
GstAsfMux *asfmux;
|
|
GstAsfAudioPad *audiopad;
|
|
GstStructure *structure;
|
|
const gchar *caps_name;
|
|
gint channels, rate;
|
|
gchar *aux;
|
|
const GValue *codec_data;
|
|
|
|
asfmux = GST_ASF_MUX (gst_pad_get_parent (pad));
|
|
|
|
audiopad = (GstAsfAudioPad *) gst_pad_get_element_private (pad);
|
|
g_assert (audiopad);
|
|
|
|
aux = gst_caps_to_string (caps);
|
|
GST_DEBUG_OBJECT (asfmux, "%s:%s, caps=%s", GST_DEBUG_PAD_NAME (pad), aux);
|
|
g_free (aux);
|
|
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
caps_name = gst_structure_get_name (structure);
|
|
|
|
if (!gst_structure_get_int (structure, "channels", &channels) ||
|
|
!gst_structure_get_int (structure, "rate", &rate))
|
|
goto refuse_caps;
|
|
|
|
audiopad->audioinfo.channels = (guint16) channels;
|
|
audiopad->audioinfo.rate = (guint32) rate;
|
|
|
|
/* taken from avimux
|
|
* codec initialization data, if any
|
|
*/
|
|
codec_data = gst_structure_get_value (structure, "codec_data");
|
|
if (codec_data) {
|
|
audiopad->pad.codec_data = gst_value_get_buffer (codec_data);
|
|
gst_buffer_ref (audiopad->pad.codec_data);
|
|
}
|
|
|
|
if (strcmp (caps_name, "audio/x-wma") == 0) {
|
|
gint version;
|
|
gint block_align = 0;
|
|
gint bitrate = 0;
|
|
|
|
if (!gst_structure_get_int (structure, "wmaversion", &version)) {
|
|
goto refuse_caps;
|
|
}
|
|
|
|
if (gst_structure_get_int (structure, "block_align", &block_align)) {
|
|
audiopad->audioinfo.blockalign = (guint16) block_align;
|
|
}
|
|
if (gst_structure_get_int (structure, "bitrate", &bitrate)) {
|
|
audiopad->pad.bitrate = (guint32) bitrate;
|
|
audiopad->audioinfo.av_bps = bitrate / 8;
|
|
}
|
|
|
|
if (version == 1) {
|
|
audiopad->audioinfo.format = GST_RIFF_WAVE_FORMAT_WMAV1;
|
|
} else if (version == 2) {
|
|
audiopad->audioinfo.format = GST_RIFF_WAVE_FORMAT_WMAV2;
|
|
} else if (version == 3) {
|
|
audiopad->audioinfo.format = GST_RIFF_WAVE_FORMAT_WMAV3;
|
|
} else {
|
|
goto refuse_caps;
|
|
}
|
|
} else if (strcmp (caps_name, "audio/mpeg") == 0) {
|
|
gint version;
|
|
gint layer;
|
|
|
|
if (!gst_structure_get_int (structure, "mpegversion", &version) ||
|
|
!gst_structure_get_int (structure, "layer", &layer)) {
|
|
goto refuse_caps;
|
|
}
|
|
if (version != 1 || layer != 3) {
|
|
goto refuse_caps;
|
|
}
|
|
|
|
audiopad->audioinfo.format = GST_RIFF_WAVE_FORMAT_MPEGL3;
|
|
} else {
|
|
goto refuse_caps;
|
|
}
|
|
|
|
gst_object_unref (asfmux);
|
|
return TRUE;
|
|
|
|
refuse_caps:
|
|
GST_WARNING_OBJECT (asfmux, "pad %s refused caps %" GST_PTR_FORMAT,
|
|
GST_PAD_NAME (pad), caps);
|
|
gst_object_unref (asfmux);
|
|
return FALSE;
|
|
}
|
|
|
|
/* TODO Read pixel aspect ratio */
|
|
static gboolean
|
|
gst_asf_mux_video_set_caps (GstPad * pad, GstCaps * caps)
|
|
{
|
|
GstAsfMux *asfmux;
|
|
GstAsfVideoPad *videopad;
|
|
GstStructure *structure;
|
|
const gchar *caps_name;
|
|
gint width, height;
|
|
gchar *aux;
|
|
const GValue *codec_data;
|
|
|
|
asfmux = GST_ASF_MUX (gst_pad_get_parent (pad));
|
|
|
|
videopad = (GstAsfVideoPad *) gst_pad_get_element_private (pad);
|
|
g_assert (videopad);
|
|
|
|
aux = gst_caps_to_string (caps);
|
|
GST_DEBUG_OBJECT (asfmux, "%s:%s, caps=%s", GST_DEBUG_PAD_NAME (pad), aux);
|
|
g_free (aux);
|
|
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
caps_name = gst_structure_get_name (structure);
|
|
|
|
if (!gst_structure_get_int (structure, "width", &width) ||
|
|
!gst_structure_get_int (structure, "height", &height))
|
|
goto refuse_caps;
|
|
|
|
videopad->vidinfo.width = (gint32) width;
|
|
videopad->vidinfo.height = (gint32) height;
|
|
|
|
/* taken from avimux
|
|
* codec initialization data, if any
|
|
*/
|
|
codec_data = gst_structure_get_value (structure, "codec_data");
|
|
if (codec_data) {
|
|
videopad->pad.codec_data = gst_value_get_buffer (codec_data);
|
|
gst_buffer_ref (videopad->pad.codec_data);
|
|
}
|
|
|
|
if (strcmp (caps_name, "video/x-wmv") == 0) {
|
|
guint32 fourcc;
|
|
|
|
videopad->vidinfo.bit_cnt = 24;
|
|
|
|
/* in case we have a fourcc, we use it */
|
|
if (gst_structure_get_fourcc (structure, "format", &fourcc)) {
|
|
videopad->vidinfo.compression = fourcc;
|
|
} else {
|
|
gint version;
|
|
if (!gst_structure_get_int (structure, "wmvversion", &version))
|
|
goto refuse_caps;
|
|
if (version == 2) {
|
|
videopad->vidinfo.compression = GST_MAKE_FOURCC ('W', 'M', 'V', '2');
|
|
} else if (version == 1) {
|
|
videopad->vidinfo.compression = GST_MAKE_FOURCC ('W', 'M', 'V', '1');
|
|
} else if (version == 3) {
|
|
videopad->vidinfo.compression = GST_MAKE_FOURCC ('W', 'M', 'V', '3');
|
|
} else {
|
|
goto refuse_caps;
|
|
}
|
|
}
|
|
} else {
|
|
goto refuse_caps;
|
|
}
|
|
|
|
gst_object_unref (asfmux);
|
|
return TRUE;
|
|
|
|
refuse_caps:
|
|
GST_WARNING_OBJECT (asfmux, "pad %s refused caps %" GST_PTR_FORMAT,
|
|
GST_PAD_NAME (pad), caps);
|
|
gst_object_unref (asfmux);
|
|
return FALSE;
|
|
}
|
|
|
|
static GstPad *
|
|
gst_asf_mux_request_new_pad (GstElement * element,
|
|
GstPadTemplate * templ, const gchar * req_name)
|
|
{
|
|
GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
|
|
GstAsfMux *asfmux = GST_ASF_MUX_CAST (element);
|
|
GstPad *newpad;
|
|
GstAsfPad *collect_pad;
|
|
gboolean is_audio;
|
|
guint collect_size = 0;
|
|
gchar *name;
|
|
|
|
GST_DEBUG_OBJECT (asfmux, "Requested pad: %s", GST_STR_NULL (req_name));
|
|
|
|
if (asfmux->state != GST_ASF_MUX_STATE_NONE) {
|
|
GST_WARNING_OBJECT (asfmux, "Not providing request pad after element is at "
|
|
"paused/playing state.");
|
|
return NULL;
|
|
}
|
|
|
|
if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
|
|
name = g_strdup_printf ("audio_%02d", asfmux->stream_number + 1);
|
|
GST_DEBUG_OBJECT (asfmux, "Adding new pad %s", name);
|
|
newpad = gst_pad_new_from_template (templ, name);
|
|
g_free (name);
|
|
is_audio = TRUE;
|
|
gst_pad_set_setcaps_function (newpad,
|
|
GST_DEBUG_FUNCPTR (gst_asf_mux_audio_set_caps));
|
|
} else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
|
|
name = g_strdup_printf ("video_%02d", asfmux->stream_number + 1);
|
|
GST_DEBUG_OBJECT (asfmux, "Adding new pad %s", name);
|
|
newpad = gst_pad_new_from_template (templ, name);
|
|
g_free (name);
|
|
is_audio = FALSE;
|
|
gst_pad_set_setcaps_function (newpad,
|
|
GST_DEBUG_FUNCPTR (gst_asf_mux_video_set_caps));
|
|
} else {
|
|
GST_WARNING_OBJECT (asfmux, "This is not our template!");
|
|
return NULL;
|
|
}
|
|
|
|
/* add pad to collections */
|
|
if (is_audio) {
|
|
collect_size = sizeof (GstAsfAudioPad);
|
|
} else {
|
|
collect_size = sizeof (GstAsfVideoPad);
|
|
}
|
|
collect_pad = (GstAsfPad *)
|
|
gst_collect_pads_add_pad_full (asfmux->collect, newpad, collect_size,
|
|
(GstCollectDataDestroyNotify) (gst_asf_mux_pad_reset));
|
|
|
|
/* set up pad */
|
|
collect_pad->is_audio = is_audio;
|
|
if (!is_audio)
|
|
((GstAsfVideoPad *) collect_pad)->simple_index = NULL;
|
|
collect_pad->taglist = NULL;
|
|
gst_asf_mux_pad_reset (collect_pad);
|
|
|
|
/* set pad stream number */
|
|
asfmux->stream_number += 1;
|
|
collect_pad->stream_number = asfmux->stream_number;
|
|
|
|
/* FIXME: hacked way to override/extend the event function of
|
|
* GstCollectPads; because it sets its own event function giving
|
|
* the element no access to events.
|
|
*/
|
|
asfmux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
|
|
gst_pad_set_event_function (newpad,
|
|
GST_DEBUG_FUNCPTR (gst_asf_mux_sink_event));
|
|
|
|
gst_pad_set_active (newpad, TRUE);
|
|
gst_element_add_pad (element, newpad);
|
|
|
|
return newpad;
|
|
}
|
|
|
|
static void
|
|
gst_asf_mux_get_property (GObject * object,
|
|
guint prop_id, GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstAsfMux *asfmux;
|
|
|
|
asfmux = GST_ASF_MUX (object);
|
|
switch (prop_id) {
|
|
case PROP_PACKET_SIZE:
|
|
g_value_set_uint (value, asfmux->prop_packet_size);
|
|
break;
|
|
case PROP_PREROLL:
|
|
g_value_set_uint64 (value, asfmux->prop_preroll);
|
|
break;
|
|
case PROP_MERGE_STREAM_TAGS:
|
|
g_value_set_boolean (value, asfmux->prop_merge_stream_tags);
|
|
break;
|
|
case PROP_PADDING:
|
|
g_value_set_uint64 (value, asfmux->prop_padding);
|
|
break;
|
|
case PROP_IS_LIVE:
|
|
g_value_set_boolean (value, asfmux->prop_is_live);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_asf_mux_set_property (GObject * object,
|
|
guint prop_id, const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstAsfMux *asfmux;
|
|
|
|
asfmux = GST_ASF_MUX (object);
|
|
switch (prop_id) {
|
|
case PROP_PACKET_SIZE:
|
|
asfmux->prop_packet_size = g_value_get_uint (value);
|
|
break;
|
|
case PROP_PREROLL:
|
|
asfmux->prop_preroll = g_value_get_uint64 (value);
|
|
break;
|
|
case PROP_MERGE_STREAM_TAGS:
|
|
asfmux->prop_merge_stream_tags = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_PADDING:
|
|
asfmux->prop_padding = g_value_get_uint64 (value);
|
|
break;
|
|
case PROP_IS_LIVE:
|
|
asfmux->prop_is_live = g_value_get_boolean (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_asf_mux_change_state (GstElement * element, GstStateChange transition)
|
|
{
|
|
GstAsfMux *asfmux;
|
|
GstStateChangeReturn ret;
|
|
|
|
asfmux = GST_ASF_MUX (element);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
|
/* TODO - check if it is possible to mux 2 files without going
|
|
* through here */
|
|
asfmux->packet_size = asfmux->prop_packet_size;
|
|
asfmux->preroll = asfmux->prop_preroll;
|
|
asfmux->merge_stream_tags = asfmux->prop_merge_stream_tags;
|
|
gst_collect_pads_start (asfmux->collect);
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
gst_collect_pads_stop (asfmux->collect);
|
|
asfmux->state = GST_ASF_MUX_STATE_NONE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
if (ret == GST_STATE_CHANGE_FAILURE)
|
|
goto done;
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
done:
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
gst_asf_mux_plugin_init (GstPlugin * plugin)
|
|
{
|
|
return gst_element_register (plugin, "asfmux",
|
|
GST_RANK_NONE, GST_TYPE_ASF_MUX);
|
|
}
|