/* GStreamer * Copyright (C) <2006> Renato Araujo Oliveira Filho * Rosfran Borges * * 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. */ /* Element-Checklist-Version: 5 */ /** * SECTION:element-nuvdemux * * * * Demuxes an .nuv file into raw or compressed audio and/or video streams. * * * This element currently only supports pull-based scheduling. * * Example launch line * * * gst-launch filesrc test.nuv ! nuvdemux name=demux demux.audio_00 ! decodebin ! audioconvert ! audioresample ! autoaudiosink demux.video_00 ! queue ! decodebin ! ffmpegcolorspace ! videoscale ! autovideosink * * Play (parse and decode) an .nuv file and try to output it to * an automatically detected soundcard and videosink. If the NUV file contains * compressed audio or video data, this will only work if you have the * right decoder elements/plugins installed. * * * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include "glib/gi18n.h" #include "gstnuvdemux.h" #define GST_NUV_DEMUX_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_NUV_DEMUX, GstNuvDemuxPrivate)) GST_DEBUG_CATEGORY_STATIC (nuvdemux_debug); #define GST_CAT_DEFAULT nuvdemux_debug #define GST_FLOW_ERROR_NO_DATA -101 #define GST_FLOW_ERROR_EOS -102 enum { NUV_PUSH_MODE = 0, NUV_PULL_MODE }; GST_DEBUG_CATEGORY_EXTERN (GST_CAT_EVENT); static const GstElementDetails gst_nuv_demux_details = GST_ELEMENT_DETAILS ("Nuv demuxer", "Codec/Demuxer", "Demultiplex a .nuv file into audio and video", "Renato Araujo Oliveira Filho ," "Rosfran Borges "); /* file header */ typedef struct { gchar id[12]; /* "NuppelVideo\0" or "MythTVVideo\0" */ gchar version[5]; /* "x.xx\0" */ gint i_width; gint i_height; gint i_width_desired; gint i_height_desired; gchar i_mode; /* P progressive, I interlaced */ gdouble d_aspect; /* 1.0 squared pixel */ gdouble d_fps; //fps num/denom gint i_fpsn; gint i_fpsd; gint i_video_blocks; /* 0 no video, -1 unknown */ gint i_audio_blocks; gint i_text_blocks; gint i_keyframe_distance; } nuv_header; /* frame header */ typedef struct { gchar i_type; /* A: audio, V: video, S: sync; T: test R: Seekpoint (string:RTjjjjjjjj) D: Extra data for codec */ gchar i_compression; /* V: 0 uncompressed 1 RTJpeg 2 RTJpeg+lzo N black frame L copy last A: 0 uncompressed (44100 1-bits, 2ch) 1 lzo 2 layer 2 3 layer 3 F flac S shorten N null frame loudless L copy last S: B audio and vdeo sync point A audio sync info (timecode == effective dsp frequency*100) V next video sync (timecode == next video frame num) S audio,video,text correlation */ gchar i_keyframe; /* 0 keyframe, else no no key frame */ guint8 i_filters; /* 0x01: gauss 5 pixel (8,2,2,2,2)/16 0x02: gauss 5 pixel (8,1,1,1,1)/12 0x04: cartoon filter */ gint32 i_timecode; /* ms */ gint i_length; /* V,A,T: length of following data S: length of packet correl */ } nuv_frame_header; /* FIXME Not sure of this one */ typedef struct { gint i_version; guint32 i_video_fcc; guint32 i_audio_fcc; gint i_audio_sample_rate; gint i_audio_bits_per_sample; gint i_audio_channels; gint i_audio_compression_ratio; gint i_audio_quality; gint i_rtjpeg_quality; gint i_rtjpeg_luma_filter; gint i_rtjpeg_chroma_filter; gint i_lavc_bitrate; gint i_lavc_qmin; gint i_lavc_qmax; gint i_lavc_maxqdiff; gint64 i_seekable_offset; gint64 i_keyframe_adjust_offset; } nuv_extended_header; typedef struct { gint64 timecode; gint64 offset; } frame_index_data; typedef enum { GST_NUV_DEMUX_START, GST_NUV_DEMUX_HEADER_DATA, GST_NUV_DEMUX_EXTRA_DATA, GST_NUV_DEMUX_MPEG_DATA, GST_NUV_DEMUX_EXTEND_HEADER, GST_NUV_DEMUX_EXTEND_HEADER_DATA, GST_NUV_DEMUX_INDEX_CREATE, GST_NUV_DEMUX_FRAME_HEADER, GST_NUV_DEMUX_MOVI, GST_NUV_DEMUX_INVALID_DATA } GstNuvDemuxState; struct _GstNuvDemuxPrivate { /* used for indicate the mode */ guint mode; /* used on push mode */ GstAdapter *adapter; /* pads */ GstPad *sinkpad; GstPad *src_video_pad; GstPad *src_audio_pad; /* Flow control */ GstFlowReturn last_video_return; GstFlowReturn last_audio_return; gboolean more_data; gboolean eos; /* NUV decoding state */ GstNuvDemuxState state; guint64 offset; /* duration information */ guint64 duration_bytes; guint64 duration_time; guint64 segment_stop; guint64 segment_start; /* segment control info */ gboolean new_audio_segment; gboolean new_video_segment; /* Mpeg ExtraData */ guint64 mpeg_data_size; GstBuffer *mpeg_buffer; /* Headers */ nuv_header h; nuv_extended_header eh; nuv_frame_header fh; /* anothers info */ guint64 header_lengh; gint64 time_start; gint64 time_diff; gint64 time_qos; guint64 last_frame_time; GSList *index; }; static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/x-nuv")); static GstStaticPadTemplate audio_src_template = GST_STATIC_PAD_TEMPLATE ("audio_src", GST_PAD_SRC, GST_PAD_SOMETIMES, GST_STATIC_CAPS_ANY); static GstStaticPadTemplate video_src_template = GST_STATIC_PAD_TEMPLATE ("video_src", GST_PAD_SRC, GST_PAD_SOMETIMES, GST_STATIC_CAPS_ANY); static void gst_nuv_demux_dispose (GObject * object); static void gst_nuv_demux_finalize (GObject * object); static GstStateChangeReturn gst_nuv_demux_change_state (GstElement * element, GstStateChange transition); static void gst_nuv_demux_loop (GstPad * pad); static GstFlowReturn gst_nuv_demux_chain (GstPad * pad, GstBuffer * buf); static GstFlowReturn gst_nuv_demux_play (GstPad * pad); static gboolean gst_nuv_demux_sink_activate_pull (GstPad * sinkpad, gboolean active); static gboolean gst_nuv_demux_sink_activate_push (GstPad * pad, gboolean active); static gboolean gst_nuv_demux_sink_activate (GstPad * sinkpad); //static gboolean gst_nuv_demux_sink_event (GstPad *pad, GstEvent *event); static gboolean gst_nuv_demux_srcpad_event (GstPad * pad, GstEvent * event); static frame_index_data * gst_nuv_demux_do_seek_index (GstNuvDemux *nuv, gint64 seek_pos, gint64 segment_stop, GstFormat format); static GstFlowReturn gst_nuv_demux_read_bytes (GstNuvDemux * nuv, guint64 size, gboolean move, GstBuffer ** buffer); static void gst_nuv_demux_reset (GstNuvDemux * nuv); static void gst_nuv_demux_destoy_src_pad (GstNuvDemux * nuv); static void gst_nuv_demux_send_eos (GstNuvDemux * nuv); static void gst_nuv_demux_create_seek_index (GstNuvDemux * nuv); #if (GST_VERSION_MINOR == 10) && (GST_VERSION_MICRO < 6) GstBuffer * gst_adapter_take_buffer (GstAdapter * adapter, guint nbytes); #endif GST_BOILERPLATE (GstNuvDemux, gst_nuv_demux, GstElement, GST_TYPE_ELEMENT); /****************************************************************************** * Utils function ******************************************************************************/ #if G_BYTE_ORDER == G_BIG_ENDIAN static inline gdouble _gdouble_swap_le_be (gdouble * d) { union { guint64 i; gdouble d; } u; u.d = *d; u.i = GUINT64_SWAP_LE_BE (u.i); return u.d; } #define READ_DOUBLE_FROM_LE(d) (_gdouble_swap_le_be((gdouble* ) d)) #else /* G_BYTE_ORDER != G_BIG_ENDIAN */ #define READ_DOUBLE_FROM_LE(d) *((gdouble* ) (d)) #endif /* G_BYTE_ORDER != G_BIG_ENDIAN */ static void double2fraction (double in, int *num, int *denom) { if (in == 29.97) { *num = 30000; *denom = 1001; } else if (in == 23.976) { *num = 24000; *denom = 1001; } else { *denom = 1; while (in - floor(in) >= 0.1) { *denom *= 10; in *= 10.0; } *num = (int)floor(in); } } /* GObject Functions */ static void gst_nuv_demux_base_init (gpointer klass) { GstElementClass *element_class = GST_ELEMENT_CLASS (klass); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&audio_src_template)); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&video_src_template)); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&sink_template)); gst_element_class_set_details (element_class, &gst_nuv_demux_details); } static void gst_nuv_demux_class_init (GstNuvDemuxClass * klass) { GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); GObjectClass *gobject_class = (GObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (nuvdemux_debug, "nuvdemux", 0, "Demuxer for NUV streams"); parent_class = g_type_class_peek_parent (klass); gobject_class->dispose = gst_nuv_demux_dispose; gobject_class->finalize = gst_nuv_demux_finalize; gstelement_class->change_state = gst_nuv_demux_change_state; g_type_class_add_private (gobject_class, sizeof (GstNuvDemuxPrivate)); } static void gst_nuv_demux_init (GstNuvDemux * nuv, GstNuvDemuxClass * nuv_class) { nuv->priv = GST_NUV_DEMUX_GET_PRIVATE (nuv); nuv->priv->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink"); /* creating adapter */ nuv->priv->mode = NUV_PUSH_MODE; nuv->priv->adapter = gst_adapter_new (); nuv->priv->new_audio_segment = TRUE; nuv->priv->new_video_segment = TRUE; gst_pad_set_activate_function (nuv->priv->sinkpad, gst_nuv_demux_sink_activate); gst_pad_set_activatepull_function (nuv->priv->sinkpad, gst_nuv_demux_sink_activate_pull); gst_pad_set_activatepush_function (nuv->priv->sinkpad, gst_nuv_demux_sink_activate_push); gst_pad_set_chain_function (nuv->priv->sinkpad, GST_DEBUG_FUNCPTR (gst_nuv_demux_chain)); /* gst_pad_set_event_function (nuv->priv->sinkpad, gst_nuv_demux_sink_event); */ gst_element_add_pad (GST_ELEMENT (nuv), nuv->priv->sinkpad); } static void gst_nuv_demux_dispose (GObject * object) { GstNuvDemux *nuv = GST_NUV_DEMUX (object); if (nuv->priv->mpeg_buffer != NULL) { gst_buffer_unref (nuv->priv->mpeg_buffer); } gst_nuv_demux_reset (GST_NUV_DEMUX (object)); gst_nuv_demux_destoy_src_pad (GST_NUV_DEMUX (object)); if (nuv->priv->adapter != NULL) { gst_object_unref (nuv->priv->adapter); } } static void gst_nuv_demux_finalize (GObject * object) { G_OBJECT_CLASS (parent_class)->finalize (object); } /* HeaderLoad: */ static GstFlowReturn gst_nuv_demux_header_load (GstNuvDemux * nuv, nuv_header *h) { GstBuffer *buffer = NULL; GstFlowReturn res = gst_nuv_demux_read_bytes (nuv, 72, TRUE, &buffer); if ((res != GST_FLOW_OK) || (buffer == NULL)) { goto done; } memcpy (h->id, buffer->data, 12); memcpy (h->version, buffer->data + 12, 5); h->i_width = GST_READ_UINT32_LE (&buffer->data[20]); h->i_height = GST_READ_UINT32_LE (&buffer->data[24]); h->i_width_desired = GST_READ_UINT32_LE (&buffer->data[28]); h->i_height_desired = GST_READ_UINT32_LE (&buffer->data[32]); h->i_mode = GPOINTER_TO_INT (buffer->data[36]); h->d_aspect = READ_DOUBLE_FROM_LE (&buffer->data[40]); h->d_fps = READ_DOUBLE_FROM_LE (&buffer->data[48]); /* get the num and denom values from fps */ double2fraction (h->d_fps, &h->i_fpsn, &h->i_fpsd); h->i_video_blocks = GST_READ_UINT32_LE (&buffer->data[56]); h->i_audio_blocks = GST_READ_UINT32_LE (&buffer->data[60]); h->i_text_blocks = GST_READ_UINT32_LE (&buffer->data[64]); h->i_keyframe_distance = GST_READ_UINT32_LE (&buffer->data[68]); GST_DEBUG_OBJECT (nuv, "nuv: h=%s v=%s %dx%d a=%f fps=%f v=%d a=%d t=%d kfd=%d", h->id, h->version, h->i_width, h->i_height, h->d_aspect, h->d_fps, h->i_video_blocks, h->i_audio_blocks, h->i_text_blocks, h->i_keyframe_distance); done: if (buffer != NULL) { gst_buffer_unref (buffer); buffer = NULL; } return res; } static GstFlowReturn gst_nuv_demux_stream_header_data (GstNuvDemux * nuv) { GstFlowReturn res; res = gst_nuv_demux_header_load (nuv, &nuv->priv->h); if (res == GST_FLOW_OK) nuv->priv->state = GST_NUV_DEMUX_EXTRA_DATA; return res; } /* * Read NUV file tag */ static GstFlowReturn gst_nuv_demux_stream_file_header (GstNuvDemux * nuv) { GstFlowReturn res = GST_FLOW_OK; GstBuffer *file_header = NULL; res = gst_nuv_demux_read_bytes (nuv, 12, FALSE, &file_header); if (res == GST_FLOW_OK) { if (strncmp ((gchar *) file_header->data, "MythTVVideo", 11) || strncmp ((gchar *) file_header->data, "NuppelVideo", 11)) { nuv->priv->state = GST_NUV_DEMUX_HEADER_DATA; } else { GST_DEBUG_OBJECT (nuv, "error parsing file header"); nuv->priv->state = GST_NUV_DEMUX_INVALID_DATA; res = GST_FLOW_ERROR; } } if (file_header != NULL) { gst_buffer_unref (file_header); file_header = NULL; } return res; } /* FrameHeaderLoad: */ static GstFlowReturn gst_nuv_demux_frame_header_load (GstNuvDemux * nuv, nuv_frame_header *h) { unsigned char *data; GstBuffer *buf = NULL; GstFlowReturn res = gst_nuv_demux_read_bytes (nuv, 12, TRUE, &buf); if ((res != GST_FLOW_OK) || (buf == NULL)) { goto done; } data = buf->data; h->i_type = GPOINTER_TO_INT (data[0]); h->i_compression = GPOINTER_TO_INT (data[1]); h->i_keyframe = GPOINTER_TO_INT (data[2]); h->i_filters = GPOINTER_TO_INT (data[3]); h->i_timecode = GST_READ_UINT32_LE (&data[4]); h->i_length = GST_READ_UINT32_LE (&data[8]); GST_DEBUG_OBJECT (nuv, "frame hdr: t=%c c=%c k=%d f=0x%x timecode=%d l=%d", h->i_type, h->i_compression ? h->i_compression : ' ', h->i_keyframe ? h->i_keyframe : ' ', h->i_filters, h->i_timecode, h->i_length); done: if (buf != NULL) { gst_buffer_unref (buf); buf = NULL; } return res; } static GstFlowReturn gst_nuv_demux_extended_header_load (GstNuvDemux * nuv, nuv_extended_header * h) { unsigned char *data; GstBuffer *buff = NULL; GstFlowReturn res = gst_nuv_demux_read_bytes (nuv, 512, TRUE, &buff); if ((res != GST_FLOW_OK) || (buff == NULL)) { goto done; } data = buff->data; h->i_version = GST_READ_UINT32_LE (&data[0]); h->i_video_fcc = GST_MAKE_FOURCC (data[4], data[5], data[6], data[7]); h->i_audio_fcc = GST_MAKE_FOURCC (data[8], data[9], data[10], data[11]); h->i_audio_sample_rate = GST_READ_UINT32_LE (&data[12]); h->i_audio_bits_per_sample = GST_READ_UINT32_LE (&data[16]); h->i_audio_channels = GST_READ_UINT32_LE (&data[20]); h->i_audio_compression_ratio = GST_READ_UINT32_LE (&data[24]); h->i_audio_quality = GST_READ_UINT32_LE (&data[28]); h->i_rtjpeg_quality = GST_READ_UINT32_LE (&data[32]); h->i_rtjpeg_luma_filter = GST_READ_UINT32_LE (&data[36]); h->i_rtjpeg_chroma_filter = GST_READ_UINT32_LE (&data[40]); h->i_lavc_bitrate = GST_READ_UINT32_LE (&data[44]); h->i_lavc_qmin = GST_READ_UINT32_LE (&data[48]); h->i_lavc_qmin = GST_READ_UINT32_LE (&data[52]); h->i_lavc_maxqdiff = GST_READ_UINT32_LE (&data[56]); h->i_seekable_offset = GST_READ_UINT64_LE (&data[60]); h->i_keyframe_adjust_offset = GST_READ_UINT64_LE (&data[68]); GST_DEBUG_OBJECT (nuv, "ex hdr: v=%d vffc=%4.4s afcc=%4.4s %dHz %dbits ach=%d acr=%d aq=%d" "rtjpeg q=%d lf=%d lc=%d lavc br=%d qmin=%d qmax=%d maxqdiff=%d seekableoff=%lld keyfao=%lld", h->i_version, (gchar *) & h->i_video_fcc, (gchar *) & h->i_audio_fcc, h->i_audio_sample_rate, h->i_audio_bits_per_sample, h->i_audio_channels, h->i_audio_compression_ratio, h->i_audio_quality, h->i_rtjpeg_quality, h->i_rtjpeg_luma_filter, h->i_rtjpeg_chroma_filter, h->i_lavc_bitrate, h->i_lavc_qmin, h->i_lavc_qmax, h->i_lavc_maxqdiff, h->i_seekable_offset, h->i_keyframe_adjust_offset); done: if (buff != NULL) { gst_buffer_unref (buff); buff = NULL; } return res; } /* Query Functions */ static const GstQueryType * gst_nuv_demux_get_src_query_types (GstPad * pad) { static const GstQueryType src_types[] = { GST_QUERY_POSITION, GST_QUERY_DURATION, 0 }; return src_types; } static gboolean gst_nuv_demux_handle_src_query (GstPad * pad, GstQuery * query) { gboolean res = FALSE; GstNuvDemux *nuv = GST_NUV_DEMUX (gst_pad_get_parent (pad)); switch (GST_QUERY_TYPE (query)) { case GST_QUERY_POSITION: { GstFormat format; gst_query_parse_position (query, &format, NULL); switch (format) { case GST_FORMAT_TIME: if (GST_CLOCK_TIME_IS_VALID (nuv->priv->last_frame_time)) { gst_query_set_position (query, GST_FORMAT_TIME, nuv->priv->last_frame_time); res = TRUE; } break; default: break; } break; } case GST_QUERY_DURATION: { GstFormat format; gst_query_parse_duration (query, &format, NULL); switch (format) { case GST_FORMAT_TIME: if (nuv->priv->duration_time != GST_CLOCK_TIME_NONE) { gst_query_set_duration (query, GST_FORMAT_TIME, nuv->priv->duration_time); res = TRUE; } break; default: break; } break; } default: break; } if (res==FALSE) { res = gst_pad_query_default (pad, query); } gst_object_unref (nuv); return res; } static GstPad* gst_nuv_demux_create_pad (GstNuvDemux *nuv, GstCaps *caps, GstStaticPadTemplate *template, const gchar* name) { GstPad *pad = NULL; pad = gst_pad_new_from_static_template (template, name); gst_pad_set_caps (pad, caps); gst_pad_set_active (pad, TRUE); gst_pad_use_fixed_caps (pad); gst_element_add_pad (GST_ELEMENT (nuv), pad); gst_pad_set_event_function (pad, GST_DEBUG_FUNCPTR (gst_nuv_demux_srcpad_event)); gst_pad_set_query_type_function (pad, GST_DEBUG_FUNCPTR (gst_nuv_demux_get_src_query_types)); gst_pad_set_query_function (pad, GST_DEBUG_FUNCPTR (gst_nuv_demux_handle_src_query)); return pad; } static void gst_nuv_demux_create_pads (GstNuvDemux * nuv) { if (nuv->priv->h.i_video_blocks != 0) { GstCaps *video_caps = NULL; video_caps = gst_caps_new_simple ("video/x-divx", "divxversion", G_TYPE_INT, 4, "width", G_TYPE_INT, nuv->priv->h.i_width, "height", G_TYPE_INT, nuv->priv->h.i_height, "framerate", GST_TYPE_FRACTION, nuv->priv->h.i_fpsn, nuv->priv->h.i_fpsd, "format", GST_TYPE_FOURCC, nuv->priv->eh.i_video_fcc, "pixel-aspect-ratio", GST_TYPE_FRACTION, (gint) (nuv->priv->h.d_aspect * 1000.0f), 1000, NULL); nuv->priv->src_video_pad = gst_nuv_demux_create_pad (nuv, video_caps, &video_src_template, "video_src"); gst_caps_unref (video_caps); } if (nuv->priv->h.i_audio_blocks != 0) { GstCaps *audio_caps = NULL; audio_caps = gst_caps_new_simple ("audio/mpeg", "rate", G_TYPE_INT, nuv->priv->eh.i_audio_sample_rate, "format", GST_TYPE_FOURCC, nuv->priv->eh.i_audio_fcc, "channels", G_TYPE_INT, nuv->priv->eh.i_audio_channels, "layer", G_TYPE_INT, 3, // fixme: magic number "mpegversion", G_TYPE_INT, nuv->priv->eh.i_version, NULL); nuv->priv->src_audio_pad = gst_nuv_demux_create_pad (nuv, audio_caps, &audio_src_template, "audio_src"); gst_caps_unref (audio_caps); } gst_element_no_more_pads (GST_ELEMENT (nuv)); } static gboolean gst_nuv_demux_validate_header (nuv_frame_header *h) { gboolean valid = FALSE; //g_usleep (1 * G_USEC_PER_SEC ); switch (h->i_type) { /* case 'V': if (h->i_compression == 0 || h->i_compression == 1 || h->i_compression == 2 || h->i_compression == 'N' || h->i_compression == 'L') { valid = TRUE; } break; case 'A': if (h->i_compression == 0 || h->i_compression == 1 || h->i_compression == 2 || h->i_compression == 3 || h->i_compression == 'F' || h->i_compression == 'S' || h->i_compression == 'N' || h->i_compression == 'L') { valid = TRUE; } break; case 'S': if (h->i_compression == 'B' || h->i_compression == 'A' || h->i_compression == 'V' || h->i_compression == 'S') { valid = TRUE; } break; */ case 'A': case 'V': case 'S': case 'R': case 'D': case 'Q': valid = TRUE; break; default: valid = FALSE; } return valid; } static GstFlowReturn gst_nuv_demux_read_head_frame (GstNuvDemux * nuv) { GstFlowReturn ret = GST_FLOW_OK; gboolean valid = FALSE; do { ret = gst_nuv_demux_frame_header_load (nuv, &nuv->priv->fh); if (ret != GST_FLOW_OK) { return ret; } if (gst_nuv_demux_validate_header (&nuv->priv->fh) == TRUE) valid = TRUE; } while (valid == FALSE); nuv->priv->state = GST_NUV_DEMUX_MOVI; return ret; } static gboolean gst_nuv_combine_flow (GstNuvDemux *nuv) { GstFlowReturn ret_video = nuv->priv->last_video_return; GstFlowReturn ret_audio = nuv->priv->last_audio_return; if ((ret_video != GST_FLOW_OK) && (ret_audio != GST_FLOW_OK)) return FALSE; if (GST_FLOW_IS_FATAL (ret_video)) return FALSE; if (GST_FLOW_IS_FATAL (ret_audio)) return FALSE; return TRUE; } static GstFlowReturn gst_nuv_demux_stream_data (GstNuvDemux * nuv) { GstFlowReturn ret = GST_FLOW_OK; GstPad *pad = NULL; guint64 timestamp; GstBuffer *buf = NULL; nuv_frame_header h; h = nuv->priv->fh; if (h.i_type == 'R') { goto done; } if (h.i_length > 0) { ret = gst_nuv_demux_read_bytes (nuv, h.i_length, TRUE, &buf); if ((ret != GST_FLOW_OK) || (buf == NULL)) { goto done; } if ((h.i_timecode < 0)) { h.i_timecode = 0; //goto done; } timestamp = h.i_timecode * GST_MSECOND; GST_BUFFER_TIMESTAMP (buf) = timestamp; } else { goto done; } switch (h.i_type) { case 'V': { pad = nuv->priv->src_video_pad; if (nuv->priv->new_video_segment) { /* send new segment event*/ gst_pad_push_event (nuv->priv->src_video_pad, gst_event_new_new_segment (TRUE, 1.0, GST_FORMAT_TIME, 0, GST_CLOCK_TIME_NONE, 0)); if (nuv->priv->time_start == GST_CLOCK_TIME_NONE) { nuv->priv->time_start = timestamp; } nuv->priv->new_video_segment = FALSE; } break; } case 'A': { pad = nuv->priv->src_audio_pad; if (nuv->priv->new_audio_segment) { /* send new segment event*/ gst_pad_push_event (nuv->priv->src_audio_pad, gst_event_new_new_segment (TRUE, 1.0, GST_FORMAT_TIME, 0, GST_CLOCK_TIME_NONE, 0)); if (nuv->priv->time_start == GST_CLOCK_TIME_NONE) { nuv->priv->time_start = timestamp; } nuv->priv->new_audio_segment = FALSE; } break; } case 'S': { switch (h.i_compression) { case 'V': GST_DEBUG_OBJECT (nuv, "sending new video segment: %d", h.i_timecode); gst_pad_push_event (nuv->priv->src_video_pad, gst_event_new_new_segment (TRUE, 1.0, GST_FORMAT_TIME, h.i_timecode * GST_MSECOND, GST_CLOCK_TIME_NONE, 0)); break; case 'A': GST_DEBUG_OBJECT (nuv, "sending new audio segment: %d", h.i_timecode); gst_pad_push_event (nuv->priv->src_audio_pad, gst_event_new_new_segment (TRUE, 1.0, GST_FORMAT_TIME, 0, GST_CLOCK_TIME_NONE, 0)); break; default: break; } goto done; } default: break; } if ((buf != NULL) && (pad != NULL)) { /* pushing the buffer */ gst_buffer_set_caps (buf, GST_PAD_CAPS (pad)); ret = gst_pad_push (pad, buf); buf = NULL; if (ret != GST_FLOW_OK) { GST_WARNING_OBJECT (nuv, "error: %d pushing on srcpad %s", ret, gst_pad_get_name (pad)); if (pad == nuv->priv->src_video_pad) { nuv->priv->last_video_return = ret; } else if (pad == nuv->priv->src_audio_pad) { nuv->priv->last_audio_return = ret; } /* verify anothers flow if is necessary stop task */ if (gst_nuv_combine_flow (nuv) != FALSE) { ret = GST_FLOW_OK; } else { GST_WARNING_OBJECT (nuv, "error: on push"); } } } done: if (buf != NULL) { gst_buffer_unref (buf); buf = NULL; } if (ret == GST_FLOW_OK) { nuv->priv->state = GST_NUV_DEMUX_FRAME_HEADER; memset (&nuv->priv->fh, 0, sizeof (nuv->priv->fh)); } return ret; } static GstFlowReturn gst_nuv_demux_stream_mpeg_data (GstNuvDemux * nuv) { GstFlowReturn ret = GST_FLOW_OK; /* ffmpeg extra data */ ret = gst_nuv_demux_read_bytes (nuv, nuv->priv->mpeg_data_size, TRUE, &nuv->priv->mpeg_buffer); if ((ret != GST_FLOW_OK) || (nuv->priv->mpeg_buffer == NULL)) { return ret; } GST_BUFFER_SIZE (nuv->priv->mpeg_buffer) = nuv->priv->mpeg_data_size; nuv->priv->state = GST_NUV_DEMUX_EXTEND_HEADER; return ret; } static GstFlowReturn gst_nuv_demux_stream_extra_data (GstNuvDemux * nuv) { GstFlowReturn ret = GST_FLOW_OK; /* Load 'D' */ nuv_frame_header h; ret = gst_nuv_demux_frame_header_load (nuv, &h); if (ret != GST_FLOW_OK) return ret; if (h.i_type != 'D') { GST_WARNING_OBJECT (nuv, "Unsuported rtjpeg"); return GST_FLOW_NOT_SUPPORTED; } if (h.i_length > 0) { if (h.i_compression == 'F') { nuv->priv->state = GST_NUV_DEMUX_MPEG_DATA; } else { GST_WARNING_OBJECT (nuv, "only file with extended chunk are supported"); return GST_FLOW_NOT_SUPPORTED; } } else { nuv->priv->state = GST_NUV_DEMUX_EXTEND_HEADER; } return ret; } static GstFlowReturn gst_nuv_demux_stream_extend_header_data (GstNuvDemux * nuv) { GstFlowReturn ret = GST_FLOW_OK; ret = gst_nuv_demux_extended_header_load (nuv, &nuv->priv->eh); if (ret != GST_FLOW_OK) return ret; gst_nuv_demux_create_pads (nuv); nuv->priv->state = GST_NUV_DEMUX_INDEX_CREATE; return ret; } static GstFlowReturn gst_nuv_demux_stream_extend_header (GstNuvDemux * nuv) { GstBuffer *buf = NULL; GstFlowReturn res = GST_FLOW_OK; res = gst_nuv_demux_read_bytes (nuv, 1, FALSE, &buf); if ((res != GST_FLOW_OK) || (buf == NULL)) { if (buf != NULL) { gst_buffer_unref (buf); } return res; } if (buf->data[0] == 'X') { gst_buffer_unref (buf); buf = NULL; nuv_frame_header h; res = gst_nuv_demux_frame_header_load (nuv, &h); if (res != GST_FLOW_OK) return res; if (h.i_length != 512) { return GST_FLOW_ERROR; } nuv->priv->state = GST_NUV_DEMUX_EXTEND_HEADER_DATA; } else { nuv->priv->state = GST_NUV_DEMUX_INVALID_DATA; g_object_unref (buf); GST_ELEMENT_WARNING (nuv, STREAM, FAILED, (_("incomplete NUV support")), ("incomplete NUV support")); return GST_FLOW_ERROR; } return res; } static void gst_nuv_demux_create_seek_index (GstNuvDemux * nuv) { GstMessage *msg; nuv_frame_header h; while (gst_nuv_demux_frame_header_load (nuv, &h) == GST_FLOW_OK) { if ((h.i_type == 'V') && (h.i_keyframe == 0)) { frame_index_data *f = g_new0 (frame_index_data, 1); f->offset = nuv->priv->offset - 12; f->timecode = h.i_timecode * GST_MSECOND; nuv->priv->index = g_slist_append (nuv->priv->index, f); } if (h.i_type != 'R') { nuv->priv->offset += h.i_length; if (h.i_type == 'A' || h.i_type == 'V') nuv->priv->duration_time = h.i_timecode * GST_MSECOND; } } GST_DEBUG_OBJECT (nuv, "CREATING INDEX: DONE : DURATION Bytes/Sec: %" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT, nuv->priv->offset, nuv->priv->duration_time); nuv->priv->duration_bytes = nuv->priv->offset; nuv->priv->offset = nuv->priv->header_lengh; msg = gst_message_new_duration (GST_OBJECT (nuv), GST_FORMAT_TIME, nuv->priv->duration_time); gst_element_post_message (GST_ELEMENT (nuv), msg); } static GstFlowReturn gst_nuv_demux_play (GstPad * pad) { GstFlowReturn res = GST_FLOW_OK; GstNuvDemux *nuv = GST_NUV_DEMUX (GST_PAD_PARENT (pad)); switch (nuv->priv->state) { case GST_NUV_DEMUX_START: res = gst_nuv_demux_stream_file_header (nuv); if ((res != GST_FLOW_OK) && (res != GST_FLOW_ERROR_NO_DATA)) { goto pause; } break; case GST_NUV_DEMUX_HEADER_DATA: res = gst_nuv_demux_stream_header_data (nuv); if ((res != GST_FLOW_OK) && (res != GST_FLOW_ERROR_NO_DATA)) { goto pause; } break; case GST_NUV_DEMUX_EXTRA_DATA: res = gst_nuv_demux_stream_extra_data (nuv); if ((res != GST_FLOW_OK) && (res != GST_FLOW_ERROR_NO_DATA)) { goto pause; } break; case GST_NUV_DEMUX_MPEG_DATA: res = gst_nuv_demux_stream_mpeg_data (nuv); if ((res != GST_FLOW_OK) && (res != GST_FLOW_ERROR_NO_DATA)) { goto pause; } break; case GST_NUV_DEMUX_EXTEND_HEADER: res = gst_nuv_demux_stream_extend_header (nuv); if ((res != GST_FLOW_OK) && (res != GST_FLOW_ERROR_NO_DATA)) { goto pause; } break; case GST_NUV_DEMUX_EXTEND_HEADER_DATA: res = gst_nuv_demux_stream_extend_header_data (nuv); if ((res != GST_FLOW_OK) && (res != GST_FLOW_ERROR_NO_DATA)) { goto pause; } //store file header size nuv->priv->header_lengh = nuv->priv->offset; break; case GST_NUV_DEMUX_INDEX_CREATE: if (nuv->priv->mode == NUV_PULL_MODE) { gst_nuv_demux_create_seek_index (nuv); } case GST_NUV_DEMUX_FRAME_HEADER: res = gst_nuv_demux_read_head_frame (nuv); if ((res != GST_FLOW_OK) && (res != GST_FLOW_ERROR_NO_DATA)) { goto pause; } break; case GST_NUV_DEMUX_MOVI: res = gst_nuv_demux_stream_data (nuv); if ((res != GST_FLOW_OK) && (res != GST_FLOW_ERROR_NO_DATA)) { goto pause; } break; case GST_NUV_DEMUX_INVALID_DATA: goto pause; break; default: g_assert_not_reached (); } return GST_FLOW_OK; pause: GST_LOG_OBJECT (nuv, "pausing task, reason %s", gst_flow_get_name (res)); gst_pad_pause_task (nuv->priv->sinkpad); if (res == GST_FLOW_ERROR_EOS) { gst_nuv_demux_send_eos (nuv); nuv->priv->eos = TRUE; res = GST_FLOW_OK; } if (GST_FLOW_IS_FATAL (res)) { GST_ELEMENT_ERROR (nuv, STREAM, FAILED, (_("Internal data stream error.")), ("streaming stopped, reason %s", gst_flow_get_name (res))); gst_nuv_demux_send_eos (nuv); } return res; } static void gst_nuv_demux_send_eos (GstNuvDemux * nuv) { gst_element_post_message (GST_ELEMENT (nuv), gst_message_new_segment_done (GST_OBJECT (nuv), GST_FORMAT_TIME, -1)); if (nuv->priv->src_video_pad) gst_pad_push_event (nuv->priv->src_video_pad, gst_event_new_eos ()); if (nuv->priv->src_audio_pad) gst_pad_push_event (nuv->priv->src_audio_pad, gst_event_new_eos ()); } static GstFlowReturn gst_nuv_demux_read_bytes (GstNuvDemux * nuv, guint64 size, gboolean move, GstBuffer ** buffer) { GstFlowReturn ret = GST_FLOW_OK; if (size == 0) { return ret; } if (nuv->priv->mode == NUV_PULL_MODE) { ret = gst_pad_pull_range (nuv->priv->sinkpad, nuv->priv->offset, size, buffer); if (ret == GST_FLOW_OK) { if (move) { nuv->priv->offset += size; } /* got eos */ } else if (ret == GST_FLOW_UNEXPECTED) { return GST_FLOW_ERROR_EOS; } } else { if (gst_adapter_available (nuv->priv->adapter) < size) { nuv->priv->more_data = TRUE; return GST_FLOW_ERROR_NO_DATA; } if (move) { *buffer = gst_adapter_take_buffer (nuv->priv->adapter, size); } else { guint8 *data = NULL; data = (guint8 *) gst_adapter_peek (nuv->priv->adapter, size); *buffer = gst_buffer_new (); gst_buffer_set_data (*buffer, data, size); } } return ret; } static gboolean gst_nuv_demux_sink_activate (GstPad * sinkpad) { gboolean res = TRUE; GstNuvDemux *nuv = GST_NUV_DEMUX (gst_pad_get_parent (sinkpad)); if (gst_pad_check_pull_range (sinkpad)) { gst_adapter_clear (nuv->priv->adapter); res = gst_pad_activate_pull (sinkpad, TRUE); } else { gst_adapter_clear (nuv->priv->adapter); res = gst_pad_activate_push (sinkpad, TRUE); } g_object_unref (nuv); return res; } static gboolean gst_nuv_demux_sink_activate_pull (GstPad * sinkpad, gboolean active) { GstNuvDemux *nuv = GST_NUV_DEMUX (gst_pad_get_parent (sinkpad)); if (active) { GST_DEBUG_OBJECT (nuv, "activating pull function"); nuv->priv->mode = NUV_PULL_MODE; gst_adapter_clear (nuv->priv->adapter); gst_pad_start_task (sinkpad, (GstTaskFunction) gst_nuv_demux_loop, sinkpad); } else { GST_DEBUG_OBJECT (nuv, "deactivating pull function"); gst_pad_stop_task (sinkpad); } gst_object_unref (nuv); return TRUE; } static gboolean gst_nuv_demux_sink_activate_push (GstPad * pad, gboolean active) { GstNuvDemux *nuv = GST_NUV_DEMUX (gst_pad_get_parent (pad)); if (active) { nuv->priv->mode = NUV_PUSH_MODE; gst_adapter_clear (nuv->priv->adapter); GST_DEBUG_OBJECT (nuv, "activating push/chain function"); } else { GST_DEBUG_OBJECT (nuv, "deactivating push/chain function"); } gst_object_unref (nuv); return TRUE; } static frame_index_data * gst_nuv_demux_do_seek_index (GstNuvDemux *nuv, gint64 seek_pos, gint64 segment_stop, GstFormat format) { GSList *l; frame_index_data *ret = NULL; if (nuv->priv->index == NULL) { return NULL; } /* find keyframe closest to the requested position */ for (l = nuv->priv->index; l != NULL; l = l->next) { frame_index_data *f = (frame_index_data *) l->data; gint64 pos = 0; if (format == GST_FORMAT_BYTES) { pos = f->offset; } else if (format == GST_FORMAT_TIME) { pos = f->timecode; } else { return NULL; } if (pos >= seek_pos) { ret = f; break; } if ((segment_stop != -1) && (segment_stop != GST_CLOCK_TIME_NONE) && (pos > segment_stop)) { break; } } return ret; } static gboolean gst_nuv_demux_do_seek (GstNuvDemux *nuv, GstEvent * event) { gdouble rate; GstFormat format; GstSeekFlags flags; GstSeekType cur_type; gint64 cur; GstSeekType stop_type; gint64 stop; gboolean flush; frame_index_data *entry; gint64 segment_start; gint64 segment_stop; GstEvent *newsegment_event; if (nuv->priv->eos) { return FALSE; } if (nuv->priv->mode == NUV_PUSH_MODE) { return FALSE; } gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur, &stop_type, &stop); /* if (format == GST_FORMAT_TIME) { GST_DEBUG_OBJECT (nuv, "Can only seek on BYTES"); return FALSE; } */ if (rate <= 0.0) { GST_DEBUG_OBJECT (nuv, "Can only seek with positive rate"); return FALSE; } if (cur_type == GST_SEEK_TYPE_SET) { GST_OBJECT_LOCK (nuv); if (gst_nuv_demux_do_seek_index (nuv, cur, -1, format) == NULL) { GST_DEBUG_OBJECT (nuv, "No matching seek entry in index"); GST_OBJECT_UNLOCK (nuv); return FALSE; } GST_OBJECT_UNLOCK (nuv); } flush = !!(flags & GST_SEEK_FLAG_FLUSH); if (flush) { gst_pad_push_event (nuv->priv->sinkpad, gst_event_new_flush_start ()); if (nuv->priv->src_video_pad != NULL) { gst_pad_push_event (nuv->priv->src_video_pad, gst_event_new_flush_start ()); } if (nuv->priv->src_audio_pad != NULL) { gst_pad_push_event (nuv->priv->src_audio_pad, gst_event_new_flush_start ()); } } else { gst_pad_pause_task (nuv->priv->sinkpad); } GST_PAD_STREAM_LOCK (nuv->priv->sinkpad); GST_OBJECT_LOCK (nuv); if (cur == GST_CLOCK_TIME_NONE) cur = 0; if (stop == GST_CLOCK_TIME_NONE) stop = nuv->priv->duration_time; if (cur_type == GST_SEEK_TYPE_SET) segment_start = cur; else if (cur_type == GST_SEEK_TYPE_CUR) segment_start = nuv->priv->segment_start + cur; else segment_start = nuv->priv->segment_start; if (stop_type == GST_SEEK_TYPE_SET) segment_stop = stop; else if (stop_type == GST_SEEK_TYPE_CUR) segment_stop = nuv->priv->segment_stop + stop; else segment_stop = nuv->priv->segment_stop; segment_start = CLAMP (segment_start, 0, nuv->priv->duration_time); segment_stop = CLAMP (segment_stop, 0, nuv->priv->duration_time); entry = gst_nuv_demux_do_seek_index (nuv, segment_start, segment_stop, format); if (entry == NULL) { GST_DEBUG_OBJECT (nuv, "No matching seek entry in index"); goto seek_error; } segment_start = entry->timecode; nuv->priv->segment_start = segment_start; nuv->priv->segment_stop = segment_stop; GST_OBJECT_UNLOCK (nuv); if (!nuv->priv->eos) { GstMessage *msg; msg = gst_message_new_segment_start (GST_OBJECT (nuv), GST_FORMAT_TIME, nuv->priv->segment_start); gst_element_post_message (GST_ELEMENT (nuv), msg); } GST_DEBUG_OBJECT (nuv, "NEW SEGMENT START %" G_GUINT64_FORMAT ", STOP %" G_GUINT64_FORMAT, segment_start, segment_stop); newsegment_event = gst_event_new_new_segment (FALSE, rate, GST_FORMAT_TIME, segment_start, segment_stop, segment_start); if (flush) { if (nuv->priv->src_video_pad != NULL) { gst_pad_push_event (nuv->priv->src_video_pad, gst_event_new_flush_stop ()); } if (nuv->priv->src_audio_pad != NULL) { gst_pad_push_event (nuv->priv->src_audio_pad, gst_event_new_flush_stop ()); } gst_pad_push_event (nuv->priv->sinkpad, gst_event_new_flush_stop ()); } if (nuv->priv->src_video_pad != NULL) { gst_pad_push_event (nuv->priv->src_video_pad, gst_event_ref (newsegment_event)); } if (nuv->priv->src_audio_pad != NULL) { gst_pad_push_event (nuv->priv->src_audio_pad, gst_event_ref (newsegment_event)); } gst_event_unref (newsegment_event); nuv->priv->state = GST_NUV_DEMUX_FRAME_HEADER; nuv->priv->offset = entry->offset; gst_pad_start_task (nuv->priv->sinkpad, (GstTaskFunction) gst_nuv_demux_loop, nuv->priv->sinkpad); GST_PAD_STREAM_UNLOCK (nuv->priv->sinkpad); return TRUE; seek_error: GST_DEBUG_OBJECT (nuv, "Got a seek error"); GST_OBJECT_UNLOCK (nuv); GST_PAD_STREAM_UNLOCK (nuv->priv->sinkpad); return FALSE; } static gboolean gst_nuv_demux_srcpad_event (GstPad * pad, GstEvent * event) { gboolean res = FALSE; GstNuvDemux *nuv; nuv = GST_NUV_DEMUX (gst_pad_get_parent (pad)); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK: res = gst_nuv_demux_do_seek (nuv, event); break; default: res = FALSE; break; } gst_object_unref (nuv); return res; } static GstFlowReturn gst_nuv_demux_chain (GstPad * pad, GstBuffer * buf) { GstFlowReturn ret = GST_FLOW_OK; GstNuvDemux *nuv = GST_NUV_DEMUX (gst_pad_get_parent (pad)); if (nuv->priv->mode != NUV_PUSH_MODE) return ret; gst_adapter_push (nuv->priv->adapter, buf); while ((ret == GST_FLOW_OK) && (nuv->priv->more_data == FALSE)) { ret = gst_nuv_demux_play (pad); } nuv->priv->more_data = FALSE; gst_object_unref (nuv); return ret; } static void gst_nuv_demux_loop (GstPad * pad) { gst_nuv_demux_play (pad); } static void gst_nuv_demux_index_free (gpointer data, gpointer user_data) { g_free (data); } static void gst_nuv_demux_reset (GstNuvDemux * nuv) { nuv->priv->eos = FALSE; nuv->priv->more_data = FALSE; nuv->priv->state = GST_NUV_DEMUX_START; nuv->priv->mode = NUV_PUSH_MODE; nuv->priv->offset = 0; nuv->priv->time_start = 0; nuv->priv->time_qos = GST_CLOCK_TIME_NONE; nuv->priv->duration_bytes = GST_CLOCK_TIME_NONE; nuv->priv->duration_time = GST_CLOCK_TIME_NONE; nuv->priv->last_video_return = GST_FLOW_OK; nuv->priv->last_audio_return = GST_FLOW_OK; nuv->priv->header_lengh = 0; nuv->priv->segment_stop = GST_CLOCK_TIME_NONE; nuv->priv->segment_start = GST_CLOCK_TIME_NONE; //clear index list g_slist_foreach (nuv->priv->index, gst_nuv_demux_index_free, NULL); g_slist_free (nuv->priv->index); nuv->priv->index = NULL; gst_adapter_clear (nuv->priv->adapter); if (nuv->priv->mpeg_buffer != NULL) { gst_buffer_unref (nuv->priv->mpeg_buffer); nuv->priv->mpeg_buffer = NULL; } } static void gst_nuv_demux_destoy_src_pad (GstNuvDemux * nuv) { if (nuv->priv->src_video_pad) { gst_element_remove_pad (GST_ELEMENT (nuv), nuv->priv->src_video_pad); nuv->priv->src_video_pad = NULL; } if (nuv->priv->src_audio_pad) { gst_element_remove_pad (GST_ELEMENT (nuv), nuv->priv->src_audio_pad); nuv->priv->src_audio_pad = NULL; } } static GstStateChangeReturn gst_nuv_demux_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; g_debug ("Nuvdemux state_change"); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: gst_nuv_demux_reset (GST_NUV_DEMUX (element)); gst_nuv_demux_destoy_src_pad (GST_NUV_DEMUX (element)); break; default: break; } g_debug ("Nuvdemux state_change: 1"); ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); if (ret == GST_STATE_CHANGE_FAILURE) { goto done; } g_debug ("Nuvdemux state_change: 2"); switch (transition) { case GST_STATE_CHANGE_READY_TO_NULL: gst_nuv_demux_reset (GST_NUV_DEMUX (element)); gst_nuv_demux_destoy_src_pad (GST_NUV_DEMUX (element)); break; default: break; } g_debug ("Nuvdemux state_change: DONE"); done: return ret; } #if (GST_VERSION_MINOR == 10) && (GST_VERSION_MICRO < 6) GstBuffer * gst_adapter_take_buffer (GstAdapter * adapter, guint nbytes) { GstBuffer *buffer; GstBuffer *cur; guint8 *data; g_return_val_if_fail (GST_IS_ADAPTER (adapter), NULL); g_return_val_if_fail (nbytes > 0, NULL); GST_LOG_OBJECT (adapter, "taking buffer of %u bytes", nbytes); /* we don't have enough data, return NULL. This is unlikely * as one usually does an _available() first instead of peeking a * random size. */ if (G_UNLIKELY (nbytes > adapter->size)) return NULL; /* our head buffer has enough data left, return it */ cur = adapter->buflist->data; if (GST_BUFFER_SIZE (cur) >= nbytes + adapter->skip) { GST_LOG_OBJECT (adapter, "providing buffer of %d bytes via sub-buffer", nbytes); buffer = gst_buffer_create_sub (cur, adapter->skip, nbytes); gst_adapter_flush (adapter, nbytes); return buffer; } data = gst_adapter_take (adapter, nbytes); if (data == NULL) return NULL; buffer = gst_buffer_new (); GST_BUFFER_DATA (buffer) = data; GST_BUFFER_MALLOCDATA (buffer) = data; GST_BUFFER_SIZE (buffer) = nbytes; return buffer; } #endif static void gst_nuv_typefind (GstTypeFind * tf, gpointer unused) { guint8 *data = gst_type_find_peek (tf, 0, 11); if (data) { if (memcmp (data, "MythTVVideo", 11) == 0 || memcmp (data, "NuppelVideo", 11) == 0) { gst_type_find_suggest (tf, GST_TYPE_FIND_MAXIMUM, gst_caps_new_simple ("video/x-nuv", NULL)); } } } static gboolean plugin_init (GstPlugin * plugin) { static gchar *exts[] = { "nuv", NULL }; #ifdef ENABLE_NLS setlocale (LC_ALL, ""); bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); #endif /* ENABLE_NLS */ if (!gst_element_register (plugin, "nuvdemux", GST_RANK_SECONDARY, GST_TYPE_NUV_DEMUX)) { return FALSE; } if (!gst_type_find_register (plugin, "video/x-nuv", GST_RANK_SECONDARY, gst_nuv_typefind, exts, gst_caps_new_simple ("video/x-nuv", NULL), NULL, NULL)) { GST_WARNING ("can't register typefind"); return FALSE; } return TRUE; } GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, "nuvdemux", "Demuxes and muxes audio and video", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)