blob: b123d2d083d2673ffa21753100d4a6e24ca2f687 [file] [log] [blame]
/* Interplay MVE multiplexer plugin for GStreamer
* Copyright (C) 2006 Jens Granseuer <jensgr@gmx.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., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/*
gst-launch-1.0 filesrc location=movie.mve ! mvedemux name=d !
video/x-raw-rgb ! mvemux quick=true name=m !
filesink location=test.mve d. ! audio/x-raw-int ! m.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <gst/gst.h>
#include <gst/glib-compat-private.h>
#include "gstmvemux.h"
#include "mve.h"
GST_DEBUG_CATEGORY_STATIC (mvemux_debug);
#define GST_CAT_DEFAULT mvemux_debug
static const char mve_preamble[] = MVE_PREAMBLE;
enum
{
PROP_0,
PROP_AUDIO_COMPRESSION,
PROP_VIDEO_QUICK_ENCODING,
PROP_VIDEO_SCREEN_WIDTH,
PROP_VIDEO_SCREEN_HEIGHT
};
#define MVE_MUX_DEFAULT_COMPRESSION FALSE
#define MVE_MUX_DEFAULT_SCREEN_WIDTH 640
#define MVE_MUX_DEFAULT_SCREEN_HEIGHT 480
enum MveMuxState
{
MVE_MUX_STATE_INITIAL, /* initial state */
MVE_MUX_STATE_CONNECTED, /* linked, caps set, header not written */
MVE_MUX_STATE_PREBUFFER, /* prebuffering audio data */
MVE_MUX_STATE_MOVIE, /* writing the movie */
MVE_MUX_STATE_EOS
};
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/x-mve")
);
static GstStaticPadTemplate video_sink_factory =
GST_STATIC_PAD_TEMPLATE ("video",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS ("video/x-raw-rgb, "
"width = (int) [ 24, 1600 ], "
"height = (int) [ 24, 1200 ], "
"framerate = (fraction) [ 1, MAX ], "
"bpp = (int) 16, "
"depth = (int) 15, "
"endianness = (int) BYTE_ORDER, "
"red_mask = (int) 31744, "
"green_mask = (int) 992, "
"blue_mask = (int) 31; "
"video/x-raw-rgb, "
"bpp = (int) 8, "
"depth = (int) 8, "
"width = (int) [ 24, 1600 ], "
"height = (int) [ 24, 1200 ], "
"framerate = (fraction) [ 1, MAX ], " "endianness = (int) BYTE_ORDER"));
static GstStaticPadTemplate audio_sink_factory =
GST_STATIC_PAD_TEMPLATE ("audio",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS ("audio/x-raw-int, "
"width = (int) 8, "
"rate = (int) [ 1, MAX ], "
"channels = (int) [ 1, 2 ], "
"depth = (int) 8, "
"signed = (boolean) false; "
"audio/x-raw-int, "
"width = (int) 16, "
"rate = (int) [ 1, MAX ], "
"channels = (int) [ 1, 2 ], "
"depth = (int) 16, "
"signed = (boolean) true, " "endianness = (int) BYTE_ORDER"));
static void gst_mve_mux_base_init (GstMveMuxClass * klass);
static void gst_mve_mux_class_init (GstMveMuxClass * klass);
static void gst_mve_mux_init (GstMveMux * mvemux);
static GstElementClass *parent_class = NULL;
static void
gst_mve_mux_reset (GstMveMux * mvemux)
{
mvemux->state = MVE_MUX_STATE_INITIAL;
mvemux->stream_time = 0;
mvemux->stream_offset = 0;
mvemux->timer = 0;
mvemux->frame_duration = GST_CLOCK_TIME_NONE;
mvemux->width = 0;
mvemux->height = 0;
mvemux->screen_width = MVE_MUX_DEFAULT_SCREEN_WIDTH;
mvemux->screen_height = MVE_MUX_DEFAULT_SCREEN_HEIGHT;
mvemux->bpp = 0;
mvemux->video_frames = 0;
mvemux->pal_changed = FALSE;
mvemux->pal_first_color = 0;
mvemux->pal_colors = MVE_PALETTE_COUNT;
mvemux->quick_encoding = TRUE;
mvemux->bps = 0;
mvemux->rate = 0;
mvemux->channels = 0;
mvemux->compression = MVE_MUX_DEFAULT_COMPRESSION;
mvemux->next_ts = 0;
mvemux->max_ts = 0;
mvemux->spf = 0;
mvemux->lead_frames = 0;
mvemux->audio_frames = 0;
mvemux->chunk_has_palette = FALSE;
mvemux->chunk_has_audio = FALSE;
mvemux->audio_pad_eos = TRUE;
mvemux->video_pad_eos = TRUE;
g_free (mvemux->chunk_code_map);
mvemux->chunk_code_map = NULL;
if (mvemux->chunk_video != NULL) {
g_byte_array_free (mvemux->chunk_video, TRUE);
mvemux->chunk_video = NULL;
}
if (mvemux->chunk_audio != NULL) {
g_byte_array_free (mvemux->chunk_audio, TRUE);
mvemux->chunk_audio = NULL;
}
if (mvemux->last_frame != NULL) {
gst_buffer_unref (mvemux->last_frame);
mvemux->last_frame = NULL;
}
if (mvemux->second_last_frame != NULL) {
gst_buffer_unref (mvemux->second_last_frame);
mvemux->second_last_frame = NULL;
}
if (mvemux->audio_buffer != NULL) {
g_queue_foreach (mvemux->audio_buffer, (GFunc) gst_mini_object_unref, NULL);
g_queue_free (mvemux->audio_buffer);
}
mvemux->audio_buffer = g_queue_new ();
if (mvemux->video_buffer != NULL) {
g_queue_foreach (mvemux->video_buffer, (GFunc) gst_mini_object_unref, NULL);
g_queue_free (mvemux->video_buffer);
}
mvemux->video_buffer = g_queue_new ();
}
static void
gst_mve_mux_pad_link (GstPad * pad, GstPad * peer, gpointer data)
{
GstMveMux *mvemux = GST_MVE_MUX (data);
if (pad == mvemux->audiosink) {
mvemux->audio_pad_connected = TRUE;
} else if (pad == mvemux->videosink) {
mvemux->video_pad_connected = TRUE;
} else {
g_assert_not_reached ();
}
GST_DEBUG_OBJECT (mvemux, "pad '%s' connected", GST_PAD_NAME (pad));
}
static void
gst_mve_mux_pad_unlink (GstPad * pad, GstPad * peer, gpointer data)
{
GstMveMux *mvemux = GST_MVE_MUX (data);
if (pad == mvemux->audiosink) {
mvemux->audio_pad_connected = FALSE;
} else if (pad == mvemux->videosink) {
mvemux->video_pad_connected = FALSE;
} else {
g_assert_not_reached ();
}
GST_DEBUG_OBJECT (mvemux, "pad '%s' unlinked", GST_PAD_NAME (pad));
}
static void
gst_mve_mux_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec)
{
GstMveMux *mvemux;
g_return_if_fail (GST_IS_MVE_MUX (object));
mvemux = GST_MVE_MUX (object);
switch (prop_id) {
case PROP_AUDIO_COMPRESSION:
g_value_set_boolean (value, mvemux->compression);
break;
case PROP_VIDEO_QUICK_ENCODING:
g_value_set_boolean (value, mvemux->quick_encoding);
break;
case PROP_VIDEO_SCREEN_WIDTH:
g_value_set_uint (value, mvemux->screen_width);
break;
case PROP_VIDEO_SCREEN_HEIGHT:
g_value_set_uint (value, mvemux->screen_height);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mve_mux_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec)
{
GstMveMux *mvemux;
g_return_if_fail (GST_IS_MVE_MUX (object));
mvemux = GST_MVE_MUX (object);
switch (prop_id) {
case PROP_AUDIO_COMPRESSION:
mvemux->compression = g_value_get_boolean (value);
break;
case PROP_VIDEO_QUICK_ENCODING:
mvemux->quick_encoding = g_value_get_boolean (value);
break;
case PROP_VIDEO_SCREEN_WIDTH:
mvemux->screen_width = g_value_get_uint (value);
break;
case PROP_VIDEO_SCREEN_HEIGHT:
mvemux->screen_height = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GstStateChangeReturn
gst_mve_mux_change_state (GstElement * element, GstStateChange transition)
{
GstMveMux *mvemux;
g_return_val_if_fail (GST_IS_MVE_MUX (element), GST_STATE_CHANGE_FAILURE);
mvemux = GST_MVE_MUX (element);
if (GST_ELEMENT_CLASS (parent_class)->change_state) {
GstStateChangeReturn ret;
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
if (ret != GST_STATE_CHANGE_SUCCESS)
return ret;
}
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
gst_mve_mux_reset (mvemux);
break;
default:
break;
}
return GST_STATE_CHANGE_SUCCESS;
}
static const GstBuffer *
gst_mve_mux_palette_from_buffer (GstBuffer * buf)
{
const GstBuffer *palette = NULL;
GstCaps *caps = GST_BUFFER_CAPS (buf);
if (caps != NULL) {
GstStructure *str = gst_caps_get_structure (caps, 0);
const GValue *pal = gst_structure_get_value (str, "palette_data");
if (pal != NULL) {
palette = gst_value_get_buffer (pal);
if (GST_BUFFER_SIZE (palette) < 256 * 4)
palette = NULL;
}
}
return palette;
}
static GstFlowReturn
gst_mve_mux_palette_from_current_frame (GstMveMux * mvemux,
const GstBuffer ** pal)
{
GstBuffer *buf = g_queue_peek_head (mvemux->video_buffer);
/* get palette from buffer */
*pal = gst_mve_mux_palette_from_buffer (buf);
if (*pal == NULL) {
GST_ERROR_OBJECT (mvemux, "video buffer has no palette data");
return GST_FLOW_ERROR;
}
return GST_FLOW_OK;
}
static void
gst_mve_mux_palette_analyze (GstMveMux * mvemux, const GstBuffer * pal,
guint16 * first, guint16 * last)
{
gint i;
guint32 *col1;
col1 = (guint32 *) GST_BUFFER_DATA (pal);
/* compare current palette against last frame */
if (mvemux->last_frame == NULL) {
/* ignore 0,0,0 entries but make sure we get
at least one color */
/* FIXME: is ignoring 0,0,0 safe? possibly depends on player impl */
for (i = 0; i < MVE_PALETTE_COUNT; ++i) {
if (col1[i] != 0) {
*first = i;
break;
}
}
if (i == MVE_PALETTE_COUNT) {
*first = *last = 0;
} else {
for (i = MVE_PALETTE_COUNT - 1; i >= 0; --i) {
if (col1[i] != 0) {
*last = i;
break;
}
}
}
} else {
const GstBuffer *last_pal;
guint32 *col2;
last_pal = gst_mve_mux_palette_from_buffer (mvemux->last_frame);
g_return_if_fail (last_pal != NULL);
col2 = (guint32 *) GST_BUFFER_DATA (last_pal);
for (i = 0; i < MVE_PALETTE_COUNT; ++i) {
if (col1[i] != col2[i]) {
*first = i;
break;
}
}
for (i = MVE_PALETTE_COUNT - 1; i >= 0; --i) {
if (col1[i] != col2[i]) {
*last = i;
break;
}
}
}
GST_DEBUG_OBJECT (mvemux, "palette first:%d, last:%d", *first, *last);
}
static gboolean
gst_mve_mux_palette_changed (GstMveMux * mvemux, const GstBuffer * pal)
{
const GstBuffer *last_pal;
g_return_val_if_fail (mvemux->last_frame != NULL, TRUE);
last_pal = gst_mve_mux_palette_from_buffer (mvemux->last_frame);
if (last_pal == NULL)
return TRUE;
return memcmp (GST_BUFFER_DATA (last_pal), GST_BUFFER_DATA (pal),
MVE_PALETTE_COUNT * 4) != 0;
}
static GstFlowReturn
gst_mve_mux_push_buffer (GstMveMux * mvemux, GstBuffer * buffer)
{
GST_BUFFER_OFFSET (buffer) = mvemux->stream_offset;
mvemux->stream_offset += GST_BUFFER_SIZE (buffer);
GST_BUFFER_OFFSET_END (buffer) = mvemux->stream_offset;
return gst_pad_push (mvemux->source, buffer);
}
/* returns TRUE if audio segment is complete */
static gboolean
gst_mve_mux_audio_data (GstMveMux * mvemux)
{
gboolean complete = FALSE;
while (!complete) {
GstBuffer *buf;
GstClockTime buftime;
GstClockTime duration;
GstClockTime t_needed;
gint b_needed;
gint len;
buf = g_queue_peek_head (mvemux->audio_buffer);
if (buf == NULL)
return (mvemux->audio_pad_eos && mvemux->chunk_audio) ||
(mvemux->stream_time + mvemux->frame_duration < mvemux->max_ts);
buftime = GST_BUFFER_TIMESTAMP (buf);
duration = GST_BUFFER_DURATION (buf);
/* FIXME: adjust buffer timestamps using segment info */
/* assume continuous buffers on invalid time stamps */
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (buftime)))
buftime = mvemux->next_ts;
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (duration)))
duration = gst_util_uint64_scale_int (mvemux->frame_duration,
GST_BUFFER_SIZE (buf), mvemux->spf);
if (mvemux->chunk_audio) {
b_needed = mvemux->spf - mvemux->chunk_audio->len;
t_needed = (gint) gst_util_uint64_scale_int (mvemux->frame_duration,
b_needed, mvemux->spf);
} else {
b_needed = mvemux->spf;
t_needed = mvemux->frame_duration;
}
if (buftime > mvemux->next_ts + t_needed) {
/* future buffer - fill chunk with silence */
GST_DEBUG_OBJECT (mvemux, "future buffer, inserting silence");
/* if we already have a chunk started, fill it
otherwise we'll simply insert a silence chunk */
if (mvemux->chunk_audio) {
len = mvemux->chunk_audio->len;
g_byte_array_set_size (mvemux->chunk_audio, mvemux->spf);
memset (mvemux->chunk_audio->data + len, 0, mvemux->spf - len);
}
mvemux->next_ts += t_needed;
complete = TRUE;
} else if (buftime + duration <= mvemux->next_ts) {
/* past buffer - drop */
GST_DEBUG_OBJECT (mvemux, "dropping past buffer");
g_queue_pop_head (mvemux->audio_buffer);
gst_buffer_unref (buf);
} else {
/* our data starts somewhere in this buffer */
const guint8 *bufdata = GST_BUFFER_DATA (buf);
gint b_available = GST_BUFFER_SIZE (buf);
gint align = (mvemux->bps / 8) * mvemux->channels - 1;
gint offset;
if (mvemux->chunk_audio == NULL)
mvemux->chunk_audio = g_byte_array_sized_new (mvemux->spf);
if (buftime >= mvemux->next_ts) {
/* insert silence as necessary */
len = mvemux->chunk_audio->len;
offset = (gint) gst_util_uint64_scale_int (mvemux->spf,
buftime - mvemux->next_ts, mvemux->frame_duration);
offset = (offset + align) & ~align;
if (len < offset) {
g_byte_array_set_size (mvemux->chunk_audio, offset);
memset (mvemux->chunk_audio->data + len, 0, offset - len);
b_needed -= offset - len;
mvemux->next_ts += gst_util_uint64_scale_int (mvemux->frame_duration,
offset - len, mvemux->spf);
}
offset = 0;
} else {
offset = (gint) gst_util_uint64_scale_int (mvemux->spf,
mvemux->next_ts - buftime, mvemux->frame_duration);
offset = (offset + align) & ~align;
}
g_assert (offset <= b_available);
bufdata += offset;
b_available -= offset;
if (b_needed > b_available)
b_needed = b_available;
if (mvemux->bps == 8) {
g_byte_array_append (mvemux->chunk_audio, bufdata, b_needed);
} else {
guint i;
gint16 *sample = (gint16 *) bufdata;
guint8 s[2];
len = b_needed / 2;
for (i = 0; i < len; ++i) {
s[0] = (*sample) & 0x00FF;
s[1] = ((*sample) & 0xFF00) >> 8;
g_byte_array_append (mvemux->chunk_audio, s, 2);
++sample;
}
}
mvemux->next_ts += gst_util_uint64_scale_int (mvemux->frame_duration,
b_needed, mvemux->spf);
if (b_available - b_needed == 0) {
/* consumed buffer */
GST_LOG_OBJECT (mvemux, "popping consumed buffer");
g_queue_pop_head (mvemux->audio_buffer);
gst_buffer_unref (buf);
}
complete = (mvemux->chunk_audio->len >= mvemux->spf);
}
if (mvemux->max_ts < mvemux->next_ts)
mvemux->max_ts = mvemux->next_ts;
}
return complete;
}
static GstFlowReturn
gst_mve_mux_start_movie (GstMveMux * mvemux)
{
GstFlowReturn res;
GstBuffer *buf;
GST_DEBUG_OBJECT (mvemux, "writing movie preamble");
res = gst_pad_alloc_buffer (mvemux->source, 0,
MVE_PREAMBLE_SIZE, GST_PAD_CAPS (mvemux->source), &buf);
if (res != GST_FLOW_OK)
return res;
gst_pad_push_event (mvemux->source,
gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0));
memcpy (GST_BUFFER_DATA (buf), mve_preamble, MVE_PREAMBLE_SIZE);
return gst_mve_mux_push_buffer (mvemux, buf);
}
static GstFlowReturn
gst_mve_mux_end_movie (GstMveMux * mvemux)
{
GstFlowReturn res;
GstBuffer *buf;
guint8 *bufdata;
GST_DEBUG_OBJECT (mvemux, "writing movie shutdown chunk");
res = gst_pad_alloc_buffer (mvemux->source, 0, 16,
GST_PAD_CAPS (mvemux->source), &buf);
if (res != GST_FLOW_OK)
return res;
bufdata = GST_BUFFER_DATA (buf);
GST_WRITE_UINT16_LE (bufdata, 8); /* shutdown chunk */
GST_WRITE_UINT16_LE (bufdata + 2, MVE_CHUNK_SHUTDOWN);
GST_WRITE_UINT16_LE (bufdata + 4, 0); /* end movie segment */
bufdata[6] = MVE_OC_END_OF_STREAM;
bufdata[7] = 0;
GST_WRITE_UINT16_LE (bufdata + 8, 0); /* end chunk segment */
bufdata[10] = MVE_OC_END_OF_CHUNK;
bufdata[11] = 0;
GST_WRITE_UINT16_LE (bufdata + 12, 0); /* end movie chunk */
GST_WRITE_UINT16_LE (bufdata + 14, MVE_CHUNK_END);
return gst_mve_mux_push_buffer (mvemux, buf);
}
static GstFlowReturn
gst_mve_mux_init_video_chunk (GstMveMux * mvemux, const GstBuffer * pal)
{
GstFlowReturn res;
GstBuffer *buf;
guint8 *bufdata;
guint16 buf_size;
guint16 first_col = 0, last_col = 0;
guint pal_size = 0;
GST_DEBUG_OBJECT (mvemux, "init-video chunk w:%d, h:%d, bpp:%d",
mvemux->width, mvemux->height, mvemux->bpp);
buf_size = 4; /* chunk header */
buf_size += 4 + 6; /* init video mode segment */
buf_size += 4 + 8; /* create video buffers segment */
if (mvemux->bpp == 8) {
g_return_val_if_fail (pal != NULL, GST_FLOW_ERROR);
/* install palette segment */
gst_mve_mux_palette_analyze (mvemux, pal, &first_col, &last_col);
pal_size = (last_col - first_col + 1) * 3;
buf_size += 4 + 4 + pal_size;
}
buf_size += 4 + 0; /* end chunk segment */
res = gst_pad_alloc_buffer (mvemux->source, 0, buf_size,
GST_PAD_CAPS (mvemux->source), &buf);
if (res != GST_FLOW_OK)
return res;
bufdata = GST_BUFFER_DATA (buf);
GST_WRITE_UINT16_LE (bufdata, buf_size - 4);
GST_WRITE_UINT16_LE (bufdata + 2, MVE_CHUNK_INIT_VIDEO);
GST_WRITE_UINT16_LE (bufdata + 4, 6);
bufdata[6] = MVE_OC_VIDEO_MODE;
bufdata[7] = 0;
GST_WRITE_UINT16_LE (bufdata + 8, mvemux->screen_width); /* screen width */
GST_WRITE_UINT16_LE (bufdata + 10, mvemux->screen_height); /* screen height */
GST_WRITE_UINT16_LE (bufdata + 12, 0); /* ??? - flags */
GST_WRITE_UINT16_LE (bufdata + 14, 8);
bufdata[16] = MVE_OC_VIDEO_BUFFERS;
bufdata[17] = 2;
GST_WRITE_UINT16_LE (bufdata + 18, mvemux->width >> 3); /* buffer width */
GST_WRITE_UINT16_LE (bufdata + 20, mvemux->height >> 3); /* buffer height */
GST_WRITE_UINT16_LE (bufdata + 22, 1); /* buffer count */
GST_WRITE_UINT16_LE (bufdata + 24, (mvemux->bpp >> 3) - 1); /* true color */
bufdata += 26;
if (mvemux->bpp == 8) {
/* TODO: check whether we really need to update the entire palette (or at all) */
gint i;
guint32 *col;
GST_DEBUG_OBJECT (mvemux, "installing palette");
GST_WRITE_UINT16_LE (bufdata, 4 + pal_size);
bufdata[2] = MVE_OC_PALETTE;
bufdata[3] = 0;
GST_WRITE_UINT16_LE (bufdata + 4, first_col); /* first color index */
GST_WRITE_UINT16_LE (bufdata + 6, last_col - first_col + 1); /* number of colors */
bufdata += 8;
col = (guint32 *) GST_BUFFER_DATA (pal);
for (i = first_col; i <= last_col; ++i) {
/* convert from 8-bit palette to 6-bit VGA */
guint32 rgb = col[i];
(*bufdata) = ((rgb & 0x00FF0000) >> 16) >> 2;
++bufdata;
(*bufdata) = ((rgb & 0x0000FF00) >> 8) >> 2;
++bufdata;
(*bufdata) = (rgb & 0x000000FF) >> 2;
++bufdata;
}
mvemux->pal_changed = TRUE;
mvemux->pal_first_color = first_col;
mvemux->pal_colors = last_col - first_col + 1;
}
GST_WRITE_UINT16_LE (bufdata, 0);
bufdata[2] = MVE_OC_END_OF_CHUNK;
bufdata[3] = 0;
return gst_mve_mux_push_buffer (mvemux, buf);
}
static GstFlowReturn
gst_mve_mux_init_audio_chunk (GstMveMux * mvemux)
{
GstFlowReturn res;
GstBuffer *buf;
guint16 buf_size;
guint8 *bufdata;
guint16 flags = 0;
gint align;
GST_DEBUG_OBJECT (mvemux,
"init-audio chunk rate:%d, chan:%d, bps:%d, comp:%d", mvemux->rate,
mvemux->channels, mvemux->bps, mvemux->compression);
if (G_UNLIKELY (mvemux->bps == 8 && mvemux->compression)) {
GST_INFO_OBJECT (mvemux,
"compression only supported for 16-bit samples, disabling");
mvemux->compression = FALSE;
}
/* calculate sample data per frame */
align = (mvemux->bps / 8) * mvemux->channels;
mvemux->spf =
(guint16) (gst_util_uint64_scale_int (align * mvemux->rate,
mvemux->frame_duration, GST_SECOND) + align - 1) & ~(align - 1);
/* prebuffer approx. 1 second of audio data */
mvemux->lead_frames = align * mvemux->rate / mvemux->spf;
GST_DEBUG_OBJECT (mvemux, "calculated spf:%d, lead frames:%d",
mvemux->spf, mvemux->lead_frames);
/* chunk header + init video mode segment + end chunk segment */
buf_size = 4 + (4 + 10) + 4;
res = gst_pad_alloc_buffer (mvemux->source, 0, buf_size,
GST_PAD_CAPS (mvemux->source), &buf);
if (res != GST_FLOW_OK)
return res;
bufdata = GST_BUFFER_DATA (buf);
if (mvemux->channels == 2)
flags |= MVE_AUDIO_STEREO;
if (mvemux->bps == 16)
flags |= MVE_AUDIO_16BIT;
if (mvemux->compression)
flags |= MVE_AUDIO_COMPRESSED;
GST_WRITE_UINT16_LE (bufdata, buf_size - 4);
GST_WRITE_UINT16_LE (bufdata + 2, MVE_CHUNK_INIT_AUDIO);
GST_WRITE_UINT16_LE (bufdata + 4, 10);
bufdata[6] = MVE_OC_AUDIO_BUFFERS;
bufdata[7] = 1;
GST_WRITE_UINT16_LE (bufdata + 8, 0); /* ??? */
GST_WRITE_UINT16_LE (bufdata + 10, flags); /* flags */
GST_WRITE_UINT16_LE (bufdata + 12, mvemux->rate); /* sample rate */
GST_WRITE_UINT32_LE (bufdata + 14, /* minimum audio buffer size */
mvemux->spf * mvemux->lead_frames);
GST_WRITE_UINT16_LE (bufdata + 18, 0);
bufdata[20] = MVE_OC_END_OF_CHUNK;
bufdata[21] = 0;
return gst_mve_mux_push_buffer (mvemux, buf);
}
static guint8 *
gst_mve_mux_write_audio_segments (GstMveMux * mvemux, guint8 * data)
{
GByteArray *chunk = mvemux->chunk_audio;
guint16 silent_mask;
GST_LOG_OBJECT (mvemux, "writing audio data");
/* audio data */
if (chunk) {
guint16 len = mvemux->compression ?
chunk->len / 2 + mvemux->channels : chunk->len;
silent_mask = 0xFFFE;
GST_WRITE_UINT16_LE (data, 6 + len);
data[2] = MVE_OC_AUDIO_DATA;
data[3] = 0;
GST_WRITE_UINT16_LE (data + 4, mvemux->audio_frames); /* frame number */
GST_WRITE_UINT16_LE (data + 6, 0x0001); /* stream mask */
GST_WRITE_UINT16_LE (data + 8, chunk->len); /* (uncompressed) data length */
data += 10;
if (mvemux->compression)
mve_compress_audio (data, chunk->data, len, mvemux->channels);
else
memcpy (data, chunk->data, chunk->len);
data += len;
g_byte_array_free (chunk, TRUE);
mvemux->chunk_audio = NULL;
} else
silent_mask = 0xFFFF;
/* audio data (silent) */
GST_WRITE_UINT16_LE (data, 6);
data[2] = MVE_OC_AUDIO_SILENCE;
data[3] = 0;
GST_WRITE_UINT16_LE (data + 4, mvemux->audio_frames++); /* frame number */
GST_WRITE_UINT16_LE (data + 6, silent_mask); /* stream mask */
GST_WRITE_UINT16_LE (data + 8, mvemux->spf); /* (imaginary) data length */
data += 10;
return data;
}
static GstFlowReturn
gst_mve_mux_prebuffer_audio_chunk (GstMveMux * mvemux)
{
GstFlowReturn ret;
GstBuffer *chunk;
guint16 size;
guint8 *data;
/* calculate chunk size */
size = 4; /* chunk header */
if (mvemux->chunk_audio) {
size += 4 + 6 + /* audio data */
(mvemux->compression ?
mvemux->chunk_audio->len / 2 + mvemux->channels :
mvemux->chunk_audio->len);
}
size += 4 + 6; /* audio data silent */
size += 4; /* end chunk */
ret = gst_pad_alloc_buffer (mvemux->source, 0, size,
GST_PAD_CAPS (mvemux->source), &chunk);
if (ret != GST_FLOW_OK)
return ret;
data = GST_BUFFER_DATA (chunk);
/* assemble chunk */
GST_WRITE_UINT16_LE (data, size - 4);
GST_WRITE_UINT16_LE (data + 2, MVE_CHUNK_AUDIO_ONLY);
data += 4;
data = gst_mve_mux_write_audio_segments (mvemux, data);
/* end chunk */
GST_WRITE_UINT16_LE (data, 0);
data[2] = MVE_OC_END_OF_CHUNK;
data[3] = 0;
if (mvemux->audio_frames >= mvemux->lead_frames)
mvemux->state = MVE_MUX_STATE_MOVIE;
mvemux->stream_time += mvemux->frame_duration;
GST_DEBUG_OBJECT (mvemux, "pushing audio chunk");
return gst_mve_mux_push_buffer (mvemux, chunk);
}
static GstFlowReturn
gst_mve_mux_push_chunk (GstMveMux * mvemux)
{
GstFlowReturn ret;
GstBuffer *chunk;
GstBuffer *frame;
guint32 size;
guint16 cm_size = 0;
guint8 *data;
/* calculate chunk size */
size = 4; /* chunk header */
if (G_UNLIKELY (mvemux->timer == 0)) {
/* we need to insert a timer segment */
size += 4 + 6;
}
if (mvemux->audio_pad_connected) {
if (mvemux->chunk_audio) {
size += 4 + 6 + /* audio data */
(mvemux->compression ?
mvemux->chunk_audio->len / 2 + mvemux->channels :
mvemux->chunk_audio->len);
}
size += 4 + 6; /* audio data silent */
}
size += 4 + 6; /* play video */
size += 4; /* play audio; present even if no audio stream */
size += 4; /* end chunk */
/* we must encode video only after we have the audio side
covered, since only then we can tell what size limit
the video data must adhere to */
frame = g_queue_pop_head (mvemux->video_buffer);
if (frame != NULL) {
cm_size = (((mvemux->width * mvemux->height) >> 6) + 1) >> 1;
size += 4 + cm_size; /* code map */
size += 4 + 14; /* video data header */
/* make sure frame is writable since the encoder may want to modify it */
frame = gst_buffer_make_writable (frame);
if (mvemux->bpp == 8) {
const GstBuffer *pal = gst_mve_mux_palette_from_buffer (frame);
if (pal == NULL)
ret = GST_FLOW_ERROR;
else
ret = mve_encode_frame8 (mvemux, frame,
(guint32 *) GST_BUFFER_DATA (pal), G_MAXUINT16 - size);
} else
ret = mve_encode_frame16 (mvemux, frame, G_MAXUINT16 - size);
if (mvemux->second_last_frame != NULL)
gst_buffer_unref (mvemux->second_last_frame);
mvemux->second_last_frame = mvemux->last_frame;
mvemux->last_frame = frame;
if (ret != GST_FLOW_OK)
return ret;
size += mvemux->chunk_video->len;
}
if (size > G_MAXUINT16) {
GST_ELEMENT_ERROR (mvemux, STREAM, ENCODE, (NULL),
("encoding frame %d failed: maximum block size exceeded (%u)",
mvemux->video_frames + 1, size));
return GST_FLOW_ERROR;
}
ret = gst_pad_alloc_buffer (mvemux->source, 0, size,
GST_PAD_CAPS (mvemux->source), &chunk);
if (ret != GST_FLOW_OK)
return ret;
data = GST_BUFFER_DATA (chunk);
/* assemble chunk */
GST_WRITE_UINT16_LE (data, size - 4);
GST_WRITE_UINT16_LE (data + 2, MVE_CHUNK_VIDEO);
data += 4;
if (G_UNLIKELY (mvemux->timer == 0)) {
/* insert a timer segment */
mvemux->timer = mvemux->frame_duration / GST_USECOND / 8;
GST_WRITE_UINT16_LE (data, 6);
data[2] = MVE_OC_CREATE_TIMER;
data[3] = 0;
GST_WRITE_UINT32_LE (data + 4, mvemux->timer); /* timer rate */
GST_WRITE_UINT16_LE (data + 8, 8); /* timer subdivision */
data += 10;
}
/* code map */
if (mvemux->chunk_video) {
GST_WRITE_UINT16_LE (data, cm_size);
data[2] = MVE_OC_CODE_MAP;
data[3] = 0;
memcpy (data + 4, mvemux->chunk_code_map, cm_size);
data += 4 + cm_size;
}
if (mvemux->audio_pad_connected)
data = gst_mve_mux_write_audio_segments (mvemux, data);
if (mvemux->chunk_video) {
GST_LOG_OBJECT (mvemux, "writing video data");
/* video data */
GST_WRITE_UINT16_LE (data, 14 + mvemux->chunk_video->len);
data[2] = MVE_OC_VIDEO_DATA;
data[3] = 0;
GST_WRITE_UINT16_LE (data + 6, mvemux->video_frames); /* previous frame */
GST_WRITE_UINT16_LE (data + 4, ++mvemux->video_frames); /* current frame */
GST_WRITE_UINT16_LE (data + 8, 0); /* x offset */
GST_WRITE_UINT16_LE (data + 10, 0); /* y offset */
GST_WRITE_UINT16_LE (data + 12, mvemux->width >> 3); /* buffer width */
GST_WRITE_UINT16_LE (data + 14, mvemux->height >> 3); /* buffer height */
GST_WRITE_UINT16_LE (data + 16, /* flags */
(mvemux->video_frames == 1 ? 0 : MVE_VIDEO_DELTA_FRAME));
memcpy (data + 18, mvemux->chunk_video->data, mvemux->chunk_video->len);
data += 18 + mvemux->chunk_video->len;
g_byte_array_free (mvemux->chunk_video, TRUE);
mvemux->chunk_video = NULL;
}
/* play audio */
GST_WRITE_UINT16_LE (data, 0);
data[2] = MVE_OC_PLAY_AUDIO;
data[3] = 0;
data += 4;
/* play video */
GST_WRITE_UINT16_LE (data, 6);
data[2] = MVE_OC_PLAY_VIDEO;
data[3] = 1;
/* this block is only set to non-zero on palette changes in 8-bit mode */
if (mvemux->pal_changed) {
GST_WRITE_UINT16_LE (data + 4, mvemux->pal_first_color); /* index of first color */
GST_WRITE_UINT16_LE (data + 6, mvemux->pal_colors); /* number of colors */
mvemux->pal_changed = FALSE;
} else {
GST_WRITE_UINT32_LE (data + 4, 0);
}
GST_WRITE_UINT16_LE (data + 8, 0); /* ??? */
data += 10;
/* end chunk */
GST_WRITE_UINT16_LE (data, 0);
data[2] = MVE_OC_END_OF_CHUNK;
data[3] = 0;
mvemux->chunk_has_palette = FALSE;
mvemux->chunk_has_audio = FALSE;
mvemux->stream_time += mvemux->frame_duration;
GST_LOG_OBJECT (mvemux, "pushing video chunk");
return gst_mve_mux_push_buffer (mvemux, chunk);
}
static GstFlowReturn
gst_mve_mux_chain (GstPad * sinkpad, GstBuffer * inbuf)
{
GstMveMux *mvemux = GST_MVE_MUX (GST_PAD_PARENT (sinkpad));
GstFlowReturn ret = GST_FLOW_OK;
const GstBuffer *palette;
gboolean audio_ok, video_ok;
/* need to serialize the buffers */
g_mutex_lock (mvemux->lock);
if (G_LIKELY (inbuf != NULL)) { /* TODO: see _sink_event... */
if (sinkpad == mvemux->audiosink)
g_queue_push_tail (mvemux->audio_buffer, inbuf);
else if (sinkpad == mvemux->videosink)
g_queue_push_tail (mvemux->video_buffer, inbuf);
else
g_assert_not_reached ();
}
/* TODO: this is gross... */
if (G_UNLIKELY (mvemux->state == MVE_MUX_STATE_INITIAL)) {
GST_DEBUG_OBJECT (mvemux, "waiting for caps");
goto done;
}
/* now actually try to mux something */
if (G_UNLIKELY (mvemux->state == MVE_MUX_STATE_CONNECTED)) {
palette = NULL;
if (mvemux->bpp == 8) {
/* we need to add palette info to the init chunk */
if (g_queue_is_empty (mvemux->video_buffer))
goto done; /* wait for more data */
ret = gst_mve_mux_palette_from_current_frame (mvemux, &palette);
if (ret != GST_FLOW_OK)
goto done;
}
gst_mve_mux_start_movie (mvemux);
gst_mve_mux_init_video_chunk (mvemux, palette);
mvemux->chunk_has_palette = TRUE;
if (mvemux->audio_pad_connected) {
gst_mve_mux_init_audio_chunk (mvemux);
mvemux->state = MVE_MUX_STATE_PREBUFFER;
} else
mvemux->state = MVE_MUX_STATE_MOVIE;
}
while ((mvemux->state == MVE_MUX_STATE_PREBUFFER) && (ret == GST_FLOW_OK) &&
gst_mve_mux_audio_data (mvemux)) {
ret = gst_mve_mux_prebuffer_audio_chunk (mvemux);
}
if (G_LIKELY (mvemux->state >= MVE_MUX_STATE_MOVIE)) {
audio_ok = !mvemux->audio_pad_connected ||
!g_queue_is_empty (mvemux->audio_buffer) ||
(mvemux->audio_pad_eos && (mvemux->stream_time <= mvemux->max_ts));
video_ok = !g_queue_is_empty (mvemux->video_buffer) ||
(mvemux->video_pad_eos &&
(!mvemux->audio_pad_eos || (mvemux->stream_time <= mvemux->max_ts)));
while ((ret == GST_FLOW_OK) && audio_ok && video_ok) {
if (!g_queue_is_empty (mvemux->video_buffer)) {
if ((mvemux->bpp == 8) && !mvemux->chunk_has_palette) {
ret = gst_mve_mux_palette_from_current_frame (mvemux, &palette);
if (ret != GST_FLOW_OK)
goto done;
if (gst_mve_mux_palette_changed (mvemux, palette))
gst_mve_mux_init_video_chunk (mvemux, palette);
mvemux->chunk_has_palette = TRUE;
}
}
/* audio data */
if (mvemux->audio_pad_connected && !mvemux->chunk_has_audio &&
gst_mve_mux_audio_data (mvemux))
mvemux->chunk_has_audio = TRUE;
if ((!g_queue_is_empty (mvemux->video_buffer) || mvemux->video_pad_eos) &&
(mvemux->chunk_has_audio || !mvemux->audio_pad_connected
|| mvemux->audio_pad_eos)) {
ret = gst_mve_mux_push_chunk (mvemux);
}
audio_ok = !mvemux->audio_pad_connected ||
!g_queue_is_empty (mvemux->audio_buffer) ||
(mvemux->audio_pad_eos && (mvemux->stream_time <= mvemux->max_ts));
video_ok = !g_queue_is_empty (mvemux->video_buffer) ||
(mvemux->video_pad_eos &&
(!mvemux->audio_pad_eos || (mvemux->stream_time <= mvemux->max_ts)));
}
}
if (G_UNLIKELY ((mvemux->state == MVE_MUX_STATE_EOS) && (ret == GST_FLOW_OK))) {
ret = gst_mve_mux_end_movie (mvemux);
gst_pad_push_event (mvemux->source, gst_event_new_eos ());
}
done:
g_mutex_unlock (mvemux->lock);
return ret;
}
static gboolean
gst_mve_mux_sink_event (GstPad * pad, GstEvent * event)
{
gboolean res = TRUE;
GstMveMux *mvemux = GST_MVE_MUX (GST_PAD_PARENT (pad));
GST_DEBUG_OBJECT (mvemux, "got %s event for pad %s",
GST_EVENT_TYPE_NAME (event), GST_PAD_NAME (pad));
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_EOS:
if (pad == mvemux->audiosink) {
mvemux->audio_pad_eos = TRUE;
if (mvemux->state == MVE_MUX_STATE_PREBUFFER)
mvemux->state = MVE_MUX_STATE_MOVIE;
} else if (pad == mvemux->videosink)
mvemux->video_pad_eos = TRUE;
/* TODO: this is evil */
if (mvemux->audio_pad_eos && mvemux->video_pad_eos) {
mvemux->state = MVE_MUX_STATE_EOS;
gst_mve_mux_chain (pad, NULL);
}
gst_event_unref (event);
break;
case GST_EVENT_NEWSEGMENT:
if (pad == mvemux->audiosink) {
GstFormat format;
gint64 start;
gboolean update;
gst_event_parse_new_segment (event, &update, NULL, &format, &start,
NULL, NULL);
if ((format == GST_FORMAT_TIME) && update && (start > mvemux->max_ts))
mvemux->max_ts = start;
}
gst_event_unref (event);
break;
default:
res = gst_pad_event_default (pad, event);
break;
}
return res;
}
static gboolean
gst_mve_mux_vidsink_set_caps (GstPad * pad, GstCaps * vscaps)
{
GstMveMux *mvemux;
GstStructure *structure;
GstClockTime duration;
const GValue *fps;
gint w, h, bpp;
gboolean ret;
mvemux = GST_MVE_MUX (GST_PAD_PARENT (pad));
GST_DEBUG_OBJECT (mvemux, "video set_caps triggered on %s",
GST_PAD_NAME (pad));
structure = gst_caps_get_structure (vscaps, 0);
ret = gst_structure_get_int (structure, "width", &w);
ret &= gst_structure_get_int (structure, "height", &h);
ret &= gst_structure_get_int (structure, "bpp", &bpp);
fps = gst_structure_get_value (structure, "framerate");
ret &= (fps != NULL && GST_VALUE_HOLDS_FRACTION (fps));
duration = gst_util_uint64_scale_int (GST_SECOND,
gst_value_get_fraction_denominator (fps),
gst_value_get_fraction_numerator (fps));
if (!ret)
return FALSE;
/* don't allow changing width, height, bpp, or framerate */
if (mvemux->state != MVE_MUX_STATE_INITIAL) {
if (mvemux->width != w || mvemux->height != h ||
mvemux->bpp != bpp || mvemux->frame_duration != duration) {
GST_ERROR_OBJECT (mvemux, "caps renegotiation not allowed");
return FALSE;
}
} else {
if (w % 8 != 0 || h % 8 != 0) {
GST_ERROR_OBJECT (mvemux, "width and height must be multiples of 8");
return FALSE;
}
mvemux->width = w;
mvemux->height = h;
mvemux->bpp = bpp;
mvemux->frame_duration = duration;
if (mvemux->screen_width < w) {
GST_INFO_OBJECT (mvemux, "setting suggested screen width to %d", w);
mvemux->screen_width = w;
}
if (mvemux->screen_height < h) {
GST_INFO_OBJECT (mvemux, "setting suggested screen height to %d", h);
mvemux->screen_height = h;
}
g_free (mvemux->chunk_code_map);
mvemux->chunk_code_map = g_malloc ((((w * h) >> 6) + 1) >> 1);
/* audio caps already initialized? */
if (mvemux->bps != 0 || !mvemux->audio_pad_connected)
mvemux->state = MVE_MUX_STATE_CONNECTED;
}
return TRUE;
}
static gboolean
gst_mve_mux_audsink_set_caps (GstPad * pad, GstCaps * ascaps)
{
GstMveMux *mvemux;
GstStructure *structure;
gboolean ret;
gint val;
mvemux = GST_MVE_MUX (GST_PAD_PARENT (pad));
GST_DEBUG_OBJECT (mvemux, "audio set_caps triggered on %s",
GST_PAD_NAME (pad));
/* don't allow caps renegotiation for now */
if (mvemux->state != MVE_MUX_STATE_INITIAL)
return FALSE;
structure = gst_caps_get_structure (ascaps, 0);
ret = gst_structure_get_int (structure, "channels", &val);
mvemux->channels = val;
ret &= gst_structure_get_int (structure, "rate", &val);
mvemux->rate = val;
ret &= gst_structure_get_int (structure, "width", &val);
mvemux->bps = val;
/* video caps already initialized? */
if (mvemux->bpp != 0)
mvemux->state = MVE_MUX_STATE_CONNECTED;
return ret;
}
static GstPad *
gst_mve_mux_request_new_pad (GstElement * element,
GstPadTemplate * templ, const gchar * req_name)
{
GstMveMux *mvemux = GST_MVE_MUX (element);
GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
GstPad *pad;
g_return_val_if_fail (templ != NULL, NULL);
if (templ->direction != GST_PAD_SINK) {
GST_WARNING_OBJECT (mvemux, "request pad is not a SINK pad");
return NULL;
}
if (templ == gst_element_class_get_pad_template (klass, "audio")) {
if (mvemux->audiosink)
return NULL;
mvemux->audiosink = gst_pad_new_from_template (templ, "audio");
gst_pad_set_setcaps_function (mvemux->audiosink,
GST_DEBUG_FUNCPTR (gst_mve_mux_audsink_set_caps));
mvemux->audio_pad_eos = FALSE;
pad = mvemux->audiosink;
} else if (templ == gst_element_class_get_pad_template (klass, "video")) {
if (mvemux->videosink)
return NULL;
mvemux->videosink = gst_pad_new_from_template (templ, "video");
gst_pad_set_setcaps_function (mvemux->videosink,
GST_DEBUG_FUNCPTR (gst_mve_mux_vidsink_set_caps));
mvemux->video_pad_eos = FALSE;
pad = mvemux->videosink;
} else {
g_return_val_if_reached (NULL);
}
gst_pad_set_chain_function (pad, GST_DEBUG_FUNCPTR (gst_mve_mux_chain));
gst_pad_set_event_function (pad, GST_DEBUG_FUNCPTR (gst_mve_mux_sink_event));
g_signal_connect (pad, "linked", G_CALLBACK (gst_mve_mux_pad_link), mvemux);
g_signal_connect (pad, "unlinked", G_CALLBACK (gst_mve_mux_pad_unlink),
mvemux);
gst_element_add_pad (element, pad);
return pad;
}
static void
gst_mve_mux_release_pad (GstElement * element, GstPad * pad)
{
GstMveMux *mvemux = GST_MVE_MUX (element);
gst_element_remove_pad (element, pad);
if (pad == mvemux->audiosink) {
mvemux->audiosink = NULL;
mvemux->audio_pad_connected = FALSE;
} else if (pad == mvemux->videosink) {
mvemux->videosink = NULL;
mvemux->video_pad_connected = FALSE;
}
}
static void
gst_mve_mux_base_init (GstMveMuxClass * klass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
gst_element_class_add_static_pad_template (element_class, &src_factory);
gst_element_class_add_static_pad_template (element_class,
&audio_sink_factory);
gst_element_class_add_static_pad_template (element_class,
&video_sink_factory);
gst_element_class_set_static_metadata (element_class, "MVE Multiplexer",
"Codec/Muxer",
"Muxes audio and video into an MVE stream",
"Jens Granseuer <jensgr@gmx.net>");
}
static void
gst_mve_mux_finalize (GObject * object)
{
GstMveMux *mvemux = GST_MVE_MUX (object);
if (mvemux->lock) {
g_mutex_free (mvemux->lock);
mvemux->lock = NULL;
}
if (mvemux->audio_buffer) {
g_queue_free (mvemux->audio_buffer);
mvemux->audio_buffer = NULL;
}
if (mvemux->video_buffer) {
g_queue_free (mvemux->video_buffer);
mvemux->video_buffer = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_mve_mux_class_init (GstMveMuxClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
gobject_class = G_OBJECT_CLASS (klass);
gstelement_class = GST_ELEMENT_CLASS (klass);
parent_class = g_type_class_peek_parent (klass);
gobject_class->finalize = gst_mve_mux_finalize;
gobject_class->get_property = gst_mve_mux_get_property;
gobject_class->set_property = gst_mve_mux_set_property;
g_object_class_install_property (gobject_class, PROP_AUDIO_COMPRESSION,
g_param_spec_boolean ("compression", "Audio compression",
"Whether to compress audio data", MVE_MUX_DEFAULT_COMPRESSION,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_VIDEO_QUICK_ENCODING,
g_param_spec_boolean ("quick", "Quick encoding",
"Whether to disable expensive encoding operations", TRUE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_VIDEO_SCREEN_WIDTH,
g_param_spec_uint ("screen-width", "Screen width",
"Suggested screen width", 320, 1600,
MVE_MUX_DEFAULT_SCREEN_WIDTH,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_VIDEO_SCREEN_HEIGHT,
g_param_spec_uint ("screen-height", "Screen height",
"Suggested screen height", 200, 1200,
MVE_MUX_DEFAULT_SCREEN_HEIGHT,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gstelement_class->request_new_pad = gst_mve_mux_request_new_pad;
gstelement_class->release_pad = gst_mve_mux_release_pad;
gstelement_class->change_state = gst_mve_mux_change_state;
}
static void
gst_mve_mux_init (GstMveMux * mvemux)
{
GstElementClass *klass = GST_ELEMENT_GET_CLASS (mvemux);
mvemux->source =
gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
"src"), "src");
gst_element_add_pad (GST_ELEMENT (mvemux), mvemux->source);
mvemux->lock = g_mutex_new ();
mvemux->audiosink = NULL;
mvemux->videosink = NULL;
mvemux->audio_pad_connected = FALSE;
mvemux->video_pad_connected = FALSE;
/* audio/video metadata initialisation */
mvemux->last_frame = NULL;
mvemux->second_last_frame = NULL;
mvemux->chunk_code_map = NULL;
mvemux->chunk_video = NULL;
mvemux->chunk_audio = NULL;
mvemux->audio_buffer = NULL;
mvemux->video_buffer = NULL;
gst_mve_mux_reset (mvemux);
}
GType
gst_mve_mux_get_type (void)
{
static GType mvemux_type = 0;
if (!mvemux_type) {
static const GTypeInfo mvemux_info = {
sizeof (GstMveMuxClass),
(GBaseInitFunc) gst_mve_mux_base_init,
NULL,
(GClassInitFunc) gst_mve_mux_class_init,
NULL,
NULL,
sizeof (GstMveMux),
0,
(GInstanceInitFunc) gst_mve_mux_init,
};
GST_DEBUG_CATEGORY_INIT (mvemux_debug, "mvemux",
0, "Interplay MVE movie muxer");
mvemux_type =
g_type_register_static (GST_TYPE_ELEMENT, "GstMveMux", &mvemux_info, 0);
}
return mvemux_type;
}