| /* 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; |
| } |