diff options
author | Jan Schmidt <thaytan@mad.scientist.com> | 2008-06-17 01:08:14 +0000 |
---|---|---|
committer | Jan Schmidt <thaytan@mad.scientist.com> | 2008-06-17 01:08:14 +0000 |
commit | 0951e00dc05236b54a03e25c2c331bf1be332dc5 (patch) | |
tree | 553db40a5b2a59e9615e2db5a10a5927e35bcfab | |
parent | bcc41766b852bae2a8014c87bc2cb67ff7453f6a (diff) | |
download | gst-plugins-bad-0951e00dc05236b54a03e25c2c331bf1be332dc5.tar.gz gst-plugins-bad-0951e00dc05236b54a03e25c2c331bf1be332dc5.tar.bz2 gst-plugins-bad-0951e00dc05236b54a03e25c2c331bf1be332dc5.zip |
configure.ac: Check for libdvdnav to build resindvd.
Original commit message from CVS:
* configure.ac:
Check for libdvdnav to build resindvd.
* ext/Makefile.am:
* ext/resindvd/Makefile.am:
* ext/resindvd/gstmpegdefs.h:
* ext/resindvd/gstmpegdemux.c:
* ext/resindvd/gstmpegdemux.h:
* ext/resindvd/gstmpegdesc.c:
* ext/resindvd/gstmpegdesc.h:
* ext/resindvd/gstpesfilter.c:
* ext/resindvd/gstpesfilter.h:
* ext/resindvd/plugin.c:
* ext/resindvd/resin-play:
* ext/resindvd/resindvdbin.c:
* ext/resindvd/resindvdbin.h:
* ext/resindvd/resindvdsrc.c:
* ext/resindvd/resindvdsrc.h:
* ext/resindvd/rsnaudiomunge.c:
* ext/resindvd/rsnaudiomunge.h:
* ext/resindvd/rsnbasesrc.c:
* ext/resindvd/rsnbasesrc.h:
* ext/resindvd/rsnpushsrc.c:
* ext/resindvd/rsnpushsrc.h:
* ext/resindvd/rsnstreamselector.c:
* ext/resindvd/rsnstreamselector.h:
First commit of DVD-Video playback component 'rsndvdbin'
and helper elements.
Use --enable-experimental for now, but feel free to give it a
try using the resin-play script.
* gst/dvdspu/gstdvdspu.c:
Add some extra guards for malformed events.
-rw-r--r-- | ChangeLog | 38 | ||||
-rw-r--r-- | configure.ac | 33 | ||||
-rw-r--r-- | ext/Makefile.am | 6 | ||||
-rw-r--r-- | ext/resindvd/Makefile.am | 30 | ||||
-rw-r--r-- | ext/resindvd/gstmpegdefs.h | 175 | ||||
-rw-r--r-- | ext/resindvd/gstmpegdemux.c | 1943 | ||||
-rw-r--r-- | ext/resindvd/gstmpegdemux.h | 127 | ||||
-rw-r--r-- | ext/resindvd/gstmpegdesc.c | 156 | ||||
-rw-r--r-- | ext/resindvd/gstmpegdesc.h | 249 | ||||
-rw-r--r-- | ext/resindvd/gstpesfilter.c | 623 | ||||
-rw-r--r-- | ext/resindvd/gstpesfilter.h | 88 | ||||
-rw-r--r-- | ext/resindvd/plugin.c | 53 | ||||
-rwxr-xr-x | ext/resindvd/resin-play | 11 | ||||
-rw-r--r-- | ext/resindvd/resindvdbin.c | 758 | ||||
-rw-r--r-- | ext/resindvd/resindvdbin.h | 80 | ||||
-rw-r--r-- | ext/resindvd/resindvdsrc.c | 1426 | ||||
-rw-r--r-- | ext/resindvd/resindvdsrc.h | 113 | ||||
-rw-r--r-- | ext/resindvd/rsnaudiomunge.c | 378 | ||||
-rw-r--r-- | ext/resindvd/rsnaudiomunge.h | 61 | ||||
-rw-r--r-- | ext/resindvd/rsnbasesrc.c | 2423 | ||||
-rw-r--r-- | ext/resindvd/rsnbasesrc.h | 257 | ||||
-rw-r--r-- | ext/resindvd/rsnpushsrc.c | 101 | ||||
-rw-r--r-- | ext/resindvd/rsnpushsrc.h | 76 | ||||
-rw-r--r-- | ext/resindvd/rsnstreamselector.c | 784 | ||||
-rw-r--r-- | ext/resindvd/rsnstreamselector.h | 62 | ||||
-rw-r--r-- | gst/dvdspu/gstdvdspu.c | 10 |
26 files changed, 10061 insertions, 0 deletions
@@ -1,3 +1,41 @@ +2008-06-17 Jan Schmidt <thaytan@noraisin.net> + + * configure.ac: + Check for libdvdnav to build resindvd. + + * ext/Makefile.am: + * ext/resindvd/Makefile.am: + * ext/resindvd/gstmpegdefs.h: + * ext/resindvd/gstmpegdemux.c: + * ext/resindvd/gstmpegdemux.h: + * ext/resindvd/gstmpegdesc.c: + * ext/resindvd/gstmpegdesc.h: + * ext/resindvd/gstpesfilter.c: + * ext/resindvd/gstpesfilter.h: + * ext/resindvd/plugin.c: + * ext/resindvd/resin-play: + * ext/resindvd/resindvdbin.c: + * ext/resindvd/resindvdbin.h: + * ext/resindvd/resindvdsrc.c: + * ext/resindvd/resindvdsrc.h: + * ext/resindvd/rsnaudiomunge.c: + * ext/resindvd/rsnaudiomunge.h: + * ext/resindvd/rsnbasesrc.c: + * ext/resindvd/rsnbasesrc.h: + * ext/resindvd/rsnpushsrc.c: + * ext/resindvd/rsnpushsrc.h: + * ext/resindvd/rsnstreamselector.c: + * ext/resindvd/rsnstreamselector.h: + + First commit of DVD-Video playback component 'rsndvdbin' + and helper elements. + + Use --enable-experimental for now, but feel free to give it a + try using the resin-play script. + + * gst/dvdspu/gstdvdspu.c: + Add some extra guards for malformed events. + 2008-06-16 David Schleef <ds@schleef.org> * configure.ac: diff --git a/configure.ac b/configure.ac index e818fa7b..ff70aeae 100644 --- a/configure.ac +++ b/configure.ac @@ -477,6 +477,38 @@ return 0; fi ]) +dnl *** dvdnav for resindvd *** +USE_DVDNAV=$BUILD_EXPERIMENTAL +translit(dnm, m, l) AM_CONDITIONAL(USE_DVDNAV, true) +AG_GST_CHECK_FEATURE(DVDNAV, [dvdnav library], resindvd, [ + translit(dnm, m, l) AC_SUBST(DVDNAV_LIBS) + translit(dnm, m, l) AC_SUBST(DVDNAV_CFLAGS) + AG_GST_CHECK_CONFIGPROG(DVDNAV, dvdnav-config) + if test x"$HAVE_DVDNAV" = x"yes"; then + dnl check version + DVDNAV_VERSION=`dvdnav-config --version|head -n 1|sed 's/^.*) //'|sed 's/ (.*)//'` + DVDNAV_MAJOR=`echo $DVDNAV_VERSION | cut -d. -f1 | sed s/[a-zA-Z\-].*//g` + DVDNAV_MINOR=`echo $DVDNAV_VERSION | cut -d. -f2 | sed s/[a-zA-Z\-].*//g` + DVDNAV_MICRO=`echo $DVDNAV_VERSION | cut -d. -f3 | sed s/[a-zA-Z\-].*//g` + if [[ "$DVDNAV_MAJOR" -eq "0" ]] && \ + [[ "$DVDNAV_MINOR" -lt "1" ]]; then + AC_MSG_WARN([libdvdnav >= 0.1.7 is required, you have $DVDNAV_VERSION]) + HAVE_DVDNAV="no" + elif [[ "$DVDNAV_MAJOR" -eq "0" ]] && \ + [[ "$DVDNAV_MINOR" -eq "1" ]] && \ + [[ "$DVDNAV_MICRO" -lt "7" ]]; then + AC_MSG_WARN([libdvdnav >= 0.1.7 is required, you have $DVDNAV_VERSION]) + HAVE_DVDNAV="no" + fi + fi + dnl now check for dvdread/nav_print.h - see #133002 + AC_CHECK_HEADER(dvdread/nav_print.h, , [ + AC_MSG_WARN([header dvdread/nav_print.h from dvdread missing]) + HAVE_DVDNAV="no" + ]) + AS_SCRUB_INCLUDE(DVDNAV_CFLAGS) +]) + dnl *** METADATA *** translit(dnm, m, l) AM_CONDITIONAL(USE_METADATA, true) AG_GST_CHECK_FEATURE(METADATA, [METADATA muxer and demuxer], metadata, [ @@ -1225,6 +1257,7 @@ ext/musicbrainz/Makefile ext/mythtv/Makefile ext/neon/Makefile ext/ofa/Makefile +ext/resindvd/Makefile ext/sdl/Makefile ext/sndfile/Makefile ext/soundtouch/Makefile diff --git a/ext/Makefile.am b/ext/Makefile.am index 8ed3191b..f1279177 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -76,6 +76,12 @@ else DTS_DIR= endif +if USE_DVDNAV + DVDNAV_DIR = resindvd +else + DVDNAV_DIR = +endif + if USE_FAAC FAAC_DIR=faac else diff --git a/ext/resindvd/Makefile.am b/ext/resindvd/Makefile.am new file mode 100644 index 00000000..9f4d3c8f --- /dev/null +++ b/ext/resindvd/Makefile.am @@ -0,0 +1,30 @@ +# plugindir is set in configure + +plugin_LTLIBRARIES = libresindvd.la + +libresindvd_la_SOURCES = \ + plugin.c \ + resindvdbin.c \ + rsnaudiomunge.c \ + rsnbasesrc.c \ + rsnpushsrc.c \ + rsnstreamselector.c \ + resindvdsrc.c \ + gstmpegdesc.c \ + gstmpegdemux.c \ + gstpesfilter.c + +libresindvd_la_CFLAGS = $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(DVDNAV_CFLAGS) +libresindvd_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) $(DVDNAV_LIBS) +libresindvd_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) + +noinst_HEADERS = resindvdbin.h \ + rsnaudiomunge.h \ + rsnbasesrc.h \ + rsnpushsrc.h \ + rsnstreamselector.h \ + resindvdsrc.h \ + gstmpegdefs.h \ + gstmpegdesc.h \ + gstmpegdemux.h \ + gstpesfilter.h diff --git a/ext/resindvd/gstmpegdefs.h b/ext/resindvd/gstmpegdefs.h new file mode 100644 index 00000000..33a8249c --- /dev/null +++ b/ext/resindvd/gstmpegdefs.h @@ -0,0 +1,175 @@ +/* + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + * License for the specific language governing rights and limitations + * under the License. + * + * The Original Code is Fluendo MPEG Demuxer plugin. + * + * The Initial Developer of the Original Code is Fluendo, S.L. + * Portions created by Fluendo, S.L. are Copyright (C) 2005 + * Fluendo, S.L. All Rights Reserved. + * + * Contributor(s): Wim Taymans <wim@fluendo.com> + * Jan Schmidt <thaytan@noraisin.net> + */ + +#ifndef __GST_MPEG_DEFS_H__ +#define __GST_MPEG_DEFS_H__ + +/* + * 1011 1100 program_stream_map + * 1011 1101 private_stream_1 + * 1011 1110 padding_stream + * 1011 1111 private_stream_2 + * 110x xxxx ISO/IEC 13818-3 or ISO/IEC 11172-3 audio stream number x xxxx + * 1110 xxxx ITU-T Rec. H.262 | ISO/IEC 13818-2 or ISO/IEC 11172-2 video stream number xxxx + * 1111 0000 ECM_stream + * 1111 0001 EMM_stream + * 1111 0010 ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Annex A or ISO/IEC 13818-6_DSMCC_stream + * 1111 0011 ISO/IEC_13522_stream + * 1111 0100 ITU-T Rec. H.222.1 type A + * 1111 0101 ITU-T Rec. H.222.1 type B + * 1111 0110 ITU-T Rec. H.222.1 type C + * 1111 0111 ITU-T Rec. H.222.1 type D + * 1111 1000 ITU-T Rec. H.222.1 type E + * 1111 1001 ancillary_stream + * 1111 1010 E 1111 1110 reserved data stream + * 1111 1111 program_stream_directory + */ + +#define ID_PS_END_CODE 0x000001B9 +#define ID_PS_PACK_START_CODE 0x000001BA +#define ID_PS_SYSTEM_HEADER_START_CODE 0x000001BB +#define ID_PS_PROGRAM_STREAM_MAP 0x000001BC +#define ID_PRIVATE_STREAM_1 0x000001BD +#define ID_PADDING_STREAM 0x000001BE +#define ID_PRIVATE_STREAM_2 0x000001BF +#define ID_ISO_IEC_MPEG12_AUDIO_STREAM_0 0x000001C0 +#define ID_ISO_IEC_MPEG12_AUDIO_STREAM_32 0x000001DF +#define ID_ISO_IEC_MPEG12_VIDEO_STREAM_0 0x000001E0 +#define ID_ISO_IEC_MPEG12_VIDEO_STREAM_16 0x000001EF +#define ID_ECM_STREAM 0x000001F0 +#define ID_EMM_STREAM 0x000001F1 +#define ID_DSMCC_STREAM 0x000001F2 +#define ID_ISO_IEC_13522_STREAM 0x000001F3 +#define ID_ITU_TREC_H222_TYPE_A_STREAM 0x000001F4 +#define ID_ITU_TREC_H222_TYPE_B_STREAM 0x000001F5 +#define ID_ITU_TREC_H222_TYPE_C_STREAM 0x000001F6 +#define ID_ITU_TREC_H222_TYPE_D_STREAM 0x000001F7 +#define ID_ITU_TREC_H222_TYPE_E_STREAM 0x000001F8 +#define ID_ANCILLARY_STREAM 0x000001F9 +#define ID_RESERVED_STREAM_1 0x000001FA +#define ID_RESERVED_STREAM_2 0x000001FB +#define ID_EXTENDED_METADATA 0x000001FC +#define ID_EXTENDED_STREAM_ID 0x000001FD +#define ID_RESERVED_STREAM_3 0x000001FE +#define ID_PROGRAM_STREAM_DIRECTORY 0x000001FF + +#define PACKET_VIDEO_START_CODE 0x000001E0 +#define PACKET_AUDIO_START_CODE 0x000001C0 +#define PICTURE_START_CODE 0x00000100 +#define USER_DATA_START_CODE 0x000001B2 +#define SEQUENCE_HEADER_CODE 0x000001B3 +#define SEQUENCE_ERROR_CODE 0x000001B4 +#define EXTENSION_START_CODE 0x000001B5 +#define SEQUENCE_END_CODE 0x000001B7 +#define GROUP_START_CODE 0x000001B8 + +#define AC3_SYNC_WORD 0x0b770000 + +#define MPEG_TS_SYNC_BYTE 0x00000047 + +#define PID_PROGRAM_ASSOCIATION_TABLE 0x0000 +#define PID_CONDITIONAL_ACCESS_TABLE 0x0001 +#define PID_RESERVED_FIRST 0x0002 +#define PID_RESERVED_LAST 0x0010 +#define PID_NULL_PACKET 0x1FFF + +#define PID_TYPE_UNKNOWN 0 +#define PID_TYPE_RESERVED 1 +#define PID_TYPE_PROGRAM_ASSOCIATION 2 +#define PID_TYPE_CONDITIONAL_ACCESS 3 +#define PID_TYPE_PROGRAM_MAP 4 +#define PID_TYPE_ELEMENTARY 5 +#define PID_TYPE_NULL_PACKET 6 +#define PID_TYPE_PRIVATE_SECTION 7 + +/* Stream type assignments + * + * 0x00 ITU-T | ISO/IEC Reserved + * 0x01 ISO/IEC 11172 Video + * 0x02 ITU-T Rec. H.262 | ISO/IEC 13818-2 Video or + * ISO/IEC 11172-2 constrained parameter video + * stream + * 0x03 ISO/IEC 11172 Audio + * 0x04 ISO/IEC 13818-3 Audio + * 0x05 ITU-T Rec. H.222.0 | ISO/IEC 13818-1 + * private_sections + * 0x06 ITU-T Rec. H.222.0 | ISO/IEC 13818-1 PES + * packets containing private data + * 0x07 ISO/IEC 13522 MHEG + * 0x08 ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Annex A + * DSM CC + * 0x09 ITU-T Rec. H.222.1 + * 0x0A ISO/IEC 13818-6 type A + * 0x0B ISO/IEC 13818-6 type B + * 0x0C ISO/IEC 13818-6 type C + * 0x0D ISO/IEC 13818-6 type D + * 0x0E ISO/IEC 13818-1 auxiliary + * 0x0F-0x7F ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Reserved + * 0x80-0xFF User Private + */ +#define ST_RESERVED 0x00 +#define ST_VIDEO_MPEG1 0x01 +#define ST_VIDEO_MPEG2 0x02 +#define ST_AUDIO_MPEG1 0x03 +#define ST_AUDIO_MPEG2 0x04 +#define ST_PRIVATE_SECTIONS 0x05 +#define ST_PRIVATE_DATA 0x06 +#define ST_MHEG 0x07 +#define ST_DSMCC 0x08 +#define ST_H222_1 0x09 + +/* later extensions */ +#define ST_AUDIO_AAC 0x0f +#define ST_VIDEO_MPEG4 0x10 +#define ST_VIDEO_H264 0x1b + +/* Un-official Dirac extension */ +#define ST_VIDEO_DIRAC 0xd1 + +/* private stream types */ +#define ST_PS_AUDIO_AC3 0x81 +#define ST_PS_AUDIO_DTS 0x8a +#define ST_PS_AUDIO_LPCM 0x8b +#define ST_PS_DVD_SUBPICTURE 0xff + +/* Un-official time-code stream */ +#define ST_PS_TIMECODE 0xd2 + +/* Internal stream types >= 0x100 */ +#define ST_GST_AUDIO_RAWA52 0x181 + /* Used when we don't yet know which stream type it will be in a PS stream */ +#define ST_GST_VIDEO_MPEG1_OR_2 0x102 + +#define CLOCK_BASE 9LL +#define CLOCK_FREQ (CLOCK_BASE * 10000) + +#define MPEGTIME_TO_GSTTIME(time) (gst_util_uint64_scale ((time), \ + GST_MSECOND/10, CLOCK_BASE)) +#define GSTTIME_TO_MPEGTIME(time) (gst_util_uint64_scale ((time), \ + CLOCK_BASE, GST_MSECOND/10)) + +#define MPEG_MUX_RATE_MULT 50 + +/* some extra GstFlowReturn values used internally */ +#define GST_FLOW_NEED_MORE_DATA -100 +#define GST_FLOW_LOST_SYNC -101 + +#endif /* __GST_MPEG_DEFS_H__ */ diff --git a/ext/resindvd/gstmpegdemux.c b/ext/resindvd/gstmpegdemux.c new file mode 100644 index 00000000..5e1da719 --- /dev/null +++ b/ext/resindvd/gstmpegdemux.c @@ -0,0 +1,1943 @@ +/* + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + * License for the specific language governing rights and limitations + * under the License. + * + * The Original Code is Fluendo MPEG Demuxer plugin. + * + * The Initial Developer of the Original Code is Fluendo, S.L. + * Portions created by Fluendo, S.L. are Copyright (C) 2005 + * Fluendo, S.L. All Rights Reserved. + * + * Contributor(s): Wim Taymans <wim@fluendo.com> + * Jan Schmidt <thaytan@noraisin.net> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> + +#include "gstmpegdefs.h" +#include "gstmpegdemux.h" + +#define MAX_DVD_AUDIO_STREAMS 8 +#define MAX_DVD_SUBPICTURE_STREAMS 32 + +#define SEGMENT_THRESHOLD (GST_SECOND/2) + +/* The SCR_MUNGE value is used to offset the scr_adjust value, to avoid + * ever generating a negative timestamp */ +#define SCR_MUNGE (2 * GST_SECOND) + +/* We clamp scr delta with 0 so negative bytes won't be possible */ +#define GSTTIME_TO_BYTES(time) \ + ((time != -1) ? gst_util_uint64_scale (MAX(0,(gint64) (GSTTIME_TO_MPEGTIME(time) - demux->first_scr)), demux->scr_rate_n, demux->scr_rate_d) : -1) +#define BYTES_TO_GSTTIME(bytes) ((bytes != -1) ? MPEGTIME_TO_GSTTIME(gst_util_uint64_scale (bytes, demux->scr_rate_d, demux->scr_rate_n)) : -1) + +#define ADAPTER_OFFSET_FLUSH(_bytes_) demux->adapter_offset += (_bytes_) + +GST_DEBUG_CATEGORY_STATIC (gstflupsdemux_debug); +#define GST_CAT_DEFAULT (gstflupsdemux_debug) + +GST_DEBUG_CATEGORY_EXTERN (gstflupesfilter_debug); + +/* elementfactory information */ +static GstElementDetails flups_demux_details = { + "MPEG Program Demuxer", + "Codec/Demuxer", + "Demultiplexes MPEG Program Streams", + "Wim Taymans <wim@fluendo.com>" +}; + +/* MPEG2Demux signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + ARG_0, + /* FILL ME */ +}; + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-resin-dvd") + ); + +static GstStaticPadTemplate video_template = + GST_STATIC_PAD_TEMPLATE ("video_%02x", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("video/mpeg, " + "mpegversion = (int) { 1, 2, 4 }, " "systemstream = (boolean) FALSE;" + "video/x-h264") + ); + +static GstStaticPadTemplate audio_template = + GST_STATIC_PAD_TEMPLATE ("audio_%02x", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("audio/mpeg, " + "mpegversion = (int) 1;" + "audio/x-private1-lpcm; " + "audio/x-private1-ac3;" "audio/x-private1-dts;" "audio/ac3") + ); + +static GstStaticPadTemplate subpicture_template = +GST_STATIC_PAD_TEMPLATE ("subpicture_%02x", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("video/x-dvd-subpicture") + ); + +static GstStaticPadTemplate private_template = +GST_STATIC_PAD_TEMPLATE ("private_%d", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static void gst_flups_demux_base_init (GstFluPSDemuxClass * klass); +static void gst_flups_demux_class_init (GstFluPSDemuxClass * klass); +static void gst_flups_demux_init (GstFluPSDemux * demux); +static void gst_flups_demux_finalize (GstFluPSDemux * demux); +static void gst_flups_demux_reset (GstFluPSDemux * demux); + +static gboolean gst_flups_demux_sink_event (GstPad * pad, GstEvent * event); +static gboolean gst_flups_demux_src_event (GstPad * pad, GstEvent * event); +static gboolean gst_flups_demux_src_query (GstPad * pad, GstQuery * query); +static GstFlowReturn gst_flups_demux_chain (GstPad * pad, GstBuffer * buffer); + +static GstStateChangeReturn gst_flups_demux_change_state (GstElement * element, + GstStateChange transition); + +static GstElementClass *parent_class = NULL; + +/*static guint gst_flups_demux_signals[LAST_SIGNAL] = { 0 };*/ + +GType +gst_flups_demux_get_type (void) +{ + static GType flups_demux_type = 0; + + if (!flups_demux_type) { + static const GTypeInfo flups_demux_info = { + sizeof (GstFluPSDemuxClass), + (GBaseInitFunc) gst_flups_demux_base_init, + NULL, + (GClassInitFunc) gst_flups_demux_class_init, + NULL, + NULL, + sizeof (GstFluPSDemux), + 0, + (GInstanceInitFunc) gst_flups_demux_init, + }; + + flups_demux_type = + g_type_register_static (GST_TYPE_ELEMENT, "RsnDVDDemux", + &flups_demux_info, 0); + + GST_DEBUG_CATEGORY_INIT (gstflupsdemux_debug, "rsndvddemux", 0, + "MPEG program stream demultiplexer element"); + } + + return flups_demux_type; +} + +static void +gst_flups_demux_base_init (GstFluPSDemuxClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + klass->sink_template = gst_static_pad_template_get (&sink_template); + klass->video_template = gst_static_pad_template_get (&video_template); + klass->audio_template = gst_static_pad_template_get (&audio_template); + klass->subpicture_template = + gst_static_pad_template_get (&subpicture_template); + klass->private_template = gst_static_pad_template_get (&private_template); + + gst_element_class_add_pad_template (element_class, klass->video_template); + gst_element_class_add_pad_template (element_class, klass->audio_template); + gst_element_class_add_pad_template (element_class, klass->private_template); + gst_element_class_add_pad_template (element_class, + klass->subpicture_template); + gst_element_class_add_pad_template (element_class, klass->sink_template); + + gst_element_class_set_details (element_class, &flups_demux_details); +} + +static void +gst_flups_demux_class_init (GstFluPSDemuxClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + parent_class = g_type_class_ref (GST_TYPE_ELEMENT); + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->finalize = (GObjectFinalizeFunc) gst_flups_demux_finalize; + + gstelement_class->change_state = gst_flups_demux_change_state; +} + +static void +gst_flups_demux_init (GstFluPSDemux * demux) +{ + GstFluPSDemuxClass *klass = GST_FLUPS_DEMUX_GET_CLASS (demux); + + demux->sinkpad = gst_pad_new_from_template (klass->sink_template, "sink"); + gst_pad_set_event_function (demux->sinkpad, gst_flups_demux_sink_event); + gst_pad_set_chain_function (demux->sinkpad, gst_flups_demux_chain); + gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad); + + demux->streams = + g_malloc0 (sizeof (GstFluPSStream *) * (GST_FLUPS_DEMUX_MAX_STREAMS)); + + demux->scr_adjust = GSTTIME_TO_MPEGTIME (SCR_MUNGE); +} + +static void +gst_flups_demux_finalize (GstFluPSDemux * demux) +{ + gst_flups_demux_reset (demux); + g_free (demux->streams); + + G_OBJECT_CLASS (parent_class)->finalize (G_OBJECT (demux)); +} + +static void +gst_flups_demux_reset (GstFluPSDemux * demux) +{ + /* Clean up the streams and pads we allocated */ + gint i; + GstEvent **p_ev; + + for (i = 0; i < GST_FLUPS_DEMUX_MAX_STREAMS; i++) { + GstFluPSStream *stream = demux->streams[i]; + + if (stream != NULL) { + if (stream->pad) + gst_element_remove_pad (GST_ELEMENT_CAST (demux), stream->pad); + + g_free (stream); + demux->streams[i] = NULL; + } + } + + p_ev = &demux->lang_codes; + gst_event_replace (p_ev, NULL); + + demux->scr_adjust = GSTTIME_TO_MPEGTIME (SCR_MUNGE); +} + +static GstFluPSStream * +gst_flups_demux_create_stream (GstFluPSDemux * demux, gint id, gint stream_type) +{ + GstFluPSStream *stream; + GstPadTemplate *template; + gchar *name; + GstFluPSDemuxClass *klass = GST_FLUPS_DEMUX_GET_CLASS (demux); + GstCaps *caps; + + name = NULL; + template = NULL; + caps = NULL; + + GST_DEBUG_OBJECT (demux, "create stream id 0x%02x, type 0x%02x", id, + stream_type); + + switch (stream_type) { + case ST_VIDEO_MPEG1: + case ST_VIDEO_MPEG2: + case ST_VIDEO_MPEG4: + case ST_GST_VIDEO_MPEG1_OR_2: + { + gint mpeg_version = 1; + if (stream_type == ST_VIDEO_MPEG2 || + (stream_type == ST_GST_VIDEO_MPEG1_OR_2 && demux->is_mpeg2_pack)) { + mpeg_version = 2; + } + if (stream_type == ST_VIDEO_MPEG4) { + mpeg_version = 4; + } + + template = klass->video_template; + name = g_strdup_printf ("video_%02x", id); + caps = gst_caps_new_simple ("video/mpeg", + "mpegversion", G_TYPE_INT, mpeg_version, + "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + break; + } + case ST_AUDIO_MPEG1: + case ST_AUDIO_MPEG2: + template = klass->audio_template; + name = g_strdup_printf ("audio_%02x", id); + caps = gst_caps_new_simple ("audio/mpeg", + "mpegversion", G_TYPE_INT, 1, NULL); + break; + case ST_PRIVATE_SECTIONS: + case ST_PRIVATE_DATA: + case ST_MHEG: + case ST_DSMCC: + case ST_AUDIO_AAC: + break; + case ST_VIDEO_H264: + template = klass->video_template; + name = g_strdup_printf ("video_%02x", id); + caps = gst_caps_new_simple ("video/x-h264", NULL); + break; + case ST_PS_AUDIO_AC3: + template = klass->audio_template; + name = g_strdup_printf ("audio_%02x", id); + caps = gst_caps_new_simple ("audio/x-private1-ac3", NULL); + break; + case ST_PS_AUDIO_DTS: + template = klass->audio_template; + name = g_strdup_printf ("audio_%02x", id); + caps = gst_caps_new_simple ("audio/x-private1-dts", NULL); + break; + case ST_PS_AUDIO_LPCM: + template = klass->audio_template; + name = g_strdup_printf ("audio_%02x", id); + caps = gst_caps_new_simple ("audio/x-private1-lpcm", NULL); + break; + case ST_PS_DVD_SUBPICTURE: + template = klass->subpicture_template; + name = g_strdup_printf ("subpicture_%02x", id); + caps = gst_caps_new_simple ("video/x-dvd-subpicture", NULL); + break; + case ST_GST_AUDIO_RAWA52: + template = klass->audio_template; + name = g_strdup_printf ("audio_%02x", id); + caps = gst_caps_new_simple ("audio/ac3", NULL); + break; + default: + break; + } + + if (name == NULL || template == NULL || caps == NULL) + return NULL; + + stream = g_new0 (GstFluPSStream, 1); + stream->id = id; + stream->discont = TRUE; + stream->notlinked = FALSE; + stream->type = stream_type; + stream->pad = gst_pad_new_from_template (template, name); + gst_pad_set_event_function (stream->pad, gst_flups_demux_src_event); + gst_pad_set_query_function (stream->pad, gst_flups_demux_src_query); + gst_pad_use_fixed_caps (stream->pad); + gst_pad_set_caps (stream->pad, caps); + gst_caps_unref (caps); + GST_DEBUG_OBJECT (demux, "create pad %s, caps %" GST_PTR_FORMAT, name, caps); + g_free (name); + + + return stream; +} + +static GstFluPSStream * +gst_flups_demux_get_stream (GstFluPSDemux * demux, gint id, gint type) +{ + GstFluPSStream *stream = demux->streams[id]; + + if (stream == NULL) { + if (!(stream = gst_flups_demux_create_stream (demux, id, type))) + goto unknown_stream; + + GST_DEBUG_OBJECT (demux, "adding pad for stream id 0x%02x type 0x%02x", id, + type); + + gst_pad_set_active (stream->pad, TRUE); + gst_element_add_pad (GST_ELEMENT (demux), stream->pad); + + demux->streams[id] = stream; + } + return stream; + + /* ERROR */ +unknown_stream: + { + GST_DEBUG_OBJECT (demux, "unknown stream id 0x%02x type 0x%02x", id, type); + return NULL; + } +} + +static GstFlowReturn +gst_flups_demux_send_data (GstFluPSDemux * demux, GstFluPSStream * stream, + GstBuffer * buf) +{ + GstFlowReturn result; + guint64 timestamp; + guint size; + + if (stream == NULL) + goto no_stream; + + /* timestamps */ + if (demux->next_pts != G_MAXUINT64) + timestamp = MPEGTIME_TO_GSTTIME (demux->next_pts); + else + timestamp = GST_CLOCK_TIME_NONE; + + if (demux->current_scr != G_MAXUINT64) { + GstClockTime cur_scr_time = MPEGTIME_TO_GSTTIME (demux->current_scr); + + if (stream->last_ts == GST_CLOCK_TIME_NONE || + stream->last_ts < cur_scr_time) { +#if 0 + g_print ("last_ts update on pad %s to time %" GST_TIME_FORMAT "\n", + GST_PAD_NAME (stream->pad), GST_TIME_ARGS (cur_scr_time)); +#endif + stream->last_ts = cur_scr_time; + } + } + + /* OK, sent new segment now prepare the buffer for sending */ + /* caps */ + gst_buffer_set_caps (buf, GST_PAD_CAPS (stream->pad)); + GST_BUFFER_TIMESTAMP (buf) = timestamp; + + /* Set the buffer discont flag, and clear discont state on the stream */ + if (stream->discont) { + GST_DEBUG_OBJECT (demux, "discont buffer to pad %" GST_PTR_FORMAT + " with TS %" GST_TIME_FORMAT "\n", stream->pad, + GST_TIME_ARGS (timestamp)); + + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + stream->discont = FALSE; + } + size = GST_BUFFER_SIZE (buf); + + demux->next_pts = G_MAXUINT64; + demux->next_dts = G_MAXUINT64; + + result = gst_pad_push (stream->pad, buf); + GST_DEBUG_OBJECT (demux, "pushed stream id 0x%02x type 0x%02x, time: %" + GST_TIME_FORMAT ", size %d. result: %s", + stream->id, stream->type, GST_TIME_ARGS (timestamp), + size, gst_flow_get_name (result)); + + return result; + + /* ERROR */ +no_stream: + { + GST_DEBUG_OBJECT (demux, "no stream given"); + gst_buffer_unref (buf); + return GST_FLOW_OK; + } +} + +static void +gst_flups_demux_mark_discont (GstFluPSDemux * demux) +{ + gint id; + + /* mark discont on all streams */ + for (id = 0; id < GST_FLUPS_DEMUX_MAX_STREAMS; id++) { + GstFluPSStream *stream = demux->streams[id]; + + if (stream) { + stream->discont = TRUE; + GST_DEBUG_OBJECT (demux, "marked stream as discont %d", stream->discont); + } + } +} + +static void +gst_flups_demux_clear_times (GstFluPSDemux * demux) +{ + gint id; + + /* Clear the last ts for all streams */ + for (id = 0; id < GST_FLUPS_DEMUX_MAX_STREAMS; id++) { + GstFluPSStream *stream = demux->streams[id]; + + if (stream) { + stream->last_ts = GST_CLOCK_TIME_NONE; + } + } +} + +static void +gst_flups_demux_send_segment_updates (GstFluPSDemux * demux, + GstClockTime new_time) +{ + /* Advance all lagging streams by sending a segment update */ + gint id; + GstEvent *event = NULL; + + if (new_time > demux->src_segment.stop) + return; + + for (id = 0; id < GST_FLUPS_DEMUX_MAX_STREAMS; id++) { + GstFluPSStream *stream = demux->streams[id]; + + if (stream) { + if (stream->last_ts == GST_CLOCK_TIME_NONE || + stream->last_ts < demux->src_segment.start) + stream->last_ts = demux->src_segment.start; + if (stream->last_ts + SEGMENT_THRESHOLD < new_time) { +#if 0 + g_print ("Segment update to pad %s time %" GST_TIME_FORMAT "\n", + GST_PAD_NAME (stream->pad), GST_TIME_ARGS (new_time)); +#endif + if (event == NULL) { + event = gst_event_new_new_segment_full (TRUE, + demux->src_segment.rate, demux->src_segment.applied_rate, + GST_FORMAT_TIME, new_time, + demux->src_segment.stop, + demux->src_segment.time + (new_time - demux->src_segment.start)); + } + gst_event_ref (event); + gst_pad_push_event (stream->pad, event); + stream->last_ts = new_time; + } + } + } + + if (event) + gst_event_unref (event); +} + +static void +gst_flups_demux_send_segment_close (GstFluPSDemux * demux) +{ + gint id; + GstEvent *event = NULL; + + for (id = 0; id < GST_FLUPS_DEMUX_MAX_STREAMS; id++) { + GstFluPSStream *stream = demux->streams[id]; + + if (stream) { + if (stream->last_ts == GST_CLOCK_TIME_NONE || + stream->last_ts < demux->src_segment.start) + stream->last_ts = demux->src_segment.start; + +#if 0 + g_print ("Segment update to pad %s start %" GST_TIME_FORMAT + " stop %" GST_TIME_FORMAT "\n", + GST_PAD_NAME (stream->pad), GST_TIME_ARGS (stream->last_ts), + GST_TIME_ARGS (demux->src_segment.stop)); +#endif + event = gst_event_new_new_segment_full (TRUE, + demux->src_segment.rate, demux->src_segment.applied_rate, + GST_FORMAT_TIME, stream->last_ts, + demux->src_segment.stop, + demux->src_segment.time + + (stream->last_ts - demux->src_segment.start)); + gst_pad_push_event (stream->pad, event); + } + } +} + +static gboolean +gst_flups_demux_send_event (GstFluPSDemux * demux, GstEvent * event) +{ + gint id; + gboolean ret = FALSE; + + for (id = 0; id < GST_FLUPS_DEMUX_MAX_STREAMS; id++) { + GstFluPSStream *stream = demux->streams[id]; + + if (stream && !stream->notlinked) { + (void) gst_event_ref (event); + + if (!gst_pad_push_event (stream->pad, event)) { + GST_DEBUG_OBJECT (stream, "event %s was not handled correctly by pad %" + GST_PTR_FORMAT, GST_EVENT_TYPE_NAME (event), stream->pad); + } else { + /* If at least one push returns TRUE, then we return TRUE. */ + GST_DEBUG_OBJECT (stream, "event %s was handled correctly by pad %" + GST_PTR_FORMAT, GST_EVENT_TYPE_NAME (event), stream->pad); + ret = TRUE; + } + } + } + + gst_event_unref (event); + return ret; +} + +static gboolean +gst_flups_demux_handle_dvd_event (GstFluPSDemux * demux, GstEvent * event) +{ + const GstStructure *structure = gst_event_get_structure (event); + const char *type = gst_structure_get_string (structure, "event"); + gint i; + gchar cur_stream_name[32]; + GstFluPSStream *temp; + + if (strcmp (type, "dvd-lang-codes") == 0) { + GstEvent **p_ev; + /* Store the language codes event on the element, then iterate over the + * streams it specifies and retrieve them. The stream creation code then + * creates the pad appropriately and sends tag events as needed */ + p_ev = &demux->lang_codes; + gst_event_replace (p_ev, event); + + GST_ERROR_OBJECT (demux, "*********************\nLang codes event %s", + gst_structure_to_string (structure)); + + GST_DEBUG_OBJECT (demux, "Handling language codes event"); + + /* Create a video pad to ensure have it before emit no more pads */ + temp = gst_flups_demux_get_stream (demux, 0xe0, ST_VIDEO_MPEG2); + + /* Read out the languages for audio streams and request each one that + * is present */ + for (i = 0; i < MAX_DVD_AUDIO_STREAMS; i++) { + gint stream_format; + gint stream_id; + + g_snprintf (cur_stream_name, 32, "audio-%d-format", i); + if (!gst_structure_get_int (structure, cur_stream_name, &stream_format)) + continue; + + switch (stream_format) { + case 0x0: + /* AC3 */ + stream_id = 0x80 + i; + temp = gst_flups_demux_get_stream (demux, stream_id, ST_PS_AUDIO_AC3); + break; + case 0x2: + case 0x3: + /* MPEG audio without and with extension stream are + * treated the same */ + stream_id = 0xC0 + i; + temp = gst_flups_demux_get_stream (demux, stream_id, ST_AUDIO_MPEG1); + break; + case 0x4: + /* LPCM */ + stream_id = 0xA0 + i; + temp = + gst_flups_demux_get_stream (demux, stream_id, ST_PS_AUDIO_LPCM); + break; + case 0x6: + /* DTS */ + stream_id = 0x88 + i; + temp = gst_flups_demux_get_stream (demux, stream_id, ST_PS_AUDIO_DTS); + break; + case 0x7: + /* FIXME: What range is SDDS? */ + break; + default: + GST_WARNING_OBJECT (demux, + "Unknown audio stream format in language code event: %d", + stream_format); + break; + } + } + + /* And subtitle streams */ + for (i = 0; i < MAX_DVD_SUBPICTURE_STREAMS; i++) { + gint stream_format; + g_snprintf (cur_stream_name, 32, "subpicture-%d-format", i); + + if (!gst_structure_get_int (structure, cur_stream_name, &stream_format)) + continue; + + /* Retrieve the subpicture stream to force pad creation */ + temp = gst_flups_demux_get_stream (demux, 0x20 + i, ST_PS_DVD_SUBPICTURE); + } + + GST_DEBUG_OBJECT (demux, "Created all pads from Language Codes event, " + "signalling no-more-pads"); + + gst_element_no_more_pads (GST_ELEMENT (demux)); + demux->need_no_more_pads = FALSE; + gst_event_unref (event); + } else { + gst_flups_demux_send_event (demux, event); + } + return TRUE; +} + +static gboolean +gst_flups_demux_sink_event (GstPad * pad, GstEvent * event) +{ + gboolean res = TRUE; + GstFluPSDemux *demux; + + demux = GST_FLUPS_DEMUX (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_START: + gst_flups_demux_send_event (demux, event); + break; + case GST_EVENT_FLUSH_STOP: + gst_flups_demux_send_event (demux, event); + + gst_segment_init (&demux->sink_segment, GST_FORMAT_UNDEFINED); + gst_segment_init (&demux->src_segment, GST_FORMAT_TIME); + gst_adapter_clear (demux->adapter); + gst_adapter_clear (demux->rev_adapter); + demux->adapter_offset = G_MAXUINT64; + gst_pes_filter_drain (&demux->filter); + demux->current_scr = G_MAXUINT64; + demux->bytes_since_scr = 0; + demux->scr_adjust = GSTTIME_TO_MPEGTIME (SCR_MUNGE); + gst_flups_demux_clear_times (demux); + break; + case GST_EVENT_NEWSEGMENT: + { + gboolean update; + gdouble rate; + GstFormat format; + gint64 start, stop, time; + gint64 accum, dur; + gdouble arate; + GstClockTimeDiff adjust; + + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + + if (format != GST_FORMAT_TIME) + return FALSE; + + dur = stop - start; + + demux->first_scr = GSTTIME_TO_MPEGTIME (start); + demux->current_scr = demux->first_scr + demux->scr_adjust; + demux->base_time = time; + demux->bytes_since_scr = 0; + + gst_segment_set_newsegment_full (&demux->sink_segment, update, rate, + arate, format, start, stop, time); + + g_print ("demux: got segment update %d start %" G_GINT64_FORMAT + " stop %" G_GINT64_FORMAT " time %" G_GINT64_FORMAT "\n", + update, start, stop, time); + + accum = demux->sink_segment.accum; + start = demux->sink_segment.start; + stop = demux->sink_segment.stop; + + adjust = accum - start + SCR_MUNGE; + start = accum + SCR_MUNGE; + + if (adjust >= 0) + demux->scr_adjust = GSTTIME_TO_MPEGTIME (adjust); + else + demux->scr_adjust = -GSTTIME_TO_MPEGTIME (-adjust); + + if (stop != -1) + stop = start + dur; + + GST_INFO_OBJECT (demux, "sending new segment: rate %g " + "format %d, start: %" G_GINT64_FORMAT ", stop: %" G_GINT64_FORMAT + ", time: %" G_GINT64_FORMAT, rate, format, start, stop, time); + + g_print ("sending new segment: update %d rate %g format %d, start: %" + G_GINT64_FORMAT ", stop: %" G_GINT64_FORMAT ", time: %" + G_GINT64_FORMAT " scr_adjust: %" G_GINT64_FORMAT + "(%" GST_TIME_FORMAT ")\n", + update, rate, format, start, stop, time, demux->scr_adjust, + GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (demux->scr_adjust))); + + gst_segment_set_newsegment_full (&demux->src_segment, update, + rate, arate, format, start, stop, time); + + gst_event_unref (event); + if (update) { + /* Segment closing, send it as per-pad updates to manage the accum + * properly */ + gst_flups_demux_send_segment_close (demux); + } else { + event = gst_event_new_new_segment_full (update, + rate, arate, GST_FORMAT_TIME, start, stop, time); + gst_flups_demux_send_event (demux, event); + } + + break; + } + case GST_EVENT_EOS: + GST_INFO_OBJECT (demux, "Received EOS"); + if (!gst_flups_demux_send_event (demux, event)) { + GST_WARNING_OBJECT (demux, "failed pushing EOS on streams"); + GST_ELEMENT_ERROR (demux, STREAM, FAILED, + ("Internal data stream error."), ("Can't push EOS downstream")); + } + break; + case GST_EVENT_CUSTOM_DOWNSTREAM: + case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: + { + const GstStructure *structure = gst_event_get_structure (event); + + if (structure != NULL + && gst_structure_has_name (structure, "application/x-gst-dvd")) { + res = gst_flups_demux_handle_dvd_event (demux, event); + } else { + gst_flups_demux_send_event (demux, event); + } + break; + } + default: + gst_flups_demux_send_event (demux, event); + break; + } + + gst_object_unref (demux); + + return res; +} + +static gboolean +gst_flups_demux_src_event (GstPad * pad, GstEvent * event) +{ + gboolean res = FALSE; + GstFluPSDemux *demux; + + demux = GST_FLUPS_DEMUX (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + { + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType start_type, stop_type; + gint64 start, stop; + gint64 bstart, bstop; + GstEvent *bevent; + + gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start, + &stop_type, &stop); + + GST_DEBUG_OBJECT (demux, "seek event, rate: %f start: %" GST_TIME_FORMAT + " stop: %" GST_TIME_FORMAT, rate, GST_TIME_ARGS (start), + GST_TIME_ARGS (stop)); + + if (format == GST_FORMAT_BYTES) { + GST_DEBUG_OBJECT (demux, "seek not supported on format %d", format); + goto not_supported; + } + + GST_DEBUG_OBJECT (demux, "seek - trying directly upstream first"); + + /* first try original format seek */ + (void) gst_event_ref (event); + if ((res = gst_pad_push_event (demux->sinkpad, event))) + goto done; + + if (format != GST_FORMAT_TIME) { + /* From here down, we only support time based seeks */ + GST_DEBUG_OBJECT (demux, "seek not supported on format %d", format); + goto not_supported; + } + + /* We need to convert to byte based seek and we need a scr_rate for that. */ + if (demux->scr_rate_n == G_MAXUINT64 || demux->scr_rate_d == G_MAXUINT64) { + GST_DEBUG_OBJECT (demux, "seek not possible, no scr_rate"); + goto not_supported; + } + + GST_DEBUG_OBJECT (demux, "try with scr_rate interpolation"); + + bstart = GSTTIME_TO_BYTES (start); + bstop = GSTTIME_TO_BYTES (stop); + + GST_DEBUG_OBJECT (demux, "in bytes bstart %" G_GINT64_FORMAT " bstop %" + G_GINT64_FORMAT, bstart, bstop); + bevent = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, start_type, + bstart, stop_type, bstop); + + res = gst_pad_push_event (demux->sinkpad, bevent); + + done: + gst_event_unref (event); + break; + } + default: + res = gst_pad_push_event (demux->sinkpad, event); + break; + } + + gst_object_unref (demux); + + return res; + +not_supported: + { + gst_object_unref (demux); + gst_event_unref (event); + + return FALSE; + } +} + +static gboolean +gst_flups_demux_src_query (GstPad * pad, GstQuery * query) +{ + gboolean res = FALSE; + GstFluPSDemux *demux; + + demux = GST_FLUPS_DEMUX (gst_pad_get_parent (pad)); + + GST_LOG_OBJECT (demux, "Have query of type %d on pad %" GST_PTR_FORMAT, + GST_QUERY_TYPE (query), pad); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + { + GstPad *peer; + GstFormat format; + gint64 position; + + gst_query_parse_position (query, &format, NULL); + + if ((peer = gst_pad_get_peer (demux->sinkpad)) != NULL) { + res = gst_pad_query (peer, query); + gst_object_unref (peer); + if (res) + break; + } + + if (format != GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (demux, "position not supported for format %d", + format); + goto not_supported; + } + + position = demux->base_time; + if (demux->current_scr != G_MAXUINT64 && demux->first_scr != G_MAXUINT64) { + position += + MPEGTIME_TO_GSTTIME (demux->current_scr - demux->scr_adjust - + demux->first_scr); + } + + GST_LOG_OBJECT (demux, "Position at GStreamer Time:%" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + + gst_query_set_position (query, format, position); + res = TRUE; + break; + } + case GST_QUERY_DURATION: + { + GstFormat format; + gint64 duration; + GstPad *peer; + + gst_query_parse_duration (query, &format, NULL); + + if ((peer = gst_pad_get_peer (demux->sinkpad)) == NULL) { + GST_DEBUG_OBJECT (demux, "duration not possible, no peer"); + goto not_supported; + } + + /* For any format other than bytes, see if upstream knows first */ + if (format == GST_FORMAT_BYTES) { + GST_DEBUG_OBJECT (demux, "duration not supported for format %d", + format); + gst_object_unref (peer); + goto not_supported; + } + + if (gst_pad_query (peer, query)) { + gst_object_unref (peer); + res = TRUE; + break; + } + + /* Upstream didn't know, so we can only answer TIME queries from + * here on */ + if (format != GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (demux, "duration not supported for format %d", + format); + gst_object_unref (peer); + goto not_supported; + } + + if (demux->mux_rate == -1) { + GST_DEBUG_OBJECT (demux, "duration not possible, no mux_rate"); + gst_object_unref (peer); + goto not_supported; + } + + gst_query_set_duration (query, GST_FORMAT_BYTES, -1); + + if (!gst_pad_query (peer, query)) { + GST_LOG_OBJECT (demux, "query on peer pad failed"); + gst_object_unref (peer); + goto not_supported; + } + gst_object_unref (peer); + + gst_query_parse_duration (query, &format, &duration); + + duration = BYTES_TO_GSTTIME (duration); + + gst_query_set_duration (query, GST_FORMAT_TIME, duration); + res = TRUE; + break; + } + default: + res = gst_pad_query_default (pad, query); + break; + } + + gst_object_unref (demux); + + return res; + +not_supported: + { + gst_object_unref (demux); + + return FALSE; + } +} + +static void +gst_flups_demux_reset_psm (GstFluPSDemux * demux) +{ + gint i; + +#define FILL_TYPE(start, stop, type) \ + for (i=start; i <= stop; i++) \ + demux->psm[i] = type; + + FILL_TYPE (0x00, 0x1f, -1); + FILL_TYPE (0x20, 0x3f, ST_PS_DVD_SUBPICTURE); + FILL_TYPE (0x40, 0x7f, -1); + FILL_TYPE (0x80, 0x87, ST_PS_AUDIO_AC3); + FILL_TYPE (0x88, 0x9f, ST_PS_AUDIO_DTS); + FILL_TYPE (0xa0, 0xbf, ST_PS_AUDIO_LPCM); + FILL_TYPE (0xbd, 0xbd, -1); + FILL_TYPE (0xc0, 0xdf, ST_AUDIO_MPEG1); + FILL_TYPE (0xe0, 0xef, ST_GST_VIDEO_MPEG1_OR_2); + FILL_TYPE (0xf0, 0xff, -1); + +#undef FILL_TYPE +} + +static GstFlowReturn +gst_flups_demux_parse_pack_start (GstFluPSDemux * demux) +{ + const guint8 *data; + guint length; + guint32 scr1, scr2; + guint64 scr, scr_adjusted, new_rate; + GstClockTime new_time; + + GST_DEBUG ("parsing pack start"); + + /* fixed length to begin with, start code and two scr values */ + length = 8 + 4; + + if (!(data = gst_adapter_peek (demux->adapter, length))) + goto need_more_data; + + /* skip start code */ + data += 4; + + scr1 = GUINT32_FROM_BE (*(guint32 *) data); + scr2 = GUINT32_FROM_BE (*(guint32 *) (data + 4)); + + /* start parsing the stream */ + if ((*data & 0xc0) == 0x40) { + guint32 scr_ext; + guint32 next32; + guint8 stuffing_bytes; + + GST_DEBUG ("Found MPEG2 stream"); + demux->is_mpeg2_pack = TRUE; + + /* mpeg2 has more data */ + length += 2; + if (gst_adapter_available (demux->adapter) < length) + goto need_more_data; + + /* :2=01 ! scr:3 ! marker:1==1 ! scr:15 ! marker:1==1 ! scr:15 */ + + /* check markers */ + if ((scr1 & 0xc4000400) != 0x44000400) + goto lost_sync; + + scr = ((guint64) scr1 & 0x38000000) << 3; + scr |= ((guint64) scr1 & 0x03fff800) << 4; + scr |= ((guint64) scr1 & 0x000003ff) << 5; + scr |= ((guint64) scr2 & 0xf8000000) >> 27; + + /* marker:1==1 ! scr_ext:9 ! marker:1==1 */ + if ((scr2 & 0x04010000) != 0x04010000) + goto lost_sync; + + scr_ext = (scr2 & 0x03fe0000) >> 17; + /* We keep the offset of this scr */ + demux->last_scr_offset = demux->adapter_offset + 12; + + GST_DEBUG_OBJECT (demux, "SCR: 0x%08x SCRE: 0x%08x", scr, scr_ext); + + if (scr_ext) { + scr = (scr * 300 + scr_ext % 300) / 300; + } + /* SCR has been converted into units of 90Khz ticks to make it comparable + to DTS/PTS, that also implies 1 tick rounding error */ + data += 6; + /* PMR:22 ! :2==11 ! reserved:5 ! stuffing_len:3 */ + next32 = (GUINT32_FROM_BE ((*(guint32 *) data))); + if ((next32 & 0x00000300) != 0x00000300) + goto lost_sync; + + new_rate = (next32 & 0xfffffc00) >> 10; + + stuffing_bytes = (next32 & 0x07); + GST_DEBUG_OBJECT (demux, "stuffing bytes: %d", stuffing_bytes); + + data += 4; + while (stuffing_bytes--) { + if (*data++ != 0xff) + goto lost_sync; + } + } else { + GST_DEBUG ("Found MPEG1 stream"); + demux->is_mpeg2_pack = FALSE; + + /* check markers */ + if ((scr1 & 0xf1000100) != 0x21000100) + goto lost_sync; + + if ((scr2 & 0x01800001) != 0x01800001) + goto lost_sync; + + /* :4=0010 ! scr:3 ! marker:1==1 ! scr:15 ! marker:1==1 ! scr:15 ! marker:1==1 */ + scr = ((guint64) scr1 & 0x0e000000) << 5; + scr |= ((guint64) scr1 & 0x00fffe00) << 6; + scr |= ((guint64) scr1 & 0x000000ff) << 7; + scr |= ((guint64) scr2 & 0xfe000000) >> 25; + + /* We keep the offset of this scr */ + demux->last_scr_offset = demux->adapter_offset + 8; + + /* marker:1==1 ! mux_rate:22 ! marker:1==1 */ + new_rate = (scr2 & 0x007ffffe) >> 1; + + data += 8; + } + new_rate *= MPEG_MUX_RATE_MULT; + + /* scr adjusted is the new scr found + the colected adjustment */ + scr_adjusted = scr + demux->scr_adjust; + + /* keep the first src in order to calculate delta time */ + if (demux->first_scr == G_MAXUINT64) { + demux->first_scr = scr; + demux->first_scr_offset = demux->last_scr_offset; + + if (demux->sink_segment.format == GST_FORMAT_TIME) { + demux->base_time = demux->sink_segment.time; + } else { + demux->base_time = MPEGTIME_TO_GSTTIME (demux->first_scr); + } + /* at begin consider the new_rate as the scr rate, bytes/clock ticks */ + demux->scr_rate_n = new_rate; + demux->scr_rate_d = CLOCK_FREQ; + } else if (demux->first_scr_offset != demux->last_scr_offset) { + /* estimate byte rate related to the SCR */ + demux->scr_rate_n = demux->last_scr_offset - demux->first_scr_offset; + demux->scr_rate_d = scr - demux->first_scr; + } + + GST_DEBUG_OBJECT (demux, + "SCR: %" G_GINT64_FORMAT " (%" G_GINT64_FORMAT "), mux_rate %" + G_GINT64_FORMAT ", GStreamer Time:%" GST_TIME_FORMAT, + scr, scr_adjusted, new_rate, + GST_TIME_ARGS (MPEGTIME_TO_GSTTIME ((guint64) scr - demux->first_scr))); + + GST_DEBUG_OBJECT (demux, "%s mode scr: %" G_GUINT64_FORMAT " at %" + G_GUINT64_FORMAT ", first scr: %" G_GUINT64_FORMAT + " at %" G_GUINT64_FORMAT ", scr rate: %" G_GUINT64_FORMAT + "/%" G_GUINT64_FORMAT "(%f)", + ((demux->sink_segment.rate >= 0.0) ? "forward" : "backward"), + scr, demux->last_scr_offset, + demux->first_scr, demux->first_scr_offset, + demux->scr_rate_n, demux->scr_rate_d, + (float) demux->scr_rate_n / demux->scr_rate_d); + + /* adjustment of the SCR */ + if (demux->current_scr != G_MAXUINT64) { + gint64 diff; + guint64 old_scr, old_mux_rate, bss, adjust; + + /* keep SCR of the previous packet */ + old_scr = demux->current_scr; + old_mux_rate = demux->mux_rate; + + /* Bytes since SCR is the amount we placed in the adapter since then + * (demux->bytes_since_scr) minus the amount remaining in the adapter, + * clamped to >= 0 */ + bss = MAX (0, (gint) (demux->bytes_since_scr - + gst_adapter_available (demux->adapter))); + + /* estimate the new SCR using the previous one according the notes + on point 2.5.2.2 of the ISO/IEC 13818-1 document */ + adjust = (bss * CLOCK_FREQ) / old_mux_rate; + if (demux->sink_segment.rate >= 0.0) + demux->next_scr = old_scr + adjust; + else + demux->next_scr = old_scr - adjust; + + GST_DEBUG_OBJECT (demux, + "bss: %" G_GUINT64_FORMAT ", next_scr: %" G_GUINT64_FORMAT + ", old_scr: %" G_GUINT64_FORMAT ", scr: %" G_GUINT64_FORMAT, + bss, demux->next_scr, old_scr, scr_adjusted); + + /* calculate the absolute deference between the last scr and + the new one */ + if (old_scr > scr_adjusted) + diff = old_scr - scr_adjusted; + else + diff = scr_adjusted - old_scr; + + /* if the difference is more than 1 second we need to reconfigure + adjustment */ + if (diff > CLOCK_FREQ) { +#if 0 + demux->scr_adjust = demux->next_scr - scr; + GST_DEBUG_OBJECT (demux, "discont found, diff: %" G_GINT64_FORMAT + ", adjust %" G_GINT64_FORMAT, diff, demux->scr_adjust); + scr_adjusted = demux->next_scr; +#else + g_print ("Unexpected SCR diff of %" G_GINT64_FORMAT "\n", diff); +#endif + } else { + demux->next_scr = scr_adjusted; + } + } + + /* update the current_scr and rate members */ + demux->mux_rate = new_rate; + demux->current_scr = scr_adjusted; + + new_time = MPEGTIME_TO_GSTTIME (scr_adjusted); + if (new_time != GST_CLOCK_TIME_NONE) { + // g_print ("SCR now %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (new_time)); + gst_flups_demux_send_segment_updates (demux, new_time); + demux->src_segment.last_stop = new_time; + } + + /* Reset the bytes_since_scr value to count the data remaining in the + * adapter */ + demux->bytes_since_scr = gst_adapter_available (demux->adapter); + + gst_adapter_flush (demux->adapter, length); + ADAPTER_OFFSET_FLUSH (length); + return GST_FLOW_OK; + +lost_sync: + { + GST_DEBUG_OBJECT (demux, "lost sync"); + return GST_FLOW_LOST_SYNC; + } +need_more_data: + { + GST_DEBUG_OBJECT (demux, "need more data"); + return GST_FLOW_NEED_MORE_DATA; + } +} + +static GstFlowReturn +gst_flups_demux_parse_sys_head (GstFluPSDemux * demux) +{ + guint16 length; + const guint8 *data; + gboolean csps; + + /* start code + length */ + if (!(data = gst_adapter_peek (demux->adapter, 6))) + goto need_more_data; + + /* skip start code */ + data += 4; + + length = GST_READ_UINT16_BE (data); + GST_DEBUG_OBJECT (demux, "length %d", length); + + length += 6; + + if (!(data = gst_adapter_peek (demux->adapter, length))) + goto need_more_data; + + /* skip start code and length */ + data += 6; + + /* marker:1==1 ! rate_bound:22 | marker:1==1 */ + if ((*data & 0x80) != 0x80) + goto marker_expected; + + { + guint32 rate_bound; + + if ((data[2] & 0x01) != 0x01) + goto marker_expected; + + rate_bound = ((guint32) data[0] & 0x7f) << 15; + rate_bound |= ((guint32) data[1]) << 7; + rate_bound |= ((guint32) data[2] & 0xfe) >> 1; + rate_bound *= MPEG_MUX_RATE_MULT; + + GST_DEBUG_OBJECT (demux, "rate bound %u", rate_bound); + + data += 3; + } + + /* audio_bound:6==1 ! fixed:1 | constrained:1 */ + { + guint8 audio_bound; + gboolean fixed; + + /* max number of simultaneous audio streams active */ + audio_bound = (data[0] & 0xfc) >> 2; + /* fixed or variable bitrate */ + fixed = (data[0] & 0x02) == 0x02; + /* meeting constraints */ + csps = (data[0] & 0x01) == 0x01; + + GST_DEBUG_OBJECT (demux, "audio_bound %d, fixed %d, constrained %d", + audio_bound, fixed, csps); + data += 1; + } + + /* audio_lock:1 | video_lock:1 | marker:1==1 | video_bound:5 */ + { + gboolean audio_lock; + gboolean video_lock; + guint8 video_bound; + + audio_lock = (data[0] & 0x80) == 0x80; + video_lock = (data[0] & 0x40) == 0x40; + + if ((data[0] & 0x20) != 0x20) + goto marker_expected; + + /* max number of simultaneous video streams active */ + video_bound = (data[0] & 0x1f); + + GST_DEBUG_OBJECT (demux, "audio_lock %d, video_lock %d, video_bound %d", + audio_lock, video_lock, video_bound); + data += 1; + } + + /* packet_rate_restriction:1 | reserved:7==0x7F */ + { + gboolean packet_rate_restriction; + + if ((data[0] & 0x7f) != 0x7f) + goto marker_expected; + + /* only valid if csps is set */ + if (csps) { + packet_rate_restriction = (data[0] & 0x80) == 0x80; + + GST_DEBUG_OBJECT (demux, "packet_rate_restriction %d", + packet_rate_restriction); + } + } + data += 1; + + { + gint stream_count = (length - 12) / 3; + gint i; + + GST_DEBUG_OBJECT (demux, "number of streams: %d ", stream_count); + + for (i = 0; i < stream_count; i++) { + guint8 stream_id; + gboolean STD_buffer_bound_scale; + guint16 STD_buffer_size_bound; + guint32 buf_byte_size_bound; + + stream_id = *data++; + if (!(stream_id & 0x80)) + goto sys_len_error; + + /* check marker bits */ + if ((*data & 0xC0) != 0xC0) + goto no_placeholder_bits; + + STD_buffer_bound_scale = *data & 0x20; + STD_buffer_size_bound = ((guint16) (*data++ & 0x1F)) << 8; + STD_buffer_size_bound |= *data++; + + if (STD_buffer_bound_scale == 0) { + buf_byte_size_bound = STD_buffer_size_bound * 128; + } else { + buf_byte_size_bound = STD_buffer_size_bound * 1024; + } + + GST_DEBUG_OBJECT (demux, "STD_buffer_bound_scale %d", + STD_buffer_bound_scale); + GST_DEBUG_OBJECT (demux, "STD_buffer_size_bound %d or %d bytes", + STD_buffer_size_bound, buf_byte_size_bound); + } + } + + gst_adapter_flush (demux->adapter, length); + ADAPTER_OFFSET_FLUSH (length); + return GST_FLOW_OK; + + /* ERRORS */ +marker_expected: + { + GST_DEBUG_OBJECT (demux, "expecting marker"); + return GST_FLOW_LOST_SYNC; + } +no_placeholder_bits: + { + GST_DEBUG_OBJECT (demux, "expecting placeholder bit values" + " '11' after stream id"); + return GST_FLOW_LOST_SYNC; + } +sys_len_error: + { + GST_DEBUG_OBJECT (demux, "error in system header length"); + return GST_FLOW_LOST_SYNC; + } +need_more_data: + { + GST_DEBUG_OBJECT (demux, "need more data"); + return GST_FLOW_NEED_MORE_DATA; + } +} + +static GstFlowReturn +gst_flups_demux_parse_psm (GstFluPSDemux * demux) +{ + guint16 length = 0, info_length = 0, es_map_length = 0; + guint8 psm_version = 0; + const guint8 *data, *es_map_base; + gboolean applicable; + + /* start code + length */ + if (!(data = gst_adapter_peek (demux->adapter, 6))) + goto need_more_data; + + /* skip start code */ + data += 4; + + length = GST_READ_UINT16_BE (data); + GST_DEBUG_OBJECT (demux, "length %u", length); + + if (G_UNLIKELY (length > 0x3FA)) + goto psm_len_error; + + length += 6; + + if (!(data = gst_adapter_peek (demux->adapter, length))) + goto need_more_data; + + /* skip start code and length */ + data += 6; + + /* Read PSM applicable bit together with version */ + psm_version = GST_READ_UINT8 (data); + applicable = (psm_version & 0x80) >> 7; + psm_version &= 0x1F; + GST_DEBUG_OBJECT (demux, "PSM version %u (applicable now %u)", psm_version, + applicable); + + /* Jump over version and marker bit */ + data += 2; + + /* Read PS info length */ + info_length = GST_READ_UINT16_BE (data); + /* Cap it to PSM length - needed bytes for ES map length and CRC */ + info_length = MIN (length - 16, info_length); + GST_DEBUG_OBJECT (demux, "PS info length %u bytes", info_length); + + /* Jump over that section */ + data += (2 + info_length); + + /* Read ES map length */ + es_map_length = GST_READ_UINT16_BE (data); + /* Cap it to PSM remaining length - CRC */ + es_map_length = MIN (length - (16 + info_length), es_map_length); + GST_DEBUG_OBJECT (demux, "ES map length %u bytes", es_map_length); + + /* Jump over the size */ + data += 2; + + /* Now read the ES map */ + es_map_base = data; + while (es_map_base + 4 <= data + es_map_length) { + guint8 stream_type = 0, stream_id = 0; + guint16 stream_info_length = 0; + + stream_type = GST_READ_UINT8 (es_map_base); + es_map_base++; + stream_id = GST_READ_UINT8 (es_map_base); + es_map_base++; + stream_info_length = GST_READ_UINT16_BE (es_map_base); + es_map_base += 2; + /* Cap stream_info_length */ + stream_info_length = MIN (data + es_map_length - es_map_base, + stream_info_length); + + GST_DEBUG_OBJECT (demux, "Stream type %02X with id %02X and %u bytes info", + stream_type, stream_id, stream_info_length); + demux->psm[stream_id] = stream_type; + es_map_base += stream_info_length; + } + + gst_adapter_flush (demux->adapter, length); + ADAPTER_OFFSET_FLUSH (length); + return GST_FLOW_OK; + +psm_len_error: + { + GST_DEBUG_OBJECT (demux, "error in PSM length"); + return GST_FLOW_LOST_SYNC; + } +need_more_data: + { + GST_DEBUG_OBJECT (demux, "need more data"); + return GST_FLOW_NEED_MORE_DATA; + } +} + +static void +gst_flups_demux_resync_cb (GstPESFilter * filter, GstFluPSDemux * demux) +{ +} + +static GstFlowReturn +gst_flups_demux_data_cb (GstPESFilter * filter, gboolean first, + GstBuffer * buffer, GstFluPSDemux * demux) +{ + GstBuffer *out_buf; + GstFlowReturn ret = GST_FLOW_OK; + gint stream_type; + guint32 start_code; + guint8 id; + guint8 *data; + guint datalen; + guint offset = 0; + + data = GST_BUFFER_DATA (buffer); + datalen = GST_BUFFER_SIZE (buffer); + + start_code = filter->start_code; + id = filter->id; + + if (first) { + /* find the stream type */ + stream_type = demux->psm[id]; + if (stream_type == -1) { + /* no stream type, if PS1, get the new id */ + if (start_code == ID_PRIVATE_STREAM_1 && datalen >= 2) { + guint8 nframes; + + /* VDR writes A52 streams without any header bytes + * (see ftp://ftp.mplayerhq.hu/MPlayer/samples/MPEG-VOB/vdr-AC3) */ + if (datalen >= 4) { + guint hdr = GST_READ_UINT32_BE (data); + + if (G_UNLIKELY ((hdr & 0xffff0000) == AC3_SYNC_WORD)) { + id = 0x80; + stream_type = demux->psm[id] = ST_GST_AUDIO_RAWA52; + GST_DEBUG_OBJECT (demux, "Found VDR raw A52 stream"); + } + } + + if (G_LIKELY (stream_type == -1)) { + /* new id is in the first byte */ + id = data[offset++]; + datalen--; + + /* and remap */ + stream_type = demux->psm[id]; + + /* Now, if it's a subpicture stream - no more, otherwise + * take the first byte too, since it's the frame count in audio + * streams and our backwards compat convention is to strip it off */ + if (stream_type != ST_PS_DVD_SUBPICTURE) { + /* Number of audio frames in this packet */ + nframes = data[offset++]; + datalen--; + GST_DEBUG_OBJECT (demux, "private type 0x%02x, %d frames", id, + nframes); + } else { + GST_DEBUG_OBJECT (demux, "private type 0x%02x, stream type %d", id, + stream_type); + } + } + } + if (stream_type == -1) + goto unknown_stream_type; + } + if (filter->pts != -1) { + demux->next_pts = filter->pts + demux->scr_adjust; + GST_DEBUG_OBJECT (demux, "PTS = %" G_GUINT64_FORMAT + "(%" G_GUINT64_FORMAT ")", filter->pts, demux->next_pts); + } else + demux->next_pts = G_MAXUINT64; + + if (filter->dts != -1) { + demux->next_dts = filter->dts + demux->scr_adjust; + } else { + demux->next_dts = demux->next_pts; + } + GST_DEBUG_OBJECT (demux, "DTS = orig %" G_GUINT64_FORMAT + " (%" G_GUINT64_FORMAT ")", filter->dts, demux->next_dts); + + demux->current_stream = gst_flups_demux_get_stream (demux, id, stream_type); + } + + if (demux->current_stream == NULL) { + GST_DEBUG_OBJECT (demux, "Dropping buffer for unknown stream id 0x%02x", + id); + goto done; + } + + /* After 2 seconds of bitstream emit no more pads */ + if (demux->need_no_more_pads + && (demux->current_scr - demux->first_scr - demux->scr_adjust) > + 2 * CLOCK_FREQ) { + GST_DEBUG_OBJECT (demux, "no more pads, notifying"); + gst_element_no_more_pads (GST_ELEMENT_CAST (demux)); + demux->need_no_more_pads = FALSE; + } + + /* If the stream is not-linked, don't bother creating a sub-buffer + * to send to it, unless we're processing a discont (which resets + * the not-linked status and tries again */ + if (demux->current_stream->discont) { + GST_DEBUG_OBJECT (demux, "stream is discont"); + demux->current_stream->notlinked = FALSE; + } + + if (demux->current_stream->notlinked == FALSE) { + out_buf = gst_buffer_create_sub (buffer, offset, datalen); + + ret = gst_flups_demux_send_data (demux, demux->current_stream, out_buf); + if (ret == GST_FLOW_NOT_LINKED) { + demux->current_stream->notlinked = TRUE; + ret = GST_FLOW_OK; + } + } + +done: + gst_buffer_unref (buffer); + + return ret; + + /* ERRORS */ +unknown_stream_type: + { + GST_DEBUG_OBJECT (demux, "unknown stream type %02x", id); + ret = GST_FLOW_OK; + goto done; + } +} + +static gboolean +gst_flups_demux_resync (GstFluPSDemux * demux, gboolean save) +{ + const guint8 *data; + gint avail; + guint32 code; + gint offset; + gboolean found; + + avail = gst_adapter_available (demux->adapter); + if (avail < 4) + goto need_data; + + /* Common case, read 4 bytes an check it */ + data = gst_adapter_peek (demux->adapter, 4); + + /* read currect code */ + code = GST_READ_UINT32_BE (data); + + /* The common case is that the sync code is at 0 bytes offset */ + if (G_LIKELY ((code & 0xffffff00) == 0x100L)) { + GST_LOG_OBJECT (demux, "Found resync code %08x after 0 bytes", code); + demux->last_sync_code = code; + return TRUE; + } + + /* Otherwise, we are starting at byte 4 and we need to search + the sync code in all available data in the adapter */ + offset = 4; + if (offset >= avail) + goto need_data; /* Not enough data to find sync */ + + data = gst_adapter_peek (demux->adapter, avail); + + do { + code = (code << 8) | data[offset++]; + found = (code & 0xffffff00) == 0x100L; + } while (offset < avail && !found); + + if (!save || demux->sink_segment.rate >= 0.0) { + GST_LOG_OBJECT (demux, "flushing %d bytes", offset - 4); + /* forward playback, we can discard and flush the skipped bytes */ + gst_adapter_flush (demux->adapter, offset - 4); + ADAPTER_OFFSET_FLUSH (offset - 4); + } else { + if (found) { + GST_LOG_OBJECT (demux, "reverse saving %d bytes", offset - 4); + /* reverse playback, we keep the flushed bytes and we will append them to + * the next buffer in the chain function, which is the previous buffer in + * the stream. */ + gst_adapter_push (demux->rev_adapter, + gst_adapter_take_buffer (demux->adapter, offset - 4)); + } else { + GST_LOG_OBJECT (demux, "reverse saving %d bytes", avail); + /* nothing found, keep all bytes */ + gst_adapter_push (demux->rev_adapter, + gst_adapter_take_buffer (demux->adapter, avail)); + } + } + + if (found) { + GST_LOG_OBJECT (demux, "Found resync code %08x after %d bytes", + code, offset - 4); + demux->last_sync_code = code; + } else { + GST_LOG_OBJECT (demux, "No resync after skipping %d", offset); + } + + return found; + +need_data: + { + GST_LOG_OBJECT (demux, "we need more data for resync %d", avail); + return FALSE; + } +} + +static gboolean +gst_flups_demux_is_pes_sync (guint32 sync) +{ + return ((sync & 0xfc) == 0xbc) || + ((sync & 0xe0) == 0xc0) || ((sync & 0xf0) == 0xe0); +} + +static GstFlowReturn +gst_flups_demux_chain (GstPad * pad, GstBuffer * buffer) +{ + GstFluPSDemux *demux = GST_FLUPS_DEMUX (gst_pad_get_parent (pad)); + GstFlowReturn ret = GST_FLOW_OK; + guint32 avail; + gboolean save, discont; + + discont = GST_BUFFER_IS_DISCONT (buffer); + + if (discont) { + GST_LOG_OBJECT (demux, "Received buffer with discont flag and" + " offset %" G_GUINT64_FORMAT, GST_BUFFER_OFFSET (buffer)); + + gst_pes_filter_drain (&demux->filter); + gst_flups_demux_mark_discont (demux); + + /* mark discont on all streams */ + if (demux->sink_segment.rate >= 0.0) { + demux->current_scr = G_MAXUINT64; + demux->bytes_since_scr = 0; + } + } else { + GST_LOG_OBJECT (demux, "Received buffer with offset %" G_GUINT64_FORMAT, + GST_BUFFER_OFFSET (buffer)); + } + + /* We keep the offset to interpolate SCR */ + demux->adapter_offset = GST_BUFFER_OFFSET (buffer); + + gst_adapter_push (demux->adapter, buffer); + demux->bytes_since_scr += GST_BUFFER_SIZE (buffer); + + avail = gst_adapter_available (demux->rev_adapter); + if (avail > 0) { + GST_LOG_OBJECT (demux, "appending %u saved bytes", avail); + /* if we have a previous reverse chunk, append this now */ + /* FIXME this code assumes we receive discont buffers all thei + * time */ + gst_adapter_push (demux->adapter, + gst_adapter_take_buffer (demux->rev_adapter, avail)); + } + + avail = gst_adapter_available (demux->adapter); + GST_LOG_OBJECT (demux, "avail now: %d, state %d", avail, demux->filter.state); + + switch (demux->filter.state) { + case STATE_DATA_SKIP: + case STATE_DATA_PUSH: + ret = gst_pes_filter_process (&demux->filter); + break; + case STATE_HEADER_PARSE: + break; + default: + break; + } + + switch (ret) { + case GST_FLOW_NEED_MORE_DATA: + /* Go and get more data */ + ret = GST_FLOW_OK; + goto done; + case GST_FLOW_LOST_SYNC: + /* for FLOW_OK or lost-sync, carry onto resync */ + ret = GST_FLOW_OK; + break; + case GST_FLOW_OK: + break; + default: + /* Any other return value should be sent upstream immediately */ + goto done; + } + + /* align adapter data to sync boundary, we keep the data up to the next sync + * point. */ + save = TRUE; + while (gst_flups_demux_resync (demux, save)) { + gboolean ps_sync = TRUE; + + /* now switch on last synced byte */ + switch (demux->last_sync_code) { + case ID_PS_PACK_START_CODE: + ret = gst_flups_demux_parse_pack_start (demux); + break; + case ID_PS_SYSTEM_HEADER_START_CODE: + ret = gst_flups_demux_parse_sys_head (demux); + break; + case ID_PS_END_CODE: + ret = GST_FLOW_OK; + goto done; + case ID_PS_PROGRAM_STREAM_MAP: + ret = gst_flups_demux_parse_psm (demux); + break; + default: + if (gst_flups_demux_is_pes_sync (demux->last_sync_code)) { + ret = gst_pes_filter_process (&demux->filter); + } else { + GST_DEBUG_OBJECT (demux, "sync_code=%08x, non PES sync found" + ", continuing", demux->last_sync_code); + ps_sync = FALSE; + ret = GST_FLOW_LOST_SYNC; + } + break; + } + /* if we found a ps sync, we stop saving the data, any non-ps sync gets + * saved up to the next ps sync. */ + if (ps_sync) + save = FALSE; + + switch (ret) { + case GST_FLOW_NEED_MORE_DATA: + GST_DEBUG_OBJECT (demux, "need more data"); + ret = GST_FLOW_OK; + goto done; + case GST_FLOW_LOST_SYNC: + if (!save || demux->sink_segment.rate >= 0.0) { + GST_DEBUG_OBJECT (demux, "flushing 3 bytes"); + gst_adapter_flush (demux->adapter, 3); + ADAPTER_OFFSET_FLUSH (3); + } else { + GST_DEBUG_OBJECT (demux, "saving 3 bytes"); + gst_adapter_push (demux->rev_adapter, + gst_adapter_take_buffer (demux->adapter, 3)); + } + ret = GST_FLOW_OK; + break; + default: + break; + } + } +done: + gst_object_unref (demux); + + return ret; +} + +static GstStateChangeReturn +gst_flups_demux_change_state (GstElement * element, GstStateChange transition) +{ + GstFluPSDemux *demux = GST_FLUPS_DEMUX (element); + GstStateChangeReturn result; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + demux->adapter = gst_adapter_new (); + demux->rev_adapter = gst_adapter_new (); + demux->adapter_offset = G_MAXUINT64; + gst_pes_filter_init (&demux->filter, demux->adapter, + &demux->adapter_offset); + gst_pes_filter_set_callbacks (&demux->filter, + (GstPESFilterData) gst_flups_demux_data_cb, + (GstPESFilterResync) gst_flups_demux_resync_cb, demux); + demux->filter.gather_pes = TRUE; + demux->first_scr = G_MAXUINT64; + demux->bytes_since_scr = 0; + demux->current_scr = G_MAXUINT64; + demux->base_time = G_MAXUINT64; + demux->scr_rate_n = G_MAXUINT64; + demux->scr_rate_d = G_MAXUINT64; + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + demux->current_scr = G_MAXUINT64; + demux->mux_rate = G_MAXUINT64; + demux->next_pts = G_MAXUINT64; + demux->next_dts = G_MAXUINT64; + demux->first_scr = G_MAXUINT64; + demux->bytes_since_scr = 0; + demux->base_time = G_MAXUINT64; + demux->scr_rate_n = G_MAXUINT64; + demux->scr_rate_d = G_MAXUINT64; + demux->need_no_more_pads = TRUE; + + gst_flups_demux_reset_psm (demux); + gst_segment_init (&demux->sink_segment, GST_FORMAT_UNDEFINED); + gst_segment_init (&demux->src_segment, GST_FORMAT_TIME); + break; + default: + break; + } + + result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_flups_demux_reset (demux); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_pes_filter_uninit (&demux->filter); + g_object_unref (demux->adapter); + demux->adapter = NULL; + g_object_unref (demux->rev_adapter); + demux->rev_adapter = NULL; + break; + default: + break; + } + + return result; +} + +gboolean +gst_flups_demux_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gstflupesfilter_debug, "rsnpesfilter", 0, + "MPEG program stream PES filter debug"); + + GST_DEBUG_CATEGORY_INIT (gstflupsdemux_debug, "rsndvddemux", 0, + "MPEG program stream demuxer debug"); + + return TRUE; +} diff --git a/ext/resindvd/gstmpegdemux.h b/ext/resindvd/gstmpegdemux.h new file mode 100644 index 00000000..fd566afa --- /dev/null +++ b/ext/resindvd/gstmpegdemux.h @@ -0,0 +1,127 @@ +/* + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + * License for the specific language governing rights and limitations + * under the License. + * + * The Original Code is Fluendo MPEG Demuxer plugin. + * + * The Initial Developer of the Original Code is Fluendo, S.L. + * Portions created by Fluendo, S.L. are Copyright (C) 2005 + * Fluendo, S.L. All Rights Reserved. + * + * Contributor(s): Wim Taymans <wim@fluendo.com> + * Jan Schmidt <thaytan@noraisin.net> + */ + +#ifndef __GST_FLUPS_DEMUX_H__ +#define __GST_FLUPS_DEMUX_H__ + +#include <gst/gst.h> +#include <gst/base/gstadapter.h> + +#include "gstpesfilter.h" + +G_BEGIN_DECLS + +#define GST_TYPE_FLUPS_DEMUX (gst_flups_demux_get_type()) +#define GST_FLUPS_DEMUX(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FLUPS_DEMUX,GstFluPSDemux)) +#define GST_FLUPS_DEMUX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_FLUPS_DEMUX,GstFluPSDemuxClass)) +#define GST_FLUPS_DEMUX_GET_CLASS(klass) (G_TYPE_INSTANCE_GET_CLASS((klass),GST_TYPE_FLUPS_DEMUX,GstFluPSDemuxClass)) +#define GST_IS_FLUPS_DEMUX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_FLUPS_DEMUX)) +#define GST_IS_FLUPS_DEMUX_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_FLUPS_DEMUX)) + +typedef struct _GstFluPSStream GstFluPSStream; +typedef struct _GstFluPSDemux GstFluPSDemux; +typedef struct _GstFluPSDemuxClass GstFluPSDemuxClass; + +#define GST_FLUPS_DEMUX_MAX_STREAMS 256 +#define GST_FLUPS_DEMUX_MAX_PSM 256 + +typedef enum { + STATE_FLUPS_DEMUX_NEED_SYNC, + STATE_FLUPS_DEMUX_SYNCED, + STATE_FLUPS_DEMUX_NEED_MORE_DATA, +} GstFluPSDemuxState; + +/* Information associated with a single FluPS stream. */ +struct _GstFluPSStream { + GstPad * pad; + + gint id; + gint type; + gint size_bound; + + gboolean discont; + gboolean notlinked; + gboolean need_segment; + + GstClockTime last_ts; +}; + +struct _GstFluPSDemux { + GstElement parent; + + GstPad * sinkpad; + + GstAdapter * adapter; + GstAdapter * rev_adapter; + guint64 adapter_offset; + guint32 last_sync_code; + GstPESFilter filter; + + gint64 mux_rate; + guint64 first_scr; + guint64 first_dts; + guint64 base_time; + guint64 current_scr; + guint64 next_scr; + guint64 bytes_since_scr; + gint64 scr_adjust; + guint64 scr_rate_n; + guint64 scr_rate_d; + guint64 first_scr_offset; + guint64 last_scr_offset; + + gint16 psm[GST_FLUPS_DEMUX_MAX_PSM]; + + GstSegment sink_segment; + GstSegment src_segment; + + /* stream output */ + GstFluPSStream * current_stream; + guint64 next_pts; + guint64 next_dts; + GstFluPSStream ** streams; + gboolean need_no_more_pads; + + /* Indicates an MPEG-2 stream */ + gboolean is_mpeg2_pack; + + /* Language codes event is stored when a dvd-lang-codes + * custom event arrives from upstream */ + GstEvent * lang_codes; +}; + +struct _GstFluPSDemuxClass { + GstElementClass parent_class; + + GstPadTemplate *sink_template; + GstPadTemplate *video_template; + GstPadTemplate *audio_template; + GstPadTemplate *private_template; + GstPadTemplate *subpicture_template; +}; + +GType gst_flups_demux_get_type (void); + +gboolean gst_flups_demux_plugin_init (GstPlugin *plugin); + +G_END_DECLS + +#endif /* __GST_FLUPS_DEMUX_H__ */ diff --git a/ext/resindvd/gstmpegdesc.c b/ext/resindvd/gstmpegdesc.c new file mode 100644 index 00000000..514e5d31 --- /dev/null +++ b/ext/resindvd/gstmpegdesc.c @@ -0,0 +1,156 @@ +/* + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + * License for the specific language governing rights and limitations + * under the License. + * + * The Original Code is Fluendo MPEG Demuxer plugin. + * + * The Initial Developer of the Original Code is Fluendo, S.L. + * Portions created by Fluendo, S.L. are Copyright (C) 2005 + * Fluendo, S.L. All Rights Reserved. + * + * Contributor(s): Wim Taymans <wim@fluendo.com> + * Jan Schmidt <thaytan@noraisin.net> + */ + +#include <string.h> + +#include <gst/gst.h> + +#include "gstmpegdesc.h" + +void +gst_mpeg_descriptor_free (GstMPEGDescriptor * desc) +{ + g_return_if_fail (desc != NULL); + + g_free (desc); +} + +static guint +gst_mpeg_descriptor_parse_1 (guint8 * data, guint size) +{ + guint8 tag; + guint8 length; + + /* need at least 2 bytes for tag and length */ + if (size < 2) + return 0; + + tag = *data++; + length = *data++; + size -= 2; + + GST_DEBUG ("tag: 0x%02x, length: %d", tag, length); + + if (length > size) + return 0; + + return length + 2;; +} + +GstMPEGDescriptor * +gst_mpeg_descriptor_parse (guint8 * data, guint size) +{ + guint8 *current; + guint consumed, total, n_desc; + GstMPEGDescriptor *result; + + g_return_val_if_fail (data != NULL, NULL); + + current = data; + total = 0; + n_desc = 0; + + do { + consumed = gst_mpeg_descriptor_parse_1 (current, size); + + if (consumed > 0) { + current += consumed; + total += consumed; + size -= consumed; + n_desc++; + } + } + while (consumed > 0); + + GST_DEBUG ("parsed %d descriptors", n_desc); + + if (total == 0) + return NULL; + + result = g_malloc (sizeof (GstMPEGDescriptor) + total); + result->n_desc = n_desc; + result->data_length = total; + result->data = ((guint8 *) result) + sizeof (GstMPEGDescriptor); + + memcpy (result->data, data, total); + + return result; +} + +guint +gst_mpeg_descriptor_n_desc (GstMPEGDescriptor * desc) +{ + g_return_val_if_fail (desc != NULL, 0); + + return desc->n_desc; +} + +guint8 * +gst_mpeg_descriptor_find (GstMPEGDescriptor * desc, gint tag) +{ + gint length; + guint8 *current; + guint size; + + g_return_val_if_fail (desc != NULL, NULL); + + current = desc->data; + length = desc->data_length; + + while (length > 0) { + if (DESC_TAG (current) == tag) + return current; + + size = DESC_LENGTH (current) + 2; + + current += size; + length -= size; + } + return NULL; +} + +guint8 * +gst_mpeg_descriptor_nth (GstMPEGDescriptor * desc, guint i) +{ + gint length; + guint8 *current; + guint size; + + g_return_val_if_fail (desc != NULL, NULL); + + if (i > desc->n_desc) + return NULL; + + current = desc->data; + length = desc->data_length; + + while (length > 0) { + if (i == 0) + return current; + + size = DESC_LENGTH (current) + 2; + + current += size; + length -= size; + i--; + } + return NULL; +} diff --git a/ext/resindvd/gstmpegdesc.h b/ext/resindvd/gstmpegdesc.h new file mode 100644 index 00000000..5b056128 --- /dev/null +++ b/ext/resindvd/gstmpegdesc.h @@ -0,0 +1,249 @@ +/* + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + * License for the specific language governing rights and limitations + * under the License. + * + * The Original Code is Fluendo MPEG Demuxer plugin. + * + * The Initial Developer of the Original Code is Fluendo, S.L. + * Portions created by Fluendo, S.L. are Copyright (C) 2005 + * Fluendo, S.L. All Rights Reserved. + * + * Contributor(s): Wim Taymans <wim@fluendo.com> + * Jan Schmidt <thaytan@noraisin.net> + */ + +#ifndef __GST_MPEG_DESC_H__ +#define __GST_MPEG_DESC_H__ + +#include <glib.h> +/* + * descriptor_tag TS PS Identification + * 0 n/a n/a Reserved + * 1 n/a n/a Reserved + * 2 X X video_stream_descriptor + * 3 X X audio_stream_descriptor + * 4 X X hierarchy_descriptor + * 5 X X registration_descriptor + * 6 X X data_stream_alignment_descriptor + * 7 X X target_background_grid_descriptor + * 8 X X video_window_descriptor + * 9 X X CA_descriptor + * 10 X X ISO_639_language_descriptor + * 11 X X system_clock_descriptor + * 12 X X multiplex_buffer_utilization_descriptor + * 13 X X copyright_descriptor + * 14 X maximum bitrate descriptor + * 15 X X private data indicator descriptor + * 16 X X smoothing buffer descriptor + * 17 X STD_descriptor + * 18 X X IBP descriptor + * 19-63 n/a n/a ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Reserved + * 64-255 n/a n/a User Private + */ +#define DESC_VIDEO_STREAM 2 +#define DESC_AUDIO_STREAM 3 +#define DESC_HIERARCHY 4 +#define DESC_REGISTRATION 5 +#define DESC_DATA_STREAM_ALIGNMENT 6 +#define DESC_TARGET_BACKGROUND_GRID 7 +#define DESC_VIDEO_WINDOW 8 +#define DESC_CA 9 +#define DESC_ISO_639_LANGUAGE 10 +#define DESC_SYSTEM_CLOCK 11 +#define DESC_MULTIPLEX_BUFFER_UTILISATION 12 +#define DESC_COPYRIGHT 13 +#define DESC_MAXIMUM_BITRATE 14 +#define DESC_PRIVATE_DATA_INDICATOR 15 +#define DESC_SMOOTHING_BUFFER 16 +#define DESC_STD 17 +#define DESC_IBP 18 + +#define DESC_DIRAC_TC_PRIVATE 0xAC + +/* DVB tags */ +#define DESC_DVB_NETWORK_NAME 0x40 +#define DESC_DVB_SERVICE_LIST 0x41 +#define DESC_DVB_STUFFING 0x42 +#define DESC_DVB_SATELLITE_DELIVERY_SYSTEM 0x43 +#define DESC_DVB_CABLE_DELIVERY_SYSTEM 0x44 +#define DESC_DVB_VBI_DATA 0x45 +#define DESC_DVB_VBI_TELETEXT 0x46 +#define DESC_DVB_BOUQUET_NAME 0x47 +#define DESC_DVB_SERVICE 0x48 +#define DESC_DVB_COUNTRY_AVAILABILITY 0x49 +#define DESC_DVB_LINKAGE 0x4A +#define DESC_DVB_NVOD_REFERENCE 0x4B +#define DESC_DVB_TIME_SHIFTED_SERVICE 0x4C +#define DESC_DVB_SHORT_EVENT 0x4D +#define DESC_DVB_EXTENDED_EVENT 0x4E +#define DESC_DVB_TIME_SHIFTED_EVENT 0x4F +#define DESC_DVB_COMPONENT 0x50 +#define DESC_DVB_MOSAIC 0x51 +#define DESC_DVB_STREAM_IDENTIFIER 0x52 +#define DESC_DVB_CA_IDENTIFIER 0x53 +#define DESC_DVB_CONTENT 0x54 +#define DESC_DVB_PARENTAL_RATING 0x55 +#define DESC_DVB_TELETEXT 0x56 +#define DESC_DVB_TELEPHONE 0x57 +#define DESC_DVB_LOCAL_TIME_OFFSET 0x58 +#define DESC_DVB_SUBTITLING 0x59 +#define DESC_DVB_TERRESTRIAL_DELIVERY_SYSTEM 0x5A +#define DESC_DVB_MULTILINGUAL_NETWORK_NAME 0x5B +#define DESC_DVB_MULTILINGUAL_BOUQUET_NAME 0x5C +#define DESC_DVB_MULTILINGUAL_SERVICE_NAME 0x5D +#define DESC_DVB_MULTILINGUAL_COMPONENT 0x5E +#define DESC_DVB_PRIVATE_DATA 0x5F +#define DESC_DVB_SERVICE_MOVE 0x60 +#define DESC_DVB_SHORT_SMOOTHING_BUFFER 0x61 +#define DESC_DVB_FREQUENCY_LIST 0x62 +#define DESC_DVB_PARTIAL_TRANSPORT_STREAM 0x63 +#define DESC_DVB_DATA_BROADCAST 0x64 +#define DESC_DVB_SCRAMBLING 0x65 +#define DESC_DVB_DATA_BROADCAST_ID 0x66 +#define DESC_DVB_TRANSPORT_STREAM 0x67 +#define DESC_DVB_DSNG 0x68 +#define DESC_DVB_PDC 0x69 +#define DESC_DVB_AC3 0x6A +#define DESC_DVB_ANCILLARY_DATA 0x6B +#define DESC_DVB_CELL_LIST 0x6C +#define DESC_DVB_CELL_FREQUENCY_LINK 0x6D +#define DESC_DVB_ANNOUNCEMENT_SUPPORT 0x6E +#define DESC_DVB_APPLICATION_SIGNALLING 0x6F +#define DESC_DVB_ADAPTATION_FIELD_DATA 0x70 +#define DESC_DVB_SERVICE_IDENTIFIER 0x71 +#define DESC_DVB_SERVICE_AVAILABILITY 0x72 +#define DESC_DVB_DEFAULT_AUTHORITY 0x73 +#define DESC_DVB_RELATED_CONTENT 0x74 +#define DESC_DVB_TVA_ID 0x75 +#define DESC_DVB_CONTENT_IDENTIFIER 0x76 +#define DESC_DVB_TIMESLICE_FEC_IDENTIFIER 0x77 +#define DESC_DVB_ECM_REPETITION_RATE 0x78 +#define DESC_DVB_S2_SATELLITE_DELIVERY_SYSTEM 0x79 +#define DESC_DVB_ENHANCED_AC3 0x7A +#define DESC_DVB_DTS 0x7B +#define DESC_DVB_AAC 0x7C +/* 0x7D and 0x7E are reserved for future use */ +#define DESC_DVB_EXTENSION 0x7F +/* 0x80 - 0xFE are user defined */ +/* 0xFF is forbidden */ + +/* common for all descriptors */ +#define DESC_TAG(desc) (desc[0]) +#define DESC_LENGTH(desc) (desc[1]) + +/* video_stream_descriptor */ +#define DESC_VIDEO_STREAM_multiple_framerate_flag(desc) (((desc)[2] & 0x80) == 0x80) +#define DESC_VIDEO_STREAM_frame_rate_code(desc) (((desc)[2] & 0x38) >> 3) +#define DESC_VIDEO_STREAM_MPEG_1_only_flag(desc) (((desc)[2] & 0x04) == 0x04) +#define DESC_VIDEO_STREAM_constrained_parameter_flag(desc) (((desc)[2] & 0x02) == 0x02) +#define DESC_VIDEO_STREAM_still_picture_flag(desc) (((desc)[2] & 0x01) == 0x01) +/* if (MPEG_1_only_flag == 1) */ +#define DESC_VIDEO_STREAM_profile_and_level_indication(desc) ((desc)[3]) +#define DESC_VIDEO_STREAM_chroma_format(desc) (((desc)[4] & 0xc0) >> 6) +#define DESC_VIDEO_STREAM_frame_rate_extension_flag(desc) (((desc)[4] & 0x20) == 0x20) + +/* audio_stream_descriptor */ +#define DESC_AUDIO_STREAM_free_format_flag(desc) (((desc)[2] & 0x80) == 0x80) +#define DESC_AUDIO_STREAM_ID(desc) (((desc)[2] & 0x40) == 0x40) +#define DESC_AUDIO_STREAM_layer(desc) (((desc)[2] & 0x30) >> 4) +#define DESC_AUDIO_STREAM_variable_rate_audio_indicator(desc) (((desc)[2] & 0x08) == 0x08) + +/* hierarchy_descriptor */ +#define DESC_HIERARCHY_hierarchy_type(desc) (((desc)[2] & 0x0f)) +#define DESC_HIERARCHY_hierarchy_layer_index(desc) (((desc)[3] & 0x3f)) +#define DESC_HIERARCHY_hierarchy_embedded_layer_index(desc) (((desc)[4] & 0x3f)) +#define DESC_HIERARCHY_hierarchy_channel(desc) (((desc)[5] & 0x3f)) + +/* registration_descriptor */ +#define DESC_REGISTRATION_format_identifier(desc) (GST_READ_UINT32_BE ((desc)+2)) +#define DESC_REGISTRATION_additional_ident_info_len(desc) ((desc)[1] - 4) +#define DESC_REGISTRATION_additional_ident_info(desc) (&(desc)[6]) + +/* data_stream_alignment_descriptor */ +#define DESC_DATA_STREAM_ALIGNMENT_alignment_type(desc) ((desc)[2]) + +/* target_background_grid_descriptor */ +#define DESC_TARGET_BACKGROUND_GRID_horizontal_size(desc) (GST_READ_UINT16_BE ((desc)+2) >> 2) +#define DESC_TARGET_BACKGROUND_GRID_vertical_size(desc) ((GST_READ_UINT32_BE ((desc)+2) & 0x0003fff0) >> 4) +#define DESC_TARGET_BACKGROUND_GRID_aspect_ratio_information(desc) ((desc)[5] & 0x0f) + +/* video_window_descriptor */ +#define DESC_VIDEO_WINDOW_horizontal_offset(desc) (GST_READ_UINT16_BE ((desc)+2) >> 2) +#define DESC_VIDEO_WINDOW_vertical_offset(desc) ((GST_READ_UINT32_BE ((desc)+2) & 0x0003fff0) >> 4) +#define DESC_VIDEO_WINDOW_window_priority(desc) ((desc)[5] & 0x0f) + +/* CA_descriptor */ +#define DESC_CA_system_ID(desc) (GST_READ_UINT16_BE ((desc)+2)) +#define DESC_CA_PID(desc) (GST_READ_UINT16_BE ((desc)+2) & 0x1fff) + +/* ISO_639_language_descriptor */ +#define DESC_ISO_639_LANGUAGE_codes_n(desc) ((desc[1]) >> 2) +#define DESC_ISO_639_LANGUAGE_language_code_nth(desc,i) (&(desc[2 + (4*i)])) +#define DESC_ISO_639_LANGUAGE_audio_type_nth(desc,i) ((desc)[5 + (4*i)]) + +/* system_clock_descriptor */ +#define DESC_SYSTEM_CLOCK_external_clock_reference_indicator(desc) (((desc)[2] & 0x80) == 0x80) +#define DESC_SYSTEM_CLOCK_clock_accuracy_integer(desc) ((desc)[2] & 0x3f) +#define DESC_SYSTEM_CLOCK_clock_accuracy_exponent(desc) (((desc)[3] & 0xe0) >> 5) + +/* multiplex_buffer_utilization_descriptor */ +#define DESC_MULTIPLEX_BUFFER_UTILISATION_bound_valid_flag(desc) (((desc)[2] & 0x80) == 0x80) +#define DESC_MULTIPLEX_BUFFER_UTILISATION_LTW_offset_lower_bound(desc) (GST_READ_UINT16_BE ((desc)+2) & 0x7fff) +#define DESC_MULTIPLEX_BUFFER_UTILISATION_LTW_offset_upper_bound(desc) (GST_READ_UINT16_BE ((desc)+4) & 0x7fff) + +/* copyright_descriptor */ +#define DESC_COPYRIGHT_copyright_identifier(desc) (GST_READ_UINT32_BE ((desc)+2)) +#define DESC_COPYRIGHT_additional_copyright_info_len(desc) ((desc)[1] - 4) +#define DESC_COPYRIGHT_additional_copyright_info(desc) (&(desc)[6]) + +/* maximum_bitrate_descriptor */ +#define DESC_MAXIMUM_BITRAT_maximum_bitrate(desc) (((((guint32)desc[2]) & 0x3f) << 16) | \ + GST_READ_UINT16_BE ((desc)+3)) + +/* private_data_indicator_descriptor */ +#define DESC_PRIVATE_DATA_INDICATOR_indicator(desc) (GST_READ_UINT32_BE(&desc[2])) + +/* smoothing_buffer_descriptor */ +#define DESC_SMOOTHING_BUFFER_sb_leak_rate(desc) (((((guint32)desc[2]) & 0x3f) << 16) | \ + GST_READ_UINT16_BE ((desc)+3)) +#define DESC_SMOOTHING_BUFFER_sb_size(desc) (((((guint32)desc[5]) & 0x3f) << 16) | \ + GST_READ_UINT16_BE ((desc)+6)) +/* STD_descriptor */ +#define DESC_STD_leak_valid_flag(desc) (((desc)[2] & 0x01) == 0x01) + +/* ibp_descriptor */ +#define DESC_IBP_closed_gop_flag(desc) (((desc)[2] & 0x80) == 0x80) +#define DESC_IBP_identical_gop_flag(desc) (((desc)[2] & 0x40) == 0x40) +#define DESC_IBP_max_gop_length(desc) (GST_READ_UINT16_BE ((desc)+6) & 0x3fff) + +/* time_code descriptor */ +#define DESC_TIMECODE_video_pid(desc) (GST_READ_UINT16_BE ((desc) + 2) & 0x1fff) + +/* Stream identifier descriptor */ +#define DESC_DVB_STREAM_IDENTIFIER_component_tag(desc) (desc[2]) + +/* DVB Network Name descriptor */ +#define DESC_DVB_NETWORK_NAME_length(desc) (GST_READ_UINT8((desc)+1)) +#define DESC_DVB_NETWORK_NAME_text(desc) (desc+2) + +typedef struct { + guint n_desc; + guint8 data_length; + guint8 *data; +} GstMPEGDescriptor; + +GstMPEGDescriptor* gst_mpeg_descriptor_parse (guint8 *data, guint size); +void gst_mpeg_descriptor_free (GstMPEGDescriptor *desc); + +guint gst_mpeg_descriptor_n_desc (GstMPEGDescriptor *desc); +guint8* gst_mpeg_descriptor_find (GstMPEGDescriptor *desc, gint tag); +guint8* gst_mpeg_descriptor_nth (GstMPEGDescriptor *desc, guint i); + +#endif /* __GST_MPEG_DESC_H__ */ diff --git a/ext/resindvd/gstpesfilter.c b/ext/resindvd/gstpesfilter.c new file mode 100644 index 00000000..f0a8cb54 --- /dev/null +++ b/ext/resindvd/gstpesfilter.c @@ -0,0 +1,623 @@ +/* + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + * License for the specific language governing rights and limitations + * under the License. + * + * The Original Code is Fluendo MPEG Demuxer plugin. + * + * The Initial Developer of the Original Code is Fluendo, S.L. + * Portions created by Fluendo, S.L. are Copyright (C) 2005 + * Fluendo, S.L. All Rights Reserved. + * + * Contributor(s): Wim Taymans <wim@fluendo.com> + * Jan Schmidt <thaytan@noraisin.net> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstmpegdefs.h" +#include "gstpesfilter.h" + +GST_DEBUG_CATEGORY (gstflupesfilter_debug); +#define GST_CAT_DEFAULT (gstflupesfilter_debug) + +static GstFlowReturn gst_pes_filter_data_push (GstPESFilter * filter, + gboolean first, GstBuffer * buffer); + +#define ADAPTER_OFFSET_FLUSH(_bytes_) if (filter->adapter_offset) *filter->adapter_offset = *filter->adapter_offset + (_bytes_) + +/* May pass null for adapter to have the filter create one */ +void +gst_pes_filter_init (GstPESFilter * filter, GstAdapter * adapter, + guint64 * adapter_offset) +{ + g_return_if_fail (filter != NULL); + + if (adapter != NULL) + g_object_ref (adapter); + else + adapter = gst_adapter_new (); + + filter->adapter = adapter; + filter->adapter_offset = adapter_offset; + filter->state = STATE_HEADER_PARSE; + filter->gather_pes = FALSE; + filter->allow_unbounded = FALSE; +} + +void +gst_pes_filter_uninit (GstPESFilter * filter) +{ + g_return_if_fail (filter != NULL); + + if (filter->adapter) + g_object_unref (filter->adapter); + filter->adapter = NULL; + filter->adapter_offset = NULL; +} + +void +gst_pes_filter_set_callbacks (GstPESFilter * filter, + GstPESFilterData data_cb, GstPESFilterResync resync_cb, gpointer user_data) +{ + g_return_if_fail (filter != NULL); + + filter->data_cb = data_cb; + filter->resync_cb = resync_cb; + filter->user_data = user_data; +} + +/* sync:4 == 00xx ! pts:3 ! 1 ! pts:15 ! 1 | pts:15 ! 1 */ +#define READ_TS(data, target, lost_sync_label) \ + if ((*data & 0x01) != 0x01) goto lost_sync_label; \ + target = ((guint64) (*data++ & 0x0E)) << 29; \ + target |= ((guint64) (*data++ )) << 22; \ + if ((*data & 0x01) != 0x01) goto lost_sync_label; \ + target |= ((guint64) (*data++ & 0xFE)) << 14; \ + target |= ((guint64) (*data++ )) << 7; \ + if ((*data & 0x01) != 0x01) goto lost_sync_label; \ + target |= ((guint64) (*data++ & 0xFE)) >> 1; + +static gboolean +gst_pes_filter_is_sync (guint32 sync) +{ + return ((sync & 0xfffffffc) == 0x000001bc) || + ((sync & 0xffffffe0) == 0x000001c0) || + ((sync & 0xfffffff0) == 0x000001f0) || + ((sync & 0xfffffff0) == 0x000001e0); +} + +static GstFlowReturn +gst_pes_filter_parse (GstPESFilter * filter) +{ + GstFlowReturn ret; + guint32 start_code; + + gboolean STD_buffer_bound_scale; + guint16 STD_buffer_size_bound; + const guint8 *data; + gint avail, datalen; + gboolean have_size = FALSE; + + /* read start code and length */ + if (!(data = gst_adapter_peek (filter->adapter, 6))) + goto need_more_data; + + /* get start code */ + start_code = GST_READ_UINT32_BE (data); + if (!gst_pes_filter_is_sync (start_code)) + goto lost_sync; + + filter->start_code = start_code; + filter->id = data[3]; + + /* skip start code */ + data += 4; + + /* start parsing length */ + filter->length = GST_READ_UINT16_BE (data); + + /* see how much is available */ + avail = gst_adapter_available (filter->adapter); + + GST_DEBUG ("id 0x%02x length %d, avail %d start code 0x%02x", filter->id, + filter->length, avail, filter->start_code); + + /* A data length of 0 indicates an unbounded packet in transport + * streams, but actually a 0 sized packet in program streams or + * for anything except video packets */ + + /* FIXME: Remove this hack that is checking start_code. Instead, we need + * a callback that a start_code has been collected, giving the caller a chance + * to set the allow_unbounded flag if they want */ + if (filter->length == 0 && + ((filter->start_code & 0xFFFFFFF0) == PACKET_VIDEO_START_CODE || + filter->allow_unbounded)) { + GST_DEBUG ("id 0x%02x, unbounded length", filter->id); + filter->unbounded_packet = TRUE; + } else { + filter->unbounded_packet = FALSE; + + if (filter->gather_pes && avail < filter->length + 6) { + GST_DEBUG ("id 0x%02x, bounded length %d, only have %d", + filter->id, filter->length + 6, avail); + goto need_more_data; + } + + /* if we need more data from now on, we lost sync */ + avail = MIN (avail, filter->length + 6); + } + + /* read more data, either the whole packet if there is a length + * or whatever we have available if this in an unbounded packet. */ + if (!(data = gst_adapter_peek (filter->adapter, avail))) + goto need_more_data; + + /* This will make us flag LOST_SYNC if we run out of data from here onward */ + have_size = TRUE; + + /* skip start code and length */ + data += 6; + datalen = avail - 6; + + GST_DEBUG ("datalen %d", datalen); + + switch (filter->start_code) { + case ID_PS_PROGRAM_STREAM_MAP: + case ID_PRIVATE_STREAM_2: + case ID_ECM_STREAM: + case ID_EMM_STREAM: + case ID_PROGRAM_STREAM_DIRECTORY: + case ID_DSMCC_STREAM: + case ID_ITU_TREC_H222_TYPE_E_STREAM: + goto skip; + case ID_PADDING_STREAM: + GST_DEBUG ("skipping padding stream"); + goto skip; + default: + break; + } + + if (datalen < 1) + goto need_more_data; + + filter->pts = filter->dts = -1; + + /* stuffing bits, first two bits are '10' for mpeg2 pes so this code is + * not triggered. */ + while (TRUE) { + if (*data != 0xff) + break; + + data++; + datalen--; + + GST_DEBUG ("got stuffing bit"); + + if (datalen < 1) + goto need_more_data; + } + + /* STD buffer size, never for mpeg2 */ + if ((*data & 0xc0) == 0x40) { + GST_DEBUG ("have STD"); + + if (datalen < 3) + goto need_more_data; + + STD_buffer_bound_scale = *data & 0x20; + STD_buffer_size_bound = ((guint16) (*data++ & 0x1F)) << 8; + STD_buffer_size_bound |= *data++; + + datalen -= 2; + } + + /* PTS but no DTS, never for mpeg2 */ + if ((*data & 0xf0) == 0x20) { + GST_DEBUG ("PTS without DTS"); + + if (datalen < 5) + goto need_more_data; + READ_TS (data, filter->pts, lost_sync); + GST_DEBUG ("PTS found %" G_GUINT64_FORMAT, filter->pts); + datalen -= 5; + } + /* PTS and DTS, never for mpeg2 */ + else if ((*data & 0xf0) == 0x30) { + GST_DEBUG ("PTS and DTS"); + + if (datalen < 10) + goto need_more_data; + READ_TS (data, filter->pts, lost_sync); + READ_TS (data, filter->dts, lost_sync); + GST_DEBUG ("PTS found %" G_GUINT64_FORMAT, filter->pts); + GST_DEBUG ("DTS found %" G_GUINT64_FORMAT, filter->dts); + datalen -= 10; + } else if ((*data & 0xc0) == 0x80) { + /* mpeg2 case */ + guchar flags; + guint8 header_data_length = 0; + + GST_DEBUG ("MPEG2 PES packet"); + + if (datalen < 3) + goto need_more_data; + /* 2: '10' + * 2: PES_scrambling_control + * 1: PES_priority + * 1: data_alignment_indicator + * 1: copyright + * 1: original_or_copy + */ + flags = *data++; + + GST_DEBUG ("flags: 0x%02x", flags); + if ((flags & 0xc0) != 0x80) + goto lost_sync; + + /* check PES scrambling control */ + if ((flags & 0x30) != 0) + goto encrypted; + + /* 2: PTS_DTS_flags + * 1: ESCR_flag + * 1: ES_rate_flag + * 1: DSM_trick_mode_flag + * 1: additional_copy_info_flag + * 1: PES_CRC_flag + * 1: PES_extension_flag + */ + flags = *data++; + + /* 8: PES_header_data_length */ + header_data_length = *data++; + datalen -= 3; + + GST_DEBUG ("header_data_length: %d, flags 0x%02x", + header_data_length, flags); + + if (header_data_length > datalen) + goto need_more_data; + + /* only DTS: this is invalid */ + if ((flags & 0xc0) == 0x40) + goto lost_sync; + + /* check for PTS */ + if ((flags & 0x80)) { + if (datalen < 5) + goto need_more_data; + + READ_TS (data, filter->pts, lost_sync); + GST_DEBUG ("PTS found %" G_GUINT64_FORMAT, filter->pts); + header_data_length -= 5; + datalen -= 5; + } + /* check for DTS */ + if ((flags & 0x40)) { + READ_TS (data, filter->dts, lost_sync); + if (datalen < 5) + goto need_more_data; + GST_DEBUG ("DTS found %" G_GUINT64_FORMAT, filter->dts); + header_data_length -= 5; + datalen -= 5; + } + /* ESCR_flag */ + if ((flags & 0x20)) { + GST_DEBUG ("%x ESCR found", filter->id); + if (datalen < 6) + goto need_more_data; + data += 6; + header_data_length -= 6; + datalen -= 6; + } + /* ES_rate_flag */ + if ((flags & 0x10)) { + guint32 es_rate; + + if (datalen < 3) + goto need_more_data; + + es_rate = ((guint32) (*data++ & 0x07)) << 14; + es_rate |= ((guint32) (*data++)) << 7; + es_rate |= ((guint32) (*data++ & 0xFE)) >> 1; + GST_DEBUG ("%x ES Rate found %u", filter->id, es_rate); + header_data_length -= 3; + datalen -= 3; + } + /* DSM_trick_mode_flag */ + if ((flags & 0x08)) { + guint8 trick_mode_flags; + + if (datalen < 1) + goto need_more_data; + + /* 3: trick_mode_control */ + trick_mode_flags = *data++; + GST_DEBUG ("%x DSM trick mode found, flags 0x%02x", filter->id, + trick_mode_flags); + + /* fast_forward */ + if ((trick_mode_flags & 0xe0) == 0x00) { + } + /* slow motion */ + else if ((trick_mode_flags & 0xe0) == 0x20) { + } + /* freeze frame */ + else if ((trick_mode_flags & 0xe0) == 0x40) { + } + /* fast reverse */ + else if ((trick_mode_flags & 0xe0) == 0x60) { + } + /* slow reverse */ + else if ((trick_mode_flags & 0xe0) == 0x80) { + } + /* reserved */ + else { + } + + header_data_length -= 1; + datalen -= 1; + } + /* additional_copy_info_flag */ + if ((flags & 0x04)) { + GST_DEBUG ("%x additional copy info, flags 0x%02x", filter->id, *data); + } + /* PES_CRC_flag */ + if ((flags & 0x02)) { + GST_DEBUG ("%x PES_CRC", filter->id); + } + /* PES_extension_flag */ + if ((flags & 0x01)) { + GST_DEBUG ("%x PES_extension", filter->id); + } + + /* calculate the amount of real data in this PES packet */ + data += header_data_length; + datalen -= header_data_length; + } else if (*data == 0x0f) { + /* Not sure what this clause is for */ + data++; + datalen--; + } else { + /* Data byte wasn't recognised as a flags byte */ + GST_DEBUG ("Unrecognised flags byte 0x%02x\n", *data); + goto lost_sync; + } + + { + GstBuffer *out; + guint16 consumed; + + consumed = avail - 6 - datalen; + + if (filter->unbounded_packet == FALSE) { + filter->length -= avail - 6; + GST_DEBUG ("pushing %d, need %d more, consumed %d", + datalen, filter->length, consumed); + } else { + GST_DEBUG ("pushing %d, unbounded packet, consumed %d", + datalen, consumed); + } + + if (datalen > 0) { + out = gst_buffer_new (); + GST_BUFFER_DATA (out) = g_memdup (data, datalen); + GST_BUFFER_SIZE (out) = datalen; + GST_BUFFER_MALLOCDATA (out) = GST_BUFFER_DATA (out); + + ret = gst_pes_filter_data_push (filter, TRUE, out); + filter->first = FALSE; + } else { + GST_LOG ("first being set to TRUE"); + filter->first = TRUE; + ret = GST_FLOW_OK; + } + + if (filter->length > 0 || filter->unbounded_packet) + filter->state = STATE_DATA_PUSH; + } + + gst_adapter_flush (filter->adapter, avail); + ADAPTER_OFFSET_FLUSH (avail); + + return ret; + +need_more_data: + { + if (filter->unbounded_packet == FALSE) { + if (have_size == TRUE) { + GST_DEBUG ("bounded need more data %d, lost sync", + gst_adapter_available (filter->adapter)); + ret = GST_FLOW_LOST_SYNC; + } else { + GST_DEBUG ("bounded need more data %d, breaking for more", + gst_adapter_available (filter->adapter)); + ret = GST_FLOW_NEED_MORE_DATA; + } + } else { + GST_DEBUG ("unbounded need more data %d", + gst_adapter_available (filter->adapter)); + ret = GST_FLOW_NEED_MORE_DATA; + } + + return ret; + } +skip: + { + GST_DEBUG ("skipping 0x%02x", filter->id); + gst_adapter_flush (filter->adapter, avail); + ADAPTER_OFFSET_FLUSH (avail); + + filter->length -= avail - 6; + if (filter->length > 0 || filter->unbounded_packet) + filter->state = STATE_DATA_SKIP; + return GST_FLOW_OK; + } +encrypted: + { + GST_DEBUG ("skipping encrypted 0x%02x", filter->id); + gst_adapter_flush (filter->adapter, avail); + ADAPTER_OFFSET_FLUSH (avail); + + filter->length -= avail - 6; + if (filter->length > 0 || filter->unbounded_packet) + filter->state = STATE_DATA_SKIP; + return GST_FLOW_OK; + } +lost_sync: + { + GST_DEBUG ("lost sync"); + gst_adapter_flush (filter->adapter, 4); + ADAPTER_OFFSET_FLUSH (4); + + return GST_FLOW_LOST_SYNC; + } +} + +static GstFlowReturn +gst_pes_filter_data_push (GstPESFilter * filter, gboolean first, + GstBuffer * buffer) +{ + GstFlowReturn ret; + + GST_LOG ("pushing, first: %d", first); + + if (filter->data_cb) { + ret = filter->data_cb (filter, first, buffer, filter->user_data); + } else { + gst_buffer_unref (buffer); + ret = GST_FLOW_OK; + } + return ret; +} + +GstFlowReturn +gst_pes_filter_push (GstPESFilter * filter, GstBuffer * buffer) +{ + GstFlowReturn ret; + + g_return_val_if_fail (filter != NULL, GST_FLOW_ERROR); + g_return_val_if_fail (buffer != NULL, GST_FLOW_ERROR); + + switch (filter->state) { + case STATE_HEADER_PARSE: + gst_adapter_push (filter->adapter, buffer); + ret = gst_pes_filter_parse (filter); + break; + case STATE_DATA_PUSH: + ret = gst_pes_filter_data_push (filter, filter->first, buffer); + filter->first = FALSE; + break; + case STATE_DATA_SKIP: + gst_buffer_unref (buffer); + ret = GST_FLOW_OK; + break; + default: + goto wrong_state; + } + return ret; + + /* ERROR */ +wrong_state: + { + GST_DEBUG ("wrong internal state %d", filter->state); + return GST_FLOW_ERROR; + } +} + +GstFlowReturn +gst_pes_filter_process (GstPESFilter * filter) +{ + GstFlowReturn ret; + gboolean skip = FALSE; + + g_return_val_if_fail (filter != NULL, GST_FLOW_ERROR); + + switch (filter->state) { + case STATE_HEADER_PARSE: + ret = gst_pes_filter_parse (filter); + break; + case STATE_DATA_SKIP: + skip = TRUE; + /* fallthrough */ + case STATE_DATA_PUSH: + if (filter->length > 0 || filter->unbounded_packet) { + gint avail; + + avail = gst_adapter_available (filter->adapter); + if (filter->unbounded_packet == FALSE) + avail = MIN (avail, filter->length); + + if (skip) { + gst_adapter_flush (filter->adapter, avail); + ADAPTER_OFFSET_FLUSH (avail); + ret = GST_FLOW_OK; + } else { + GstBuffer *out; + guint8 *data; + + data = gst_adapter_take (filter->adapter, avail); + + out = gst_buffer_new (); + GST_BUFFER_DATA (out) = data; + GST_BUFFER_SIZE (out) = avail; + GST_BUFFER_MALLOCDATA (out) = data; + + ret = gst_pes_filter_data_push (filter, filter->first, out); + filter->first = FALSE; + } + + if (filter->unbounded_packet == FALSE) { + filter->length -= avail; + if (filter->length == 0) + filter->state = STATE_HEADER_PARSE; + } + } else { + filter->state = STATE_HEADER_PARSE; + ret = GST_FLOW_OK; + } + break; + default: + goto wrong_state; + } + return ret; + + /* ERROR */ +wrong_state: + { + GST_DEBUG ("wrong internal state %d", filter->state); + return GST_FLOW_ERROR; + } +} + +void +gst_pes_filter_flush (GstPESFilter * filter) +{ + g_return_if_fail (filter != NULL); + + if (filter->adapter) { + gst_adapter_clear (filter->adapter); + if (filter->adapter_offset) + *filter->adapter_offset = G_MAXUINT64; + } + filter->state = STATE_HEADER_PARSE; +} + +GstFlowReturn +gst_pes_filter_drain (GstPESFilter * filter) +{ + g_return_val_if_fail (filter != NULL, GST_FLOW_ERROR); + + gst_pes_filter_flush (filter); + + return GST_FLOW_OK; +} diff --git a/ext/resindvd/gstpesfilter.h b/ext/resindvd/gstpesfilter.h new file mode 100644 index 00000000..27c618c2 --- /dev/null +++ b/ext/resindvd/gstpesfilter.h @@ -0,0 +1,88 @@ +/* + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + * License for the specific language governing rights and limitations + * under the License. + * + * The Original Code is Fluendo MPEG Demuxer plugin. + * + * The Initial Developer of the Original Code is Fluendo, S.L. + * Portions created by Fluendo, S.L. are Copyright (C) 2005 + * Fluendo, S.L. All Rights Reserved. + * + * Contributor(s): Wim Taymans <wim@fluendo.com> + * Jan Schmidt <thaytan@noraisin.net> + */ + +#ifndef __GST_PES_FILTER_H__ +#define __GST_PES_FILTER_H__ + +#include <gst/gst.h> +#include <gst/base/gstadapter.h> + +G_BEGIN_DECLS + +typedef struct _GstPESFilter GstPESFilter; + +typedef GstFlowReturn (*GstPESFilterData) (GstPESFilter * filter, gboolean first, GstBuffer * buffer, + gpointer user_data); +typedef void (*GstPESFilterResync) (GstPESFilter * filter, gpointer user_data); +typedef void (*GstPESFilterIndex) (GstPESFilter * filter, gpointer user_data); + +typedef enum { + STATE_HEADER_PARSE, + STATE_DATA_PUSH, + STATE_DATA_SKIP +} GstPESFilterState; + +struct _GstPESFilter { + GstAdapter * adapter; + guint64 * adapter_offset; + + GstPESFilterState state; + /* Whether to collect entire PES packets before + * outputting */ + gboolean gather_pes; + /* Whether unbounded packets are allowed in this + * stream */ + gboolean allow_unbounded; + + gboolean first; + GstPESFilterData data_cb; + GstPESFilterResync resync_cb; + gpointer user_data; + + guint32 start_code; + guint8 id; + gboolean unbounded_packet; + guint16 length; + + guint8 type; + + gint64 pts; + gint64 dts; +}; + +void gst_pes_filter_init (GstPESFilter * filter, GstAdapter * adapter, guint64 * adapter_offset); + +void gst_pes_filter_uninit (GstPESFilter * filter); + +void gst_pes_filter_set_callbacks (GstPESFilter * filter, + GstPESFilterData data_cb, + GstPESFilterResync resync_cb, + gpointer user_data); + +GstFlowReturn gst_pes_filter_push (GstPESFilter * filter, GstBuffer * buffer); +GstFlowReturn gst_pes_filter_process (GstPESFilter * filter); + +void gst_pes_filter_flush (GstPESFilter * filter); +GstFlowReturn gst_pes_filter_drain (GstPESFilter * filter); + +G_END_DECLS + +#endif /* __GST_PES_FILTER_H__ */ diff --git a/ext/resindvd/plugin.c b/ext/resindvd/plugin.c new file mode 100644 index 00000000..bc0a9640 --- /dev/null +++ b/ext/resindvd/plugin.c @@ -0,0 +1,53 @@ +/* GStreamer + * Copyright (C) 2008 Jan Schmidt <thaytan@noraisin.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <gst/gst.h> + +#include "resindvdbin.h" + +#include "gstmpegdemux.h" + +GST_DEBUG_CATEGORY (resindvd_debug); +#define GST_CAT_DEFAULT resindvd_debug + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gboolean result = TRUE; + + GST_DEBUG_CATEGORY_INIT (resindvd_debug, "resindvd elements", + 0, "DVD playback elements from resindvd"); + + result &= gst_element_register (plugin, "rsndvdbin", + GST_RANK_PRIMARY, RESIN_TYPE_DVDBIN); + + result &= gst_flups_demux_plugin_init (plugin); + + return result; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "resindvd", + "Resin DVD playback elements", + plugin_init, VERSION, "GPL", "GStreamer", "http://gstreamer.net/") diff --git a/ext/resindvd/resin-play b/ext/resindvd/resin-play new file mode 100755 index 00000000..c2368116 --- /dev/null +++ b/ext/resindvd/resin-play @@ -0,0 +1,11 @@ +#!/bin/sh + +if [ $# -ge 1 ]; then + DEVICE_OPT="device=$1" +else + DEVICE_OPT="" +fi + +gst-launch rsndvdbin name=dvd "$DEVICE_OPT" \ + dvd. ! ffmpegcolorspace ! videoscale ! autovideosink \ + dvd. ! audioconvert ! autoaudiosink diff --git a/ext/resindvd/resindvdbin.c b/ext/resindvd/resindvdbin.c new file mode 100644 index 00000000..9fe091f2 --- /dev/null +++ b/ext/resindvd/resindvdbin.c @@ -0,0 +1,758 @@ +/* GStreamer + * Copyright (C) 2008 Jan Schmidt <thaytan@noraisin.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <string.h> + +#include <gst/gst.h> + +#include "resindvdbin.h" +#include "resindvdsrc.h" +#include "rsnstreamselector.h" +#include "rsnaudiomunge.h" + +#include "gstmpegdemux.h" + +GST_DEBUG_CATEGORY_EXTERN (resindvd_debug); +#define GST_CAT_DEFAULT resindvd_debug + +#define DECODEBIN_AUDIO 0 + +#define DVDBIN_LOCK(d) g_mutex_lock((d)->dvd_lock) +#define DVDBIN_UNLOCK(d) g_mutex_unlock((d)->dvd_lock) + +#define DEFAULT_DEVICE "/dev/dvd" +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + ARG_0, + ARG_DEVICE +}; + +/* FIXME: Could list specific video and audio caps: */ +static GstStaticPadTemplate video_src_template = +GST_STATIC_PAD_TEMPLATE ("video", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate audio_src_template = +GST_STATIC_PAD_TEMPLATE ("audio", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static void rsn_dvdbin_do_init (GType rsn_dvdbin_type); +static void rsn_dvdbin_finalize (GObject * object); +static void rsn_dvdbin_uri_handler_init (gpointer g_iface, gpointer iface_data); + +GST_BOILERPLATE_FULL (RsnDvdBin, rsn_dvdbin, GstBin, + GST_TYPE_BIN, rsn_dvdbin_do_init); + +static void demux_pad_added (GstElement * element, GstPad * pad, + RsnDvdBin * dvdbin); +static void viddec_pad_added (GstElement * element, GstPad * pad, + gboolean last, RsnDvdBin * dvdbin); +#if DECODEBIN_AUDIO +static void auddec_pad_added (GstElement * element, GstPad * pad, + gboolean last, RsnDvdBin * dvdbin); +#endif +static void rsn_dvdbin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void rsn_dvdbin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static GstStateChangeReturn rsn_dvdbin_change_state (GstElement * element, + GstStateChange transition); + +static void +rsn_dvdbin_base_init (gpointer gclass) +{ + static GstElementDetails element_details = { + "rsndvdbin", + "Generic/Bin/Player", + "DVD playback element", + "Jan Schmidt <thaytan@noraisin.net>" + }; + GstElementClass *element_class = GST_ELEMENT_CLASS (gclass); + + 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 (&audio_src_template)); + gst_element_class_set_details (element_class, &element_details); + + element_class->change_state = GST_DEBUG_FUNCPTR (rsn_dvdbin_change_state); +} + +static void +rsn_dvdbin_class_init (RsnDvdBinClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->finalize = rsn_dvdbin_finalize; + gobject_class->set_property = rsn_dvdbin_set_property; + gobject_class->get_property = rsn_dvdbin_get_property; + + g_object_class_install_property (gobject_class, ARG_DEVICE, + g_param_spec_string ("device", "Device", "DVD device location", + NULL, G_PARAM_READWRITE)); +} + +static void +rsn_dvdbin_do_init (GType rsn_dvdbin_type) +{ + static const GInterfaceInfo urihandler_info = { + rsn_dvdbin_uri_handler_init, + NULL, + NULL + }; + + g_type_add_interface_static (rsn_dvdbin_type, GST_TYPE_URI_HANDLER, + &urihandler_info); +} + +static void +rsn_dvdbin_init (RsnDvdBin * dvdbin, RsnDvdBinClass * gclass) +{ + dvdbin->dvd_lock = g_mutex_new (); +} + +static void +rsn_dvdbin_finalize (GObject * object) +{ + RsnDvdBin *dvdbin = RESINDVDBIN (object); + + g_mutex_free (dvdbin->dvd_lock); + g_free (dvdbin->last_uri); + g_free (dvdbin->device); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/* URI interface */ +static guint +rsn_dvdbin_uri_get_type (void) +{ + return GST_URI_SRC; +} + +static gchar ** +rsn_dvdbin_uri_get_protocols (void) +{ + static gchar *protocols[] = { "dvd", NULL }; + + return protocols; +} + +static const gchar * +rsn_dvdbin_uri_get_uri (GstURIHandler * handler) +{ + RsnDvdBin *dvdbin = RESINDVDBIN (handler); + + DVDBIN_LOCK (dvdbin); + g_free (dvdbin->last_uri); + if (dvdbin->device) + dvdbin->last_uri = g_strdup_printf ("dvd://%s", dvdbin->device); + else + dvdbin->last_uri = g_strdup ("dvd://"); + DVDBIN_UNLOCK (dvdbin); + + return dvdbin->last_uri; +} + +static gboolean +rsn_dvdbin_uri_set_uri (GstURIHandler * handler, const gchar * uri) +{ + // RsnDvdBin *dvdbin = RESINDVDBIN (handler); + gboolean ret; + gchar *protocol, *location; + + protocol = gst_uri_get_protocol (uri); + + ret = (protocol && !strcmp (protocol, "dvd")) ? TRUE : FALSE; + + g_free (protocol); + protocol = NULL; + + if (!ret) + return ret; + + location = gst_uri_get_location (uri); + if (!location) + return ret; + /* + * Parse out the device name + */ +#if 0 + /* + * Parse out the new t/c/a and seek to them + */ + { + gchar **strs; + gchar **strcur; + gint pos = 0; + + location = gst_uri_get_location (uri); + + if (!location) + return ret; + + strcur = strs = g_strsplit (location, ",", 0); + while (strcur && *strcur) { + gint val; + + if (!sscanf (*strcur, "%d", &val)) + break; + + switch (pos) { + case 0: + if (val != dvdbin->uri_title) { + dvdbin->uri_title = val; + dvdbin->new_seek = TRUE; + } + break; + case 1: + if (val != dvdbin->uri_chapter) { + dvdbin->uri_chapter = val; + dvdbin->new_seek = TRUE; + } + break; + case 2: + dvdbin->uri_angle = val; + break; + } + + strcur++; + pos++; + } + + g_strfreev (strs); + } +#endif + + g_free (location); + + return ret; +} + +static void +rsn_dvdbin_uri_handler_init (gpointer g_iface, gpointer iface_data) +{ + GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; + + iface->get_type = rsn_dvdbin_uri_get_type; + iface->get_protocols = rsn_dvdbin_uri_get_protocols; + iface->get_uri = rsn_dvdbin_uri_get_uri; + iface->set_uri = rsn_dvdbin_uri_set_uri; +} + +static gboolean +try_create_piece (RsnDvdBin * dvdbin, gint index, + const gchar * factory, GType type, const gchar * name, const gchar * descr) +{ + GstElement *e; + + DVDBIN_LOCK (dvdbin); + if (dvdbin->pieces[index] != NULL) { + DVDBIN_UNLOCK (dvdbin); + return TRUE; /* Already exists */ + } + DVDBIN_UNLOCK (dvdbin); + + if (factory != NULL) { + e = gst_element_factory_make (factory, name); + } else { + if (name) + e = g_object_new (type, "name", name, NULL); + else + e = g_object_new (type, NULL); + } + if (e == NULL) + goto create_failed; + + if (!gst_bin_add (GST_BIN (dvdbin), e)) + goto add_failed; + + GST_DEBUG_OBJECT (dvdbin, "Added %s element: %" GST_PTR_FORMAT, descr, e); + + DVDBIN_LOCK (dvdbin); + dvdbin->pieces[index] = e; + DVDBIN_UNLOCK (dvdbin); + + return TRUE; +create_failed: + GST_ELEMENT_ERROR (dvdbin, CORE, MISSING_PLUGIN, (NULL), + ("Could not create %s element '%s'", descr, factory)); + return FALSE; +add_failed: + gst_object_unref (e); + GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL), + ("Could not add %s element to bin", descr)); + return FALSE; +} + +static gboolean +create_elements (RsnDvdBin * dvdbin) +{ + GstPad *src = NULL; + GstPad *sink = NULL; + GstPad *ghost = NULL; + + if (!try_create_piece (dvdbin, DVD_ELEM_SOURCE, NULL, + RESIN_TYPE_DVDSRC, "dvdsrc", "DVD source")) { + return FALSE; + } + + /* FIXME: Locking */ + if (dvdbin->device) { + g_object_set (G_OBJECT (dvdbin->pieces[DVD_ELEM_SOURCE]), + "device", dvdbin->device, NULL); + } + + if (!try_create_piece (dvdbin, DVD_ELEM_DEMUX, + NULL, GST_TYPE_FLUPS_DEMUX, "dvddemux", "DVD demuxer")) + return FALSE; + + if (gst_element_link (dvdbin->pieces[DVD_ELEM_SOURCE], + dvdbin->pieces[DVD_ELEM_DEMUX]) == FALSE) + goto failed_connect; + + /* Listen for new pads from the demuxer */ + g_signal_connect (G_OBJECT (dvdbin->pieces[DVD_ELEM_DEMUX]), "pad-added", + G_CALLBACK (demux_pad_added), dvdbin); + + if (!try_create_piece (dvdbin, DVD_ELEM_MQUEUE, "multiqueue", 0, "mq", + "multiqueue")) + return FALSE; + + g_object_set (dvdbin->pieces[DVD_ELEM_MQUEUE], + "max-size-time", (7 * GST_SECOND / 10), "max-size-bytes", 0, + "max-size-buffers", 0, NULL); + + /* Decodebin will throw a missing element message to find an MPEG decoder */ + if (!try_create_piece (dvdbin, DVD_ELEM_VIDDEC, "decodebin", 0, "viddec", + "video decoder")) + return FALSE; + + g_signal_connect (G_OBJECT (dvdbin->pieces[DVD_ELEM_VIDDEC]), + "new-decoded-pad", G_CALLBACK (viddec_pad_added), dvdbin); + + if (!try_create_piece (dvdbin, DVD_ELEM_VIDQ, "queue", 0, "vid_q", + "video decoder buffer")) + return FALSE; + + g_object_set (dvdbin->pieces[DVD_ELEM_VIDQ], + "max-size-time", G_GUINT64_CONSTANT (0), "max-size-bytes", 0, + "max-size-buffers", 3, NULL); + + if (!try_create_piece (dvdbin, DVD_ELEM_SPU_SELECT, NULL, + RSN_TYPE_STREAM_SELECTOR, "subpselect", "Subpicture stream selector")) + return FALSE; + + if (!try_create_piece (dvdbin, DVD_ELEM_SPU, "dvdspu", 0, "spu", + "Subpicture overlay")) + return FALSE; + + sink = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_SPU], "video"); + src = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_VIDQ], "src"); + if (src == NULL || sink == NULL) + goto failed_spu_connect; + if (GST_PAD_LINK_FAILED (gst_pad_link (src, sink))) + goto failed_spu_connect; + gst_object_unref (sink); + gst_object_unref (src); + src = sink = NULL; + + sink = + gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_SPU], "subpicture"); + src = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_SPU_SELECT], "src"); + if (src == NULL || sink == NULL) + goto failed_spu_connect; + if (GST_PAD_LINK_FAILED (gst_pad_link (src, sink))) + goto failed_spu_connect; + gst_object_unref (sink); + gst_object_unref (src); + src = sink = NULL; + + src = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_SPU], "src"); + if (src == NULL) + goto failed_spu_ghost; + ghost = gst_ghost_pad_new ("video", src); + if (ghost == NULL) + goto failed_spu_ghost; + if (!gst_element_add_pad (GST_ELEMENT (dvdbin), ghost)) + goto failed_spu_ghost; + gst_object_unref (src); + src = sink = NULL; + + if (!try_create_piece (dvdbin, DVD_ELEM_AUD_SELECT, NULL, + RSN_TYPE_STREAM_SELECTOR, "audioselect", "Audio stream selector")) + return FALSE; + + /* rsnaudiomunge goes after the audio decoding to regulate the stream */ + if (!try_create_piece (dvdbin, DVD_ELEM_AUD_MUNGE, NULL, + RSN_TYPE_AUDIOMUNGE, "audiomunge", "Audio output filter")) + return FALSE; + +#if DECODEBIN_AUDIO + /* Decodebin will throw a missing element message to find a suitable + * decoder */ + if (!try_create_piece (dvdbin, DVD_ELEM_AUDDEC, "decodebin", 0, "auddec", + "audio decoder")) + return FALSE; + + g_signal_connect (G_OBJECT (dvdbin->pieces[DVD_ELEM_AUDDEC]), + "new-decoded-pad", G_CALLBACK (auddec_pad_added), dvdbin); +#else + if (!try_create_piece (dvdbin, DVD_ELEM_AUDDEC, "a52dec", 0, "auddec", + "audio decoder")) + return FALSE; + + src = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_AUDDEC], "src"); + sink = + gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_AUD_MUNGE], "sink"); + if (src == NULL || sink == NULL) + goto failed_aud_connect; + if (GST_PAD_LINK_FAILED (gst_pad_link (src, sink))) + goto failed_aud_connect; + gst_object_unref (sink); + gst_object_unref (src); + src = sink = NULL; +#endif + + src = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_AUD_SELECT], "src"); + sink = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_AUDDEC], "sink"); + if (src == NULL || sink == NULL) + goto failed_aud_connect; + if (GST_PAD_LINK_FAILED (gst_pad_link (src, sink))) + goto failed_aud_connect; + gst_object_unref (sink); + gst_object_unref (src); + src = sink = NULL; + + /* ghost audio munge output pad onto bin */ + src = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_AUD_MUNGE], "src"); + if (src == NULL) + goto failed_aud_ghost; + ghost = gst_ghost_pad_new ("audio", src); + if (ghost == NULL) + goto failed_aud_ghost; + if (!gst_element_add_pad (GST_ELEMENT (dvdbin), ghost)) + goto failed_aud_ghost; + gst_object_unref (src); + src = sink = NULL; + + return TRUE; + +failed_connect: + GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL), + ("Could not connect DVD source and demuxer elements")); + return FALSE; +failed_spu_connect: + GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL), + ("Could not connect DVD video buffer and spu elements")); + if (src != NULL) + gst_object_unref (src); + if (sink != NULL) + gst_object_unref (sink); + return FALSE; +failed_spu_ghost: + GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL), + ("Could not ghost SPU output pad")); + if (src != NULL) + gst_object_unref (src); + if (ghost != NULL) + gst_object_unref (ghost); + return FALSE; +failed_aud_connect: + GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL), + ("Could not connect DVD audio decoder")); + if (src != NULL) + gst_object_unref (src); + if (sink != NULL) + gst_object_unref (sink); + return FALSE; +failed_aud_ghost: + GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL), + ("Could not ghost audio output pad")); + if (ghost != NULL) + gst_object_unref (ghost); + return FALSE; +} + +static void +remove_elements (RsnDvdBin * dvdbin) +{ + gint i; + GList *tmp; + + if (dvdbin->pieces[DVD_ELEM_MQUEUE] != NULL) { + for (tmp = dvdbin->mq_req_pads; tmp; tmp = g_list_next (tmp)) { + gst_element_release_request_pad (dvdbin->pieces[DVD_ELEM_MQUEUE], + GST_PAD (tmp->data)); + } + } + g_list_free (dvdbin->mq_req_pads); + dvdbin->mq_req_pads = NULL; + + for (i = 0; i < DVD_ELEM_LAST; i++) { + DVDBIN_LOCK (dvdbin); + if (dvdbin->pieces[i] != NULL) { + GstElement *piece = dvdbin->pieces[i]; + dvdbin->pieces[i] = NULL; + DVDBIN_UNLOCK (dvdbin); + + gst_element_set_state (piece, GST_STATE_NULL); + gst_bin_remove (GST_BIN (dvdbin), piece); + } else + DVDBIN_UNLOCK (dvdbin); + } +} + +static GstPad * +connect_thru_mq (RsnDvdBin * dvdbin, GstPad * pad) +{ + GstPad *mq_sink; + GstPad *mq_src; + gchar *tmp, *sinkname, *srcname; + + /* Request a pad from multiqueue, then connect this one, then + * discover the corresponding output pad and return it */ + mq_sink = gst_element_get_request_pad (dvdbin->pieces[DVD_ELEM_MQUEUE], + "sink%d"); + if (mq_sink == NULL) + return FALSE; + dvdbin->mq_req_pads = g_list_prepend (dvdbin->mq_req_pads, mq_sink); + + if (gst_pad_link (pad, mq_sink) != GST_PAD_LINK_OK) + return FALSE; + + sinkname = gst_pad_get_name (mq_sink); + tmp = sinkname + 4; + srcname = g_strdup_printf ("src%s", tmp); + + mq_src = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_MQUEUE], + srcname); + + g_free (sinkname); + g_free (srcname); + + return mq_src; +} + +static void +demux_pad_added (GstElement * element, GstPad * pad, RsnDvdBin * dvdbin) +{ + gboolean skip_mq = FALSE; + GstPad *mq_pad = NULL; + GstPad *dest_pad = NULL; + GstCaps *caps; + GstStructure *s; + + GST_ERROR_OBJECT (dvdbin, "New pad: %" GST_PTR_FORMAT, pad); + + caps = gst_pad_get_caps (pad); + if (caps == NULL) { + GST_WARNING_OBJECT (dvdbin, "NULL caps from pad %" GST_PTR_FORMAT, pad); + return; + } + if (!gst_caps_is_fixed (caps)) { + GST_WARNING_OBJECT (dvdbin, "Unfixed caps %" GST_PTR_FORMAT + " on pad %" GST_PTR_FORMAT, caps, pad); + gst_caps_unref (caps); + return; + } + + s = gst_caps_get_structure (caps, 0); + g_return_if_fail (s != NULL); + + if (g_str_equal (gst_structure_get_name (s), "video/mpeg")) { + dest_pad = + gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_VIDDEC], "sink"); + } else if (g_str_equal (gst_structure_get_name (s), "video/x-dvd-subpicture")) { + dest_pad = + gst_element_get_request_pad (dvdbin->pieces[DVD_ELEM_SPU_SELECT], + "sink%d"); + skip_mq = TRUE; + } else if (g_str_equal (gst_structure_get_name (s), "audio/x-private1-ac3")) { + dest_pad = + gst_element_get_request_pad (dvdbin->pieces[DVD_ELEM_AUD_SELECT], + "sink%d"); + } + + gst_caps_unref (caps); + + if (dest_pad == NULL) { + GST_DEBUG_OBJECT (dvdbin, "Don't know how to handle pad. Ignoring"); + return; + } + + if (skip_mq) { + mq_pad = gst_object_ref (pad); + } else { + mq_pad = connect_thru_mq (dvdbin, pad); + if (mq_pad == NULL) + goto failed; + GST_ERROR_OBJECT (dvdbin, "Linking new pad %" GST_PTR_FORMAT + " through multiqueue to %" GST_PTR_FORMAT, pad, dest_pad); + } + + gst_pad_link (mq_pad, dest_pad); + + gst_object_unref (mq_pad); + gst_object_unref (dest_pad); + + return; +failed: + GST_ELEMENT_ERROR (dvdbin, CORE, FAILED, (NULL), + ("Failed to handle new demuxer pad %s", GST_PAD_NAME (pad))); + if (mq_pad) + gst_object_unref (mq_pad); + if (dest_pad) + gst_object_unref (dest_pad); + return; +} + +static void +viddec_pad_added (GstElement * element, GstPad * pad, gboolean last, + RsnDvdBin * dvdbin) +{ + GstPad *q_pad; + + GST_ERROR_OBJECT (dvdbin, "New video pad: %" GST_PTR_FORMAT, pad); + + q_pad = gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_VIDQ], "sink"); + gst_pad_link (pad, q_pad); + + gst_object_unref (q_pad); +} + +#if DECODEBIN_AUDIO +static void +auddec_pad_added (GstElement * element, GstPad * pad, gboolean last, + RsnDvdBin * dvdbin) +{ + GstPad *out_pad; + + GST_ERROR_OBJECT (dvdbin, "New audio pad: %" GST_PTR_FORMAT, pad); + + out_pad = + gst_element_get_static_pad (dvdbin->pieces[DVD_ELEM_AUD_MUNGE], "sink"); + gst_pad_link (pad, out_pad); + + gst_object_unref (out_pad); +} +#endif + +static void +rsn_dvdbin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + RsnDvdBin *dvdbin = RESINDVDBIN (object); + + switch (prop_id) { + case ARG_DEVICE: + DVDBIN_LOCK (dvdbin); + g_free (dvdbin->device); + if (g_value_get_string (value) == NULL) + dvdbin->device = g_strdup (DEFAULT_DEVICE); + else + dvdbin->device = g_value_dup_string (value); + + if (dvdbin->pieces[DVD_ELEM_SOURCE]) { + g_object_set_property (G_OBJECT (dvdbin->pieces[DVD_ELEM_SOURCE]), + "device", value); + } + DVDBIN_UNLOCK (dvdbin); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +rsn_dvdbin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + RsnDvdBin *dvdbin = RESINDVDBIN (object); + + switch (prop_id) { + case ARG_DEVICE: + DVDBIN_LOCK (dvdbin); + if (dvdbin->device) + g_value_set_string (value, dvdbin->device); + else if (dvdbin->pieces[DVD_ELEM_SOURCE]) + g_object_get_property (G_OBJECT (dvdbin->pieces[DVD_ELEM_SOURCE]), + "device", value); + else + g_value_set_string (value, DEFAULT_DEVICE); + DVDBIN_UNLOCK (dvdbin); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstStateChangeReturn +rsn_dvdbin_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + RsnDvdBin *dvdbin = RESINDVDBIN (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!create_elements (dvdbin)) { + remove_elements (dvdbin); + return GST_STATE_CHANGE_FAILURE; + } + gst_element_no_more_pads (GST_ELEMENT (dvdbin)); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + case GST_STATE_CHANGE_READY_TO_NULL: + remove_elements (dvdbin); + break; + default: + break; + } + + return ret; +} diff --git a/ext/resindvd/resindvdbin.h b/ext/resindvd/resindvdbin.h new file mode 100644 index 00000000..87f4261e --- /dev/null +++ b/ext/resindvd/resindvdbin.h @@ -0,0 +1,80 @@ +/* GStreamer + * Copyright (C) 2008 Jan Schmidt <thaytan@noraisin.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __RESINDVDBIN_H__ +#define __RESINDVDBIN_H__ + +#include <gst/gst.h> + +G_BEGIN_DECLS + +/* #defines don't like whitespacey bits */ +#define RESIN_TYPE_DVDBIN \ + (rsn_dvdbin_get_type()) +#define RESINDVDBIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),RESIN_TYPE_DVDBIN,RsnDvdBin)) +#define RESINDVDBIN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),RESIN_TYPE_DVDBIN,RsnDvdBinClass)) +#define IS_RESINDVDBIN(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),RESIN_TYPE_DVDBIN)) +#define IS_RESINDVDBIN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),RESIN_TYPE_DVDBIN)) + +typedef struct _RsnDvdBin RsnDvdBin; +typedef struct _RsnDvdBinClass RsnDvdBinClass; + +#define DVD_ELEM_SOURCE 0 +#define DVD_ELEM_DEMUX 1 +#define DVD_ELEM_MQUEUE 2 +#define DVD_ELEM_SPU 3 +#define DVD_ELEM_VIDDEC 4 +#define DVD_ELEM_AUDDEC 5 +#define DVD_ELEM_VIDQ 6 +#define DVD_ELEM_SPU_SELECT 7 +#define DVD_ELEM_AUD_SELECT 8 +#define DVD_ELEM_AUD_MUNGE 9 +#define DVD_ELEM_LAST 10 + +struct _RsnDvdBin +{ + GstBin element; + + /* Protects pieces list and properties */ + GMutex *dvd_lock; + + gchar *device; + gchar *last_uri; + GstElement *pieces[DVD_ELEM_LAST]; + + GstPad *video_pad; + GstPad *audio_pad; + + GList *mq_req_pads; +}; + +struct _RsnDvdBinClass +{ + GstBinClass parent_class; +}; + +GType rsn_dvdbin_get_type (void); + +G_END_DECLS + +#endif /* __RESINDVDBIN_H__ */ diff --git a/ext/resindvd/resindvdsrc.c b/ext/resindvd/resindvdsrc.c new file mode 100644 index 00000000..c98870c7 --- /dev/null +++ b/ext/resindvd/resindvdsrc.c @@ -0,0 +1,1426 @@ +/* GStreamer + * Copyright (C) 2008 Jan Schmidt <thaytan@noraisin.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <gst/gst.h> +// #include <gst/gst-i18n-plugin.h> +#define _(s) s /* FIXME - add i18n bits to build */ + +#include "resindvdsrc.h" + +GST_DEBUG_CATEGORY_STATIC (rsndvdsrc_debug); +#define GST_CAT_DEFAULT rsndvdsrc_debug + +#define DEFAULT_DEVICE "/dev/dvd" + +#define GST_FLOW_WOULD_BLOCK GST_FLOW_CUSTOM_SUCCESS + +#define CLOCK_BASE 9LL +#define CLOCK_FREQ CLOCK_BASE * 10000 + +#define MPEGTIME_TO_GSTTIME(time) (((time) * (GST_MSECOND/10)) / CLOCK_BASE) +#define GSTTIME_TO_MPEGTIME(time) (((time) * CLOCK_BASE) / (GST_MSECOND/10)) + +typedef enum +{ + RSN_NAV_RESULT_NONE, + RSN_NAV_RESULT_HIGHLIGHT, + RSN_NAV_RESULT_BRANCH +} RsnNavResult; + +typedef enum +{ + RSN_NAV_ACTION_ACTIVATE, + RSN_NAV_ACTION_LEFT, + RSN_NAV_ACTION_RIGHT, + RSN_NAV_ACTION_DOWN, + RSN_NAV_ACTION_UP +} RsnNavAction; + +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + ARG_0, + ARG_DEVICE +}; + +static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-resin-dvd") + ); + +/* Private seek format for private flushing */ +static GstFormat rsndvd_format; + +static void rsn_dvdsrc_register_extra (GType rsn_dvdsrc_type); + +GST_BOILERPLATE_FULL (resinDvdSrc, rsn_dvdsrc, RsnPushSrc, + RSN_TYPE_PUSH_SRC, rsn_dvdsrc_register_extra); + +static gboolean read_vts_info (resinDvdSrc * src); + +static void rsn_dvdsrc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void rsn_dvdsrc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static void rsn_dvdsrc_finalize (GObject * object); + +static gboolean rsn_dvdsrc_start (RsnBaseSrc * bsrc); +static gboolean rsn_dvdsrc_stop (RsnBaseSrc * bsrc); +static gboolean rsn_dvdsrc_unlock (RsnBaseSrc * bsrc); +static gboolean rsn_dvdsrc_unlock_stop (RsnBaseSrc * bsrc); + +static gboolean rsn_dvdsrc_prepare_seek (RsnBaseSrc * bsrc, GstEvent * event, + GstSegment * segment); +static gboolean rsn_dvdsrc_do_seek (RsnBaseSrc * bsrc, GstSegment * segment); + +static void rsn_dvdsrc_prepare_spu_stream_event (resinDvdSrc * src, + guint8 phys_stream, gboolean forced_only); +static void rsn_dvdsrc_prepare_audio_stream_event (resinDvdSrc * src, + guint8 phys_stream); +static gboolean rsn_dvdsrc_prepare_streamsinfo_event (resinDvdSrc * src); +static void rsn_dvdsrc_prepare_clut_change_event (resinDvdSrc * src, + const guint32 * clut); +static void rsn_dvdsrc_update_highlight (resinDvdSrc * src); + +static GstFlowReturn rsn_dvdsrc_create (RsnPushSrc * psrc, GstBuffer ** buf); +static gboolean rsn_dvdsrc_src_event (RsnBaseSrc * basesrc, GstEvent * event); + +static void +rsn_dvdsrc_register_extra (GType rsn_dvdsrc_type) +{ + GST_DEBUG_CATEGORY_INIT (rsndvdsrc_debug, "rsndvdsrc", 0, + "Resin DVD source element based on libdvdnav"); + + rsndvd_format = gst_format_register ("rsndvdsrc-internal", + "private Resin DVD src format"); +} + +static void +rsn_dvdsrc_base_init (gpointer gclass) +{ + static GstElementDetails element_details = { + "Resin DVD Src", + "Source/DVD", + "DVD source element", + "Jan Schmidt <thaytan@noraisin.net>" + }; + GstElementClass *element_class = GST_ELEMENT_CLASS (gclass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_factory)); + gst_element_class_set_details (element_class, &element_details); +} + +static void +rsn_dvdsrc_class_init (resinDvdSrcClass * klass) +{ + GObjectClass *gobject_class; + RsnBaseSrcClass *gstbasesrc_class; + RsnPushSrcClass *gstpush_src_class; + + gobject_class = (GObjectClass *) klass; + gstbasesrc_class = GST_BASE_SRC_CLASS (klass); + gstpush_src_class = GST_PUSH_SRC_CLASS (klass); + + gobject_class->finalize = rsn_dvdsrc_finalize; + gobject_class->set_property = rsn_dvdsrc_set_property; + gobject_class->get_property = rsn_dvdsrc_get_property; + + gstbasesrc_class->start = GST_DEBUG_FUNCPTR (rsn_dvdsrc_start); + gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (rsn_dvdsrc_stop); + gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (rsn_dvdsrc_unlock); + gstbasesrc_class->unlock_stop = GST_DEBUG_FUNCPTR (rsn_dvdsrc_unlock_stop); + gstbasesrc_class->event = GST_DEBUG_FUNCPTR (rsn_dvdsrc_src_event); + gstbasesrc_class->prepare_seek_segment = + GST_DEBUG_FUNCPTR (rsn_dvdsrc_prepare_seek); + gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (rsn_dvdsrc_do_seek); + + gstpush_src_class->create = GST_DEBUG_FUNCPTR (rsn_dvdsrc_create); + + g_object_class_install_property (gobject_class, ARG_DEVICE, + g_param_spec_string ("device", "Device", "DVD device location", + NULL, G_PARAM_READWRITE)); +} + +static void +rsn_dvdsrc_init (resinDvdSrc * rsndvdsrc, resinDvdSrcClass * gclass) +{ + rsndvdsrc->device = g_strdup (DEFAULT_DEVICE); + rsndvdsrc->dvd_lock = g_mutex_new (); + rsndvdsrc->branch_lock = g_mutex_new (); + rsndvdsrc->branching = FALSE; + rsndvdsrc->still_cond = g_cond_new (); + + rsn_base_src_set_format (GST_BASE_SRC (rsndvdsrc), GST_FORMAT_TIME); +} + +static void +rsn_dvdsrc_finalize (GObject * object) +{ + resinDvdSrc *src = RESINDVDSRC (object); + g_mutex_free (src->dvd_lock); + g_mutex_free (src->branch_lock); + g_cond_free (src->still_cond); + + gst_buffer_replace (&src->alloc_buf, NULL); + gst_buffer_replace (&src->next_buf, NULL); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +rsn_dvdsrc_unlock (RsnBaseSrc * bsrc) +{ + resinDvdSrc *src = RESINDVDSRC (bsrc); + + g_mutex_lock (src->branch_lock); + src->branching = TRUE; + g_cond_broadcast (src->still_cond); + g_mutex_unlock (src->branch_lock); + + return TRUE; +} + +static gboolean +rsn_dvdsrc_unlock_stop (RsnBaseSrc * bsrc) +{ + resinDvdSrc *src = RESINDVDSRC (bsrc); + + g_mutex_lock (src->branch_lock); + src->branching = FALSE; + g_mutex_unlock (src->branch_lock); + + return TRUE; +} + +static void +rsn_dvdsrc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + resinDvdSrc *src = RESINDVDSRC (object); + + switch (prop_id) { + case ARG_DEVICE: + GST_OBJECT_LOCK (src); + g_free (src->device); + if (g_value_get_string (value) == NULL) + src->device = g_strdup (DEFAULT_DEVICE); + else + src->device = g_value_dup_string (value); + GST_OBJECT_UNLOCK (src); + g_print ("Device is now %s\n", src->device); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +rsn_dvdsrc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + resinDvdSrc *src = RESINDVDSRC (object); + + switch (prop_id) { + case ARG_DEVICE: + GST_OBJECT_LOCK (src); + g_value_set_string (value, src->device); + GST_OBJECT_UNLOCK (src); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +rsn_dvdsrc_start (RsnBaseSrc * bsrc) +{ + resinDvdSrc *src = RESINDVDSRC (bsrc); + + g_mutex_lock (src->dvd_lock); + if (!read_vts_info (src)) { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, + (_("Could not read title information for DVD.")), GST_ERROR_SYSTEM); + goto fail; + } + + if (dvdnav_open (&src->dvdnav, src->device) != DVDNAV_STATUS_OK) { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), + (_("Failed to open DVD device '%s'."), src->device)); + goto fail; + } + + src->running = TRUE; + src->branching = FALSE; + src->discont = TRUE; + src->need_segment = TRUE; + + src->cur_position = GST_CLOCK_TIME_NONE; + src->cur_start_ts = GST_CLOCK_TIME_NONE; + src->cur_end_ts = GST_CLOCK_TIME_NONE; + + src->vts_n = 0; + src->in_menu = FALSE; + + src->active_button = -1; + g_mutex_unlock (src->dvd_lock); + + return TRUE; + +fail: + if (src->dvdnav) { + dvdnav_close (src->dvdnav); + src->dvdnav = NULL; + } + g_mutex_unlock (src->dvd_lock); + return FALSE; +} + +/* Use libdvdread to read and cache info from the IFO file about + * streams in each VTS */ +static gboolean +read_vts_info (resinDvdSrc * src) +{ + gint i; + gint n_vts; + + if (src->vts_attrs) { + g_array_free (src->vts_attrs, TRUE); + src->vts_attrs = NULL; + } + + if (src->dvdread) + DVDClose (src->dvdread); + + src->dvdread = DVDOpen (src->device); + if (src->dvdread == NULL) + return FALSE; + + if (!(src->vmg_file = ifoOpen (src->dvdread, 0))) { + GST_ERROR ("Can't open VMG ifo"); + return FALSE; + } + n_vts = src->vmg_file->vts_atrt->nr_of_vtss; + memcpy (&src->vmgm_attr, src->vmg_file->vmgi_mat, sizeof (vmgi_mat_t)); + + GST_DEBUG ("Reading IFO info for %d VTSs", n_vts); + src->vts_attrs = + g_array_sized_new (FALSE, TRUE, sizeof (vtsi_mat_t), n_vts + 1); + if (!src->vts_attrs) + return FALSE; + g_array_set_size (src->vts_attrs, n_vts + 1); + + for (i = 1; i <= n_vts; i++) { + ifo_handle_t *ifo = ifoOpen (src->dvdread, i); + if (!ifo) { + GST_ERROR ("Can't open VTS %d", i); + return FALSE; + } + + GST_DEBUG ("VTS %d, Menu has %d audio %d subpictures. " + "Title has %d and %d", i, + ifo->vtsi_mat->nr_of_vtsm_audio_streams, + ifo->vtsi_mat->nr_of_vtsm_subp_streams, + ifo->vtsi_mat->nr_of_vts_audio_streams, + ifo->vtsi_mat->nr_of_vts_subp_streams); + + memcpy (&g_array_index (src->vts_attrs, vtsi_mat_t, i), + ifo->vtsi_mat, sizeof (vtsi_mat_t)); + + ifoClose (ifo); + } + + return TRUE; +} + +static gboolean +rsn_dvdsrc_stop (RsnBaseSrc * bsrc) +{ + resinDvdSrc *src = RESINDVDSRC (bsrc); + gboolean ret = TRUE; + + g_mutex_lock (src->dvd_lock); + + /* Clear any allocated output buffer */ + gst_buffer_replace (&src->alloc_buf, NULL); + gst_buffer_replace (&src->next_buf, NULL); + src->running = FALSE; + + if (src->streams_event) { + gst_event_unref (src->streams_event); + src->streams_event = NULL; + } + if (src->clut_event) { + gst_event_unref (src->clut_event); + src->clut_event = NULL; + } + if (src->spu_select_event) { + gst_event_unref (src->spu_select_event); + src->spu_select_event = NULL; + } + if (src->audio_select_event) { + gst_event_unref (src->audio_select_event); + src->audio_select_event = NULL; + } + if (src->highlight_event) { + gst_event_unref (src->highlight_event); + src->highlight_event = NULL; + } + + if (src->dvdnav) { + if (dvdnav_close (src->dvdnav) != DVDNAV_STATUS_OK) { + GST_ELEMENT_ERROR (src, RESOURCE, CLOSE, (NULL), + ("dvdnav_close failed: %s", dvdnav_err_to_string (src->dvdnav))); + ret = FALSE; + } + src->dvdnav = NULL; + } + + if (src->vmg_file) { + ifoClose (src->vmg_file); + src->vmg_file = NULL; + } + if (src->vts_file) { + ifoClose (src->vts_file); + src->vts_file = NULL; + } + if (src->dvdread) { + DVDClose (src->dvdread); + src->dvdread = NULL; + } + + g_mutex_unlock (src->dvd_lock); + + return ret; +} + +/* handle still events. Call with dvd_lock */ +static gboolean +rsn_dvdsrc_do_still (resinDvdSrc * src, int duration) +{ + GstEvent *still_event; + GstStructure *s; + GstEvent *seg_event; + GstSegment *segment = &(GST_BASE_SRC (src)->segment); + + g_print ("**** STILL FRAME. Duration %d ****\n", duration); + + /* Send a close-segment event, and a dvd-still start + * event, then sleep */ + s = gst_structure_new ("application/x-gst-dvd", + "event", G_TYPE_STRING, "dvd-still", + "still-state", G_TYPE_BOOLEAN, TRUE, NULL); + still_event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); + + segment->last_stop = src->cur_end_ts; + + seg_event = gst_event_new_new_segment_full (TRUE, + segment->rate, segment->applied_rate, segment->format, + segment->start, segment->last_stop, segment->time); + + /* Now, send the events. We need to drop the dvd lock while doing so, + * and then check after if we got flushed + */ + g_mutex_unlock (src->dvd_lock); + gst_pad_push_event (GST_BASE_SRC_PAD (src), still_event); + gst_pad_push_event (GST_BASE_SRC_PAD (src), seg_event); + g_mutex_lock (src->dvd_lock); + + g_mutex_lock (src->branch_lock); + if (src->branching) { + g_mutex_unlock (src->branch_lock); + return TRUE; + } + + if (duration == 255) { + /* + * The only way to get woken from this still is by a flushing + * seek or a user action. Either one will clear the still, so + * don't skip it + */ + src->need_segment = TRUE; + g_mutex_unlock (src->dvd_lock); + g_cond_wait (src->still_cond, src->branch_lock); + if (src->branching) { + g_mutex_unlock (src->branch_lock); + g_mutex_lock (src->dvd_lock); + return TRUE; + } + g_mutex_unlock (src->branch_lock); + g_mutex_lock (src->dvd_lock); + } else { + /* FIXME: Implement timed stills by sleeping on the clock, possibly + * in multiple steps if we get paused/unpaused */ + if (dvdnav_still_skip (src->dvdnav) != DVDNAV_STATUS_OK) + return FALSE; + + /* Later: We'll only do this if the still isn't interrupted: */ + s = gst_structure_new ("application/x-gst-dvd", + "event", G_TYPE_STRING, "dvd-still", + "still-state", G_TYPE_BOOLEAN, TRUE, NULL); + still_event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); + + g_mutex_unlock (src->branch_lock); + + g_mutex_unlock (src->dvd_lock); + gst_pad_push_event (GST_BASE_SRC_PAD (src), still_event); + g_mutex_lock (src->dvd_lock); + } + + return TRUE; +} + +static GstFlowReturn +rsn_dvdsrc_step (resinDvdSrc * src, gboolean have_dvd_lock, GstBuffer ** outbuf) +{ + GstFlowReturn ret = GST_FLOW_OK; + dvdnav_status_t dvdnav_ret; + guint8 *data; + gint event, len; + + /* Allocate an output buffer if there isn't a pending one */ + if (src->alloc_buf == NULL) + src->alloc_buf = gst_buffer_new_and_alloc (DVD_VIDEO_LB_LEN); + + data = GST_BUFFER_DATA (src->alloc_buf); + len = DVD_VIDEO_LB_LEN; + + dvdnav_ret = dvdnav_get_next_block (src->dvdnav, data, &event, &len); + if (dvdnav_ret != DVDNAV_STATUS_OK) + goto read_error; + g_mutex_lock (src->branch_lock); + if (src->branching) + goto branching; + g_mutex_unlock (src->branch_lock); + + switch (event) { + case DVDNAV_BLOCK_OK: + /* Data block that needs outputting */ + *outbuf = src->alloc_buf; + src->alloc_buf = NULL; + break; + case DVDNAV_NAV_PACKET:{ + pci_t *pci = dvdnav_get_current_nav_pci (src->dvdnav); + + GST_LOG_OBJECT (src, "NAV packet start TS %" GST_TIME_FORMAT + " end TS %" GST_TIME_FORMAT " %s", + GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (pci->pci_gi.vobu_s_ptm)), + GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (pci->pci_gi.vobu_e_ptm)), + (MPEGTIME_TO_GSTTIME (pci->pci_gi.vobu_s_ptm) != src->cur_end_ts) ? + "discont" : ""); +#if 0 + g_print ("NAV packet start TS %" GST_TIME_FORMAT + " end TS %" GST_TIME_FORMAT " %s\n", + GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (pci->pci_gi.vobu_s_ptm)), + GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (pci->pci_gi.vobu_e_ptm)), + (MPEGTIME_TO_GSTTIME (pci->pci_gi.vobu_s_ptm) != src->cur_end_ts) ? + "discont" : ""); +#endif + + if (MPEGTIME_TO_GSTTIME (pci->pci_gi.vobu_s_ptm) != src->cur_end_ts) { + g_print ("NAV packet discont: cur_end_ts %" GST_TIME_FORMAT " != " + " vobu_s_ptm: %" GST_TIME_FORMAT "\n", + GST_TIME_ARGS (src->cur_end_ts), + GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (pci->pci_gi.vobu_s_ptm))); + src->need_segment = TRUE; + } + + src->cur_start_ts = MPEGTIME_TO_GSTTIME (pci->pci_gi.vobu_s_ptm); + src->cur_end_ts = MPEGTIME_TO_GSTTIME (pci->pci_gi.vobu_e_ptm); + + /* highlight might change, let's check */ + rsn_dvdsrc_update_highlight (src); + + /* NAV packet is also a data block that needs sending */ + *outbuf = src->alloc_buf; + src->alloc_buf = NULL; + break; + } + case DVDNAV_STOP: + /* End of the disc. EOS */ + g_print ("STOP found. End of disc\n"); + ret = GST_FLOW_UNEXPECTED; + break; + case DVDNAV_STILL_FRAME:{ + dvdnav_still_event_t *info = (dvdnav_still_event_t *) data; + g_print ("STILL frame duration %d\n", info->length); + + if (!have_dvd_lock) { + /* At a still frame but can't block, handle it later */ + return GST_FLOW_WOULD_BLOCK; + } + + if (!rsn_dvdsrc_do_still (src, info->length)) + goto internal_error; + + g_mutex_lock (src->branch_lock); + if (src->branching) + goto branching; + g_mutex_unlock (src->branch_lock); + break; + } + case DVDNAV_WAIT: + /* Drain out the queues so that the info on the screen matches + * the VM state */ + if (have_dvd_lock) { + /* FIXME: Drain out the queues */ + g_print ("****** FIXME: WAIT *****\n"); + } + if (dvdnav_wait_skip (src->dvdnav) != DVDNAV_STATUS_OK) + goto internal_error; + break; + case DVDNAV_CELL_CHANGE:{ + dvdnav_cell_change_event_t *event = (dvdnav_cell_change_event_t *) data; + + src->pgc_duration = MPEGTIME_TO_GSTTIME (event->pgc_length); + src->cur_position = MPEGTIME_TO_GSTTIME (event->cell_start); + + GST_DEBUG_OBJECT (src, + "CELL change dur now %" GST_TIME_FORMAT " position now %" + GST_TIME_FORMAT, GST_TIME_ARGS (src->pgc_duration), + GST_TIME_ARGS (src->cur_position)); + break; + } + case DVDNAV_SPU_CLUT_CHANGE: + rsn_dvdsrc_prepare_clut_change_event (src, (const guint32 *) data); + break; + case DVDNAV_VTS_CHANGE:{ + dvdnav_vts_change_event_t *event = (dvdnav_vts_change_event_t *) data; + + g_print ("VTS change\n"); + if (dvdnav_is_domain_vmgm (src->dvdnav)) + src->vts_n = 0; + else + src->vts_n = event->new_vtsN; + + src->in_menu = !dvdnav_is_domain_vtsm (src->dvdnav); + + if (!dvdnav_is_domain_fp (src->dvdnav)) + rsn_dvdsrc_prepare_streamsinfo_event (src); + + break; + } + case DVDNAV_AUDIO_STREAM_CHANGE:{ + dvdnav_audio_stream_change_event_t *event = + (dvdnav_audio_stream_change_event_t *) data; + g_print ("cur audio stream change\n"); + GST_DEBUG_OBJECT (src, " physical: %d", event->physical); + GST_DEBUG_OBJECT (src, " logical: %d", event->logical); + + rsn_dvdsrc_prepare_audio_stream_event (src, event->physical); + break; + } + case DVDNAV_SPU_STREAM_CHANGE:{ + dvdnav_spu_stream_change_event_t *event = + (dvdnav_spu_stream_change_event_t *) data; + + rsn_dvdsrc_prepare_spu_stream_event (src, event->physical_wide & 0x1f, + (event->physical_wide & 0x80) ? TRUE : FALSE); + + GST_DEBUG_OBJECT (src, " physical_wide: %d", event->physical_wide); + GST_DEBUG_OBJECT (src, " physical_letterbox: %d", + event->physical_letterbox); + GST_DEBUG_OBJECT (src, " physical_pan_scan: %d", + event->physical_pan_scan); + GST_DEBUG_OBJECT (src, " logical: %d", event->logical); + break; + } + case DVDNAV_HIGHLIGHT:{ + rsn_dvdsrc_update_highlight (src); + if (src->highlight_event && have_dvd_lock) { + GstEvent *hl_event = src->highlight_event; + src->highlight_event = NULL; + g_mutex_unlock (src->dvd_lock); + g_print ("Highlight change - button: %d\n", src->active_button); + gst_pad_push_event (GST_BASE_SRC_PAD (src), hl_event); + g_mutex_lock (src->dvd_lock); + } + break; + } + case DVDNAV_HOP_CHANNEL: + g_print ("Channel hop - User action\n"); + src->need_segment = TRUE; + break; + case DVDNAV_NOP: + break; + default: + GST_WARNING_OBJECT (src, "Unknown dvdnav event %d", event); + break; + } + + return ret; +read_error: + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), + ("Failed to read next DVD block. Error: %s", + dvdnav_err_to_string (src->dvdnav))); + return GST_FLOW_ERROR; +internal_error: + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), + ("Internal error processing DVD commands. Error: %s", + dvdnav_err_to_string (src->dvdnav))); + return GST_FLOW_ERROR; +branching: + g_mutex_unlock (src->branch_lock); + return GST_FLOW_WRONG_STATE; +} + +static GstFlowReturn +rsn_dvdsrc_prepare_next_block (resinDvdSrc * src, gboolean have_dvd_lock) +{ + GstFlowReturn ret; + + /* If buffer already ready, return */ + if (src->next_buf) + return GST_FLOW_OK; + + do { + ret = rsn_dvdsrc_step (src, have_dvd_lock, &src->next_buf); + } + while (ret == GST_FLOW_OK && src->next_buf == NULL); + + if (ret == GST_FLOW_WOULD_BLOCK) + ret = GST_FLOW_OK; + + return ret; +} + +static GstFlowReturn +rsn_dvdsrc_create (RsnPushSrc * psrc, GstBuffer ** outbuf) +{ + resinDvdSrc *src = RESINDVDSRC (psrc); + GstSegment *segment = &(GST_BASE_SRC (src)->segment); + GstFlowReturn ret; + GstEvent *streams_event = NULL; + GstEvent *clut_event = NULL; + GstEvent *spu_select_event = NULL; + GstEvent *audio_select_event = NULL; + GstEvent *highlight_event = NULL; + + *outbuf = NULL; + + g_mutex_lock (src->dvd_lock); + ret = rsn_dvdsrc_prepare_next_block (src, TRUE); + if (ret != GST_FLOW_OK) { + g_mutex_unlock (src->dvd_lock); + return ret; + } + + if (src->next_buf != NULL) { + *outbuf = src->next_buf; + src->next_buf = NULL; + + if (src->discont) { + g_print ("Discont packet\n"); + GST_BUFFER_FLAG_SET (*outbuf, GST_BUFFER_FLAG_DISCONT); + src->discont = FALSE; + } + } + + streams_event = src->streams_event; + src->streams_event = NULL; + + spu_select_event = src->spu_select_event; + src->spu_select_event = NULL; + + audio_select_event = src->audio_select_event; + src->audio_select_event = NULL; + + clut_event = src->clut_event; + src->clut_event = NULL; + + highlight_event = src->highlight_event; + src->highlight_event = NULL; + + g_mutex_unlock (src->dvd_lock); + + /* Push in-band events now that we've dropped the dvd_lock */ + if (streams_event) { + g_print ("Pushing stream event\n"); + gst_pad_push_event (GST_BASE_SRC_PAD (src), streams_event); + } + if (spu_select_event) { + g_print ("Pushing spu_select event\n"); + gst_pad_push_event (GST_BASE_SRC_PAD (src), spu_select_event); + } + if (audio_select_event) { + g_print ("Pushing audio_select event\n"); + gst_pad_push_event (GST_BASE_SRC_PAD (src), audio_select_event); + } + if (clut_event) { + g_print ("Pushing clut event\n"); + gst_pad_push_event (GST_BASE_SRC_PAD (src), clut_event); + } + + if (highlight_event) { + g_print ("Pushing highlight event with TS %" GST_TIME_FORMAT "\n", + GST_TIME_ARGS (GST_EVENT_TIMESTAMP (highlight_event))); + gst_pad_push_event (GST_BASE_SRC_PAD (src), highlight_event); + } + + g_mutex_lock (src->dvd_lock); + + if (src->need_segment) { + /* Seamless segment update */ + GstEvent *seek; + + seek = gst_event_new_seek (segment->rate, rsndvd_format, + GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_NONE, -1, GST_SEEK_TYPE_NONE, -1); + gst_element_send_event (GST_ELEMENT (src), seek); + src->need_segment = FALSE; + } + if (src->cur_end_ts != GST_CLOCK_TIME_NONE) + segment->last_stop = src->cur_end_ts; + g_mutex_unlock (src->dvd_lock); + + return ret; +} + +static RsnNavResult +rsn_dvdsrc_perform_button_action (resinDvdSrc * src, RsnNavAction action) +{ + pci_t *pci = dvdnav_get_current_nav_pci (src->dvdnav); + RsnNavResult result = RSN_NAV_RESULT_NONE; + int button = 0; + btni_t *btn_info; + + if (pci == NULL) + return RSN_NAV_RESULT_NONE; + + if (pci->hli.hl_gi.hli_ss == 0) + return RSN_NAV_RESULT_NONE; /* No buttons at the moment */ + + dvdnav_get_current_highlight (src->dvdnav, &button); + + if (button > pci->hli.hl_gi.btn_ns || button < 1) + return RSN_NAV_RESULT_NONE; /* No valid button */ + + btn_info = pci->hli.btnit + button - 1; + + switch (action) { + case RSN_NAV_ACTION_ACTIVATE: + if (dvdnav_button_activate (src->dvdnav, pci) == DVDNAV_STATUS_OK) + result = RSN_NAV_RESULT_BRANCH; + break; + case RSN_NAV_ACTION_LEFT: + if (dvdnav_left_button_select (src->dvdnav, pci) == DVDNAV_STATUS_OK) { + if (btn_info->left && + pci->hli.btnit[btn_info->left - 1].auto_action_mode) + result = RSN_NAV_RESULT_BRANCH; + else + result = RSN_NAV_RESULT_HIGHLIGHT; + } + break; + case RSN_NAV_ACTION_RIGHT: + if (dvdnav_right_button_select (src->dvdnav, pci) == DVDNAV_STATUS_OK) { + if (btn_info->right && + pci->hli.btnit[btn_info->right - 1].auto_action_mode) + result = RSN_NAV_RESULT_BRANCH; + else + result = RSN_NAV_RESULT_HIGHLIGHT; + } + break; + case RSN_NAV_ACTION_DOWN: + if (dvdnav_lower_button_select (src->dvdnav, pci) == DVDNAV_STATUS_OK) { + if (btn_info->down && + pci->hli.btnit[btn_info->down - 1].auto_action_mode) + result = RSN_NAV_RESULT_BRANCH; + else + result = RSN_NAV_RESULT_HIGHLIGHT; + } + break; + case RSN_NAV_ACTION_UP: + if (dvdnav_upper_button_select (src->dvdnav, pci) == DVDNAV_STATUS_OK) { + if (btn_info->up && pci->hli.btnit[btn_info->up - 1].auto_action_mode) + result = RSN_NAV_RESULT_BRANCH; + else + result = RSN_NAV_RESULT_HIGHLIGHT; + } + break; + } + + if (result == RSN_NAV_RESULT_HIGHLIGHT) + g_cond_signal (src->still_cond); + + return result; +} + +static gboolean +rsn_dvdsrc_handle_navigation_event (resinDvdSrc * src, GstEvent * event) +{ + const GstStructure *s = gst_event_get_structure (event); + const gchar *event_type; + gboolean channel_hop = FALSE; + gboolean have_lock = FALSE; + GstEvent *hl_event = NULL; + RsnNavResult nav_res = RSN_NAV_RESULT_NONE; + + if (s == NULL) + return FALSE; + event_type = gst_structure_get_string (s, "event"); + if (event_type == NULL) + return FALSE; + + if (strcmp (event_type, "key-press") == 0) { + const gchar *key = gst_structure_get_string (s, "key"); + if (key == NULL) + return FALSE; + + GST_DEBUG ("dvdnavsrc got a keypress: %s", key); + + g_mutex_lock (src->dvd_lock); + have_lock = TRUE; + if (!src->running) + goto not_running; + + if (g_str_equal (key, "Return")) { + nav_res = rsn_dvdsrc_perform_button_action (src, RSN_NAV_ACTION_ACTIVATE); + } else if (g_str_equal (key, "Left")) { + nav_res = rsn_dvdsrc_perform_button_action (src, RSN_NAV_ACTION_LEFT); + } else if (g_str_equal (key, "Right")) { + nav_res = rsn_dvdsrc_perform_button_action (src, RSN_NAV_ACTION_RIGHT); + } else if (g_str_equal (key, "Up")) { + nav_res = rsn_dvdsrc_perform_button_action (src, RSN_NAV_ACTION_UP); + } else if (g_str_equal (key, "Down")) { + nav_res = rsn_dvdsrc_perform_button_action (src, RSN_NAV_ACTION_DOWN); + } else if (g_str_equal (key, "m")) { + if (dvdnav_menu_call (src->dvdnav, DVD_MENU_Escape) == DVDNAV_STATUS_OK) + channel_hop = TRUE; + } else if (g_str_equal (key, "t")) { + if (dvdnav_menu_call (src->dvdnav, DVD_MENU_Title) == DVDNAV_STATUS_OK) + channel_hop = TRUE; + } else if (g_str_equal (key, "r")) { + if (dvdnav_menu_call (src->dvdnav, DVD_MENU_Root) == DVDNAV_STATUS_OK) + channel_hop = TRUE; + } else if (g_str_equal (key, "comma")) { + gint title = 0; + gint part = 0; + + if (dvdnav_current_title_info (src->dvdnav, &title, &part) && title > 0 + && part > 1) { + if (dvdnav_part_play (src->dvdnav, title, part - 1) == + DVDNAV_STATUS_ERR) + dvdnav_prev_pg_search (src->dvdnav); + channel_hop = TRUE; + } + } else if (g_str_equal (key, "period")) { + gint title = 0; + gint part = 0; + + if (dvdnav_current_title_info (src->dvdnav, &title, &part) && title > 0) { + if (dvdnav_part_play (src->dvdnav, title, part + 1) == + DVDNAV_STATUS_ERR) + dvdnav_next_pg_search (src->dvdnav); + channel_hop = TRUE; + } + } else { + g_print ("Unknown keypress: %s\n", key); + } + + } else if (strcmp (event_type, "mouse-move") == 0) { + gdouble x, y; + pci_t *nav; + + if (!gst_structure_get_double (s, "pointer_x", &x) || + !gst_structure_get_double (s, "pointer_y", &y)) + return FALSE; + + g_mutex_lock (src->dvd_lock); + have_lock = TRUE; + if (!src->running) + goto not_running; + + nav = dvdnav_get_current_nav_pci (src->dvdnav); + if (nav && dvdnav_mouse_select (src->dvdnav, nav, (int) x, (int) y) == + DVDNAV_STATUS_OK) { + nav_res = RSN_NAV_RESULT_HIGHLIGHT; + } + } else if (strcmp (event_type, "mouse-button-release") == 0) { + gdouble x, y; + pci_t *nav; + + if (!gst_structure_get_double (s, "pointer_x", &x) || + !gst_structure_get_double (s, "pointer_y", &y)) + return FALSE; + + GST_DEBUG_OBJECT (src, "Got click at %g, %g", x, y); + + g_mutex_lock (src->dvd_lock); + have_lock = TRUE; + if (!src->running) + goto not_running; + + nav = dvdnav_get_current_nav_pci (src->dvdnav); + if (nav && + dvdnav_mouse_activate (src->dvdnav, nav, (int) x, (int) y) == + DVDNAV_STATUS_OK) { + nav_res = RSN_NAV_RESULT_BRANCH; + } + } + + if (have_lock) { + if (nav_res != RSN_NAV_RESULT_NONE) { + if (nav_res == RSN_NAV_RESULT_BRANCH) { + src->active_highlight = TRUE; + channel_hop = TRUE; + } + + rsn_dvdsrc_update_highlight (src); + } + + if (channel_hop) { + GstEvent *seek; + + g_print ("flush and jump\n"); + g_mutex_lock (src->branch_lock); + src->branching = TRUE; + g_cond_signal (src->still_cond); + g_mutex_unlock (src->branch_lock); + + hl_event = src->highlight_event; + src->highlight_event = NULL; + src->active_highlight = FALSE; + + g_mutex_unlock (src->dvd_lock); + + if (hl_event) { + g_print ("Highlight change - button: %d\n", src->active_button); + gst_pad_push_event (GST_BASE_SRC_PAD (src), hl_event); + } + + /* Send ourselves a seek event to wake everything up and flush */ + seek = gst_event_new_seek (1.0, rsndvd_format, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_NONE, -1, GST_SEEK_TYPE_NONE, -1); + gst_element_send_event (GST_ELEMENT (src), seek); + + g_mutex_lock (src->dvd_lock); + + rsn_dvdsrc_update_highlight (src); + } + + hl_event = src->highlight_event; + src->highlight_event = NULL; + + g_mutex_unlock (src->dvd_lock); + + if (hl_event) { + g_print ("Highlight change - button: %d\n", src->active_button); + gst_pad_push_event (GST_BASE_SRC_PAD (src), hl_event); + } + } + + return TRUE; +not_running: + g_mutex_unlock (src->dvd_lock); + GST_DEBUG_OBJECT (src, "Element not started. Ignoring navigation event"); + return FALSE; +} + +static void +rsn_dvdsrc_prepare_audio_stream_event (resinDvdSrc * src, guint8 phys_stream) +{ + GstStructure *s; + GstEvent *e; + + s = gst_structure_new ("application/x-gst-dvd", + "event", G_TYPE_STRING, "dvd-set-audio-track", + "physical-id", G_TYPE_INT, (gint) phys_stream, NULL); + + e = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); + + if (src->audio_select_event) + gst_event_unref (src->audio_select_event); + src->audio_select_event = e; +} + +static void +rsn_dvdsrc_prepare_spu_stream_event (resinDvdSrc * src, guint8 phys_stream, + gboolean forced_only) +{ + GstStructure *s; + GstEvent *e; + + s = gst_structure_new ("application/x-gst-dvd", + "event", G_TYPE_STRING, "dvd-set-subpicture-track", + "physical-id", G_TYPE_INT, (gint) phys_stream, + "forced-only", G_TYPE_BOOLEAN, forced_only, NULL); + + e = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); + + if (src->spu_select_event) + gst_event_unref (src->spu_select_event); + src->spu_select_event = e; +} + +static gboolean +rsn_dvdsrc_prepare_streamsinfo_event (resinDvdSrc * src) +{ + vtsi_mat_t *vts_attr; + audio_attr_t *a_attrs; + subp_attr_t *s_attrs; + gint n_audio, n_subp; + GstStructure *s; + GstEvent *e; + gint i; + gchar lang_code[3] = { '\0', '\0', '\0' }; + gchar *t; + + if (src->vts_attrs == NULL || src->vts_n >= src->vts_attrs->len) { + if (src->vts_attrs) + GST_ERROR_OBJECT (src, "No stream info for VTS %d (have %d)", src->vts_n, + src->vts_attrs->len); + else + GST_ERROR_OBJECT (src, "No stream info"); + return FALSE; + } + + if (src->vts_n == 0) { + /* VMGM info */ + vts_attr = NULL; + a_attrs = &src->vmgm_attr.vmgm_audio_attr; + n_audio = MIN (1, src->vmgm_attr.nr_of_vmgm_audio_streams); + s_attrs = &src->vmgm_attr.vmgm_subp_attr; + n_subp = MIN (1, src->vmgm_attr.nr_of_vmgm_subp_streams); + } else if (src->in_menu) { + /* VTSM attrs */ + vts_attr = &g_array_index (src->vts_attrs, vtsi_mat_t, src->vts_n); + a_attrs = &vts_attr->vtsm_audio_attr; + n_audio = vts_attr->nr_of_vtsm_audio_streams; + s_attrs = &vts_attr->vtsm_subp_attr; + n_subp = vts_attr->nr_of_vtsm_subp_streams; + } else { + /* VTS domain */ + vts_attr = &g_array_index (src->vts_attrs, vtsi_mat_t, src->vts_n); + a_attrs = vts_attr->vts_audio_attr; + n_audio = vts_attr->nr_of_vts_audio_streams; + s_attrs = vts_attr->vts_subp_attr; + n_subp = vts_attr->nr_of_vts_subp_streams; + } + + /* build event */ + s = gst_structure_new ("application/x-gst-dvd", + "event", G_TYPE_STRING, "dvd-lang-codes", NULL); + e = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); + + /* audio */ + if (n_audio == 0) { + /* Always create at least one audio stream */ + gst_structure_set (s, "audio-0-format", G_TYPE_INT, (int) 0, NULL); + } + for (i = 0; i < n_audio; i++) { + const audio_attr_t *a = a_attrs + i; + + t = g_strdup_printf ("audio-%d-format", i); + gst_structure_set (s, t, G_TYPE_INT, (int) a->audio_format, NULL); + g_free (t); + + GST_DEBUG_OBJECT (src, "Audio stream %d is format %d", i, + (int) a->audio_format); + + if (a->lang_type) { + t = g_strdup_printf ("audio-%d-language", i); + lang_code[0] = (a->lang_code >> 8) & 0xff; + lang_code[1] = a->lang_code & 0xff; + gst_structure_set (s, t, G_TYPE_STRING, lang_code, NULL); + g_free (t); + + GST_DEBUG_OBJECT (src, "Audio stream %d is language %s", i, lang_code); + } else + GST_DEBUG_OBJECT (src, "Audio stream %d - no language", i, lang_code); + } + + /* subpictures */ + if (n_subp == 0) { + /* Always create at least one subpicture stream */ + gst_structure_set (s, "subpicture-0-format", G_TYPE_INT, (int) 0, NULL); + gst_structure_set (s, "subpicture-0-language", G_TYPE_STRING, "MENU", NULL); + } + for (i = 0; i < n_subp; i++) { + const subp_attr_t *u = s_attrs + i; + + t = g_strdup_printf ("subpicture-%d-format", i); + gst_structure_set (s, t, G_TYPE_INT, (int) 0, NULL); + g_free (t); + + t = g_strdup_printf ("subpicture-%d-language", i); + if (u->type) { + lang_code[0] = (u->lang_code >> 8) & 0xff; + lang_code[1] = u->lang_code & 0xff; + gst_structure_set (s, t, G_TYPE_STRING, lang_code, NULL); + } else { + gst_structure_set (s, t, G_TYPE_STRING, "MENU", NULL); + } + g_free (t); + + GST_DEBUG_OBJECT (src, "Subpicture stream %d is language %s", i, + lang_code[0] ? lang_code : "NONE"); + } + + if (src->streams_event) + gst_event_unref (src->streams_event); + src->streams_event = e; + + return TRUE; +} + +static void +rsn_dvdsrc_prepare_clut_change_event (resinDvdSrc * src, const guint32 * clut) +{ + GstEvent *event; + GstStructure *structure; + gchar name[16]; + int i; + + structure = gst_structure_new ("application/x-gst-dvd", + "event", G_TYPE_STRING, "dvd-spu-clut-change", NULL); + + /* Create a separate field for each value in the table. */ + for (i = 0; i < 16; i++) { + sprintf (name, "clut%02d", i); + gst_structure_set (structure, name, G_TYPE_INT, (int) clut[i], NULL); + } + + /* Create the DVD event and put the structure into it. */ + event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, structure); + + GST_LOG_OBJECT (src, "pushing clut change event %" GST_PTR_FORMAT, event); + + if (src->clut_event) + gst_event_unref (src->clut_event); + src->clut_event = event; +} + +/* + * Check for a new highlighted area, and prepare an spu highlight event if + * necessary. + */ +static void +rsn_dvdsrc_update_highlight (resinDvdSrc * src) +{ + int button = 0; + pci_t *pci; + dvdnav_highlight_area_t area; + int mode = 0; + GstEvent *event = NULL; + GstStructure *s; + + if (dvdnav_get_current_highlight (src->dvdnav, &button) != DVDNAV_STATUS_OK) { + GST_ELEMENT_ERROR (src, LIBRARY, FAILED, (NULL), + ("dvdnav_get_current_highlight: %s", + dvdnav_err_to_string (src->dvdnav))); + return; + } + + pci = dvdnav_get_current_nav_pci (src->dvdnav); + if ((button > pci->hli.hl_gi.btn_ns) || (button < 1)) { + /* button is out of the range of possible buttons. */ + button = 0; + } + + if (pci->hli.hl_gi.hli_ss == 0 || button == 0) { + /* No highlight available, or no button selected - clear the SPU */ + if (src->active_button != 0) { + src->active_button = 0; + + s = gst_structure_new ("application/x-gst-dvd", "event", + G_TYPE_STRING, "dvd-spu-reset-highlight", NULL); + event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_OOB, s); + if (src->highlight_event) + gst_event_unref (src->highlight_event); + src->highlight_event = event; + } + return; + } + + if (src->active_highlight) + mode = 1; + + if (dvdnav_get_highlight_area (pci, button, mode, &area) != DVDNAV_STATUS_OK) { + GST_ELEMENT_ERROR (src, LIBRARY, FAILED, (NULL), + ("dvdnav_get_highlight_area: %s", dvdnav_err_to_string (src->dvdnav))); + return; + } + + /* Check if we have a new button number, or a new highlight region. */ + if (button != src->active_button || + memcmp (&area, &(src->area), sizeof (dvdnav_highlight_area_t)) != 0) { + memcpy (&(src->area), &area, sizeof (dvdnav_highlight_area_t)); + + s = gst_structure_new ("application/x-gst-dvd", "event", + G_TYPE_STRING, "dvd-spu-highlight", + "button", G_TYPE_INT, (gint) button, + "palette", G_TYPE_INT, (gint) area.palette, + "sx", G_TYPE_INT, (gint) area.sx, + "sy", G_TYPE_INT, (gint) area.sy, + "ex", G_TYPE_INT, (gint) area.ex, + "ey", G_TYPE_INT, (gint) area.ey, NULL); + + event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_OOB, s); + + if (src->active_button == 0) { + /* When setting the button for the first time, take the + timestamp into account. */ + GST_EVENT_TIMESTAMP (event) = MPEGTIME_TO_GSTTIME (area.pts); + } + + src->active_button = button; + + g_print ("Setting highlight. Button %d active %d TS %" GST_TIME_FORMAT + " palette 0x%x\n", button, mode, + GST_TIME_ARGS (GST_EVENT_TIMESTAMP (event)), area.palette); + + if (src->highlight_event) + gst_event_unref (src->highlight_event); + src->highlight_event = event; + } +} + +/* Use libdvdread to read and cache info from the IFO file about + * streams in each VTS */ +static gboolean +rsn_dvdsrc_src_event (RsnBaseSrc * basesrc, GstEvent * event) +{ + resinDvdSrc *src = RESINDVDSRC (basesrc); + gboolean res; + + GST_LOG_OBJECT (src, "handling %s event", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NAVIGATION: + res = rsn_dvdsrc_handle_navigation_event (src, event); + break; + default: + res = GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event); + break; + } + + return res; +} + +static gboolean +rsn_dvdsrc_prepare_seek (RsnBaseSrc * bsrc, GstEvent * event, + GstSegment * segment) +{ + GstSeekType cur_type, stop_type; + gint64 cur, stop; + GstSeekFlags flags; + GstFormat seek_format; + gdouble rate; + gboolean update; + + gst_event_parse_seek (event, &rate, &seek_format, &flags, + &cur_type, &cur, &stop_type, &stop); + + if (seek_format == rsndvd_format) { + /* Seeks in our internal format are passed directly through to the do_seek + * method. */ + gst_segment_init (segment, seek_format); + gst_segment_set_seek (segment, rate, seek_format, flags, cur_type, cur, + stop_type, stop, &update); + g_print ("Have internal seek event\n"); + + return TRUE; + } + /* Don't allow bytes seeks - angle, time, chapter, title only is the plan */ + if (seek_format == GST_FORMAT_BYTES) + return FALSE; + + /* Let basesrc handle other formats for now. FIXME: Implement angle, + * chapter etc */ + return GST_BASE_SRC_CLASS (parent_class)->prepare_seek_segment (bsrc, + event, segment); +} + +static gboolean +rsn_dvdsrc_do_seek (RsnBaseSrc * bsrc, GstSegment * segment) +{ + resinDvdSrc *src = RESINDVDSRC (bsrc); + gboolean ret = FALSE; + + if (segment->format == rsndvd_format) { + g_print ("Handling internal seek event\n"); + ret = TRUE; + } else { + /* FIXME: Handle other formats: Time, title, chapter, angle */ + /* HACK to make initial seek work: */ + if (segment->format == GST_FORMAT_TIME) { + ret = TRUE; + src->discont = TRUE; + } + } + + if (ret) { + /* The internal format has served its purpose of waking everything + * up and flushing, now step to the next data block so we know our + * position */ + /* Force a highlight update */ + src->active_button = -1; + + g_print ("Entering prepare_next_block after seek\n"); + if (rsn_dvdsrc_prepare_next_block (src, FALSE) != GST_FLOW_OK) + goto fail; + g_print ("prepare_next_block after seek done\n"); + + segment->format = GST_FORMAT_TIME; + /* The first TS output: */ + segment->last_stop = segment->start = src->cur_start_ts; + + /* time field = position is the 'logical' stream time here: */ + segment->time = src->cur_position; + + segment->stop = -1; + segment->duration = -1; + + g_print ("seek completed. New start TS %" GST_TIME_FORMAT + " pos %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (segment->start), + GST_TIME_ARGS (segment->time)); + + src->need_segment = FALSE; + } + + return ret; +fail: + g_print ("Seek in format %d failed\n", segment->format); + return FALSE; +} + +gboolean +rsndvdsrc_init (GstPlugin * plugin) +{ + gboolean res; + + res = gst_element_register (plugin, "rsndvdsrc", + GST_RANK_NONE, RESIN_TYPE_DVDSRC); + + return res; +} diff --git a/ext/resindvd/resindvdsrc.h b/ext/resindvd/resindvdsrc.h new file mode 100644 index 00000000..8fe68100 --- /dev/null +++ b/ext/resindvd/resindvdsrc.h @@ -0,0 +1,113 @@ +/* GStreamer + * Copyright (C) 2008 Jan Schmidt <thaytan@noraisin.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef __RESINDVDSRC_H__ +#define __RESINDVDSRC_H__ + +#include <gst/gst.h> + +#include "rsnpushsrc.h" + +#include "_stdint.h" + +#include <dvdnav/dvd_reader.h> +#include <dvdnav/ifo_read.h> + +#include <dvdnav/dvdnav.h> +#include <dvdnav/nav_print.h> + +G_BEGIN_DECLS + +#define RESIN_TYPE_DVDSRC (rsn_dvdsrc_get_type()) +#define RESINDVDSRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),RESIN_TYPE_DVDSRC,resinDvdSrc)) +#define RESINDVDSRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),RESIN_TYPE_DVDSRC,resinDvdSrcClass)) +#define IS_RESINDVDSRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),RESIN_TYPE_DVDSRC)) +#define IS_RESINDVDSRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),RESIN_TYPE_DVDSRC)) + +typedef struct _resinDvdSrc resinDvdSrc; +typedef struct _resinDvdSrcClass resinDvdSrcClass; + +struct _resinDvdSrc +{ + RsnPushSrc parent; + + GMutex *dvd_lock; + GCond *still_cond; + GMutex *branch_lock; + gboolean branching; + + gchar *device; + dvdnav_t *dvdnav; + + /* dvd_reader instance is used to load and cache VTS/VMG ifo info */ + dvd_reader_t *dvdread; + + /* vmgi_mat_t from the VMG ifo: */ + vmgi_mat_t vmgm_attr; /* VMGM domain info */ + /* Array of cached vtsi_mat_t strctures from each IFO: */ + GArray *vts_attrs; + + ifo_handle_t *vmg_file; + ifo_handle_t *vts_file; + + /* Current playback location: VTS 0 = VMG, plus in_menu or not */ + gint vts_n; + gboolean in_menu; + + gboolean running; + gboolean discont; + gboolean need_segment; + gboolean active_highlight; + + GstBuffer *alloc_buf; + GstBuffer *next_buf; + + /* Start timestamp of the previous NAV block */ + GstClockTime cur_start_ts; + /* End timestamp of the previous NAV block */ + GstClockTime cur_end_ts; + /* Position info of the previous NAV block */ + GstClockTime cur_position; + /* Duration of the current PGC */ + GstClockTime pgc_duration; + + gint active_button; + dvdnav_highlight_area_t area; + + /* Pending events to output */ + GstEvent *streams_event; + GstEvent *clut_event; + GstEvent *spu_select_event; + GstEvent *audio_select_event; + GstEvent *highlight_event; +}; + +struct _resinDvdSrcClass +{ + RsnPushSrcClass parent_class; +}; + +GType rsn_dvdsrc_get_type (void); + +G_END_DECLS + +#endif /* __RESINDVDSRC_H__ */ diff --git a/ext/resindvd/rsnaudiomunge.c b/ext/resindvd/rsnaudiomunge.c new file mode 100644 index 00000000..f0429ee9 --- /dev/null +++ b/ext/resindvd/rsnaudiomunge.c @@ -0,0 +1,378 @@ +/* GStreamer + * Copyright (C) 2008 Jan Schmidt <thaytan@noraisin.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <string.h> + +#include <gst/gst.h> + +#include "rsnaudiomunge.h" + +GST_DEBUG_CATEGORY_STATIC (rsn_audiomunge_debug); +#define GST_CAT_DEFAULT rsn_audiomunge_debug + +#define AUDIO_FILL_THRESHOLD (GST_SECOND/5) + +/* Filter signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_SILENT +}; + +/* the capabilities of the inputs and outputs. + * + * describe the real formats here. + */ +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("ANY") + ); + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("ANY") + ); + +GST_BOILERPLATE (RsnAudioMunge, rsn_audiomunge, GstElement, GST_TYPE_ELEMENT); + +static void rsn_audiomunge_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void rsn_audiomunge_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static gboolean rsn_audiomunge_set_caps (GstPad * pad, GstCaps * caps); +static GstFlowReturn rsn_audiomunge_chain (GstPad * pad, GstBuffer * buf); +static gboolean rsn_audiomunge_sink_event (GstPad * pad, GstEvent * event); + +static GstStateChangeReturn +rsn_audiomunge_change_state (GstElement * element, GstStateChange transition); + +static void +rsn_audiomunge_base_init (gpointer gclass) +{ + static GstElementDetails element_details = { + "RsnAudioMunge", + "Audio/Filter", + "Resin DVD audio stream regulator", + "Jan Schmidt <thaytan@noraisin.net>" + }; + GstElementClass *element_class = GST_ELEMENT_CLASS (gclass); + + GST_DEBUG_CATEGORY_INIT (rsn_audiomunge_debug, "rsn_audiomunge", + 0, "Resin audio stream regulator"); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); + + gst_element_class_set_details (element_class, &element_details); +} + +static void +rsn_audiomunge_class_init (RsnAudioMungeClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->set_property = rsn_audiomunge_set_property; + gobject_class->get_property = rsn_audiomunge_get_property; + + gstelement_class->change_state = rsn_audiomunge_change_state; +} + +static void +rsn_audiomunge_init (RsnAudioMunge * munge, RsnAudioMungeClass * gclass) +{ + munge->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink"); + gst_pad_set_setcaps_function (munge->sinkpad, + GST_DEBUG_FUNCPTR (rsn_audiomunge_set_caps)); + gst_pad_set_getcaps_function (munge->sinkpad, + GST_DEBUG_FUNCPTR (gst_pad_proxy_getcaps)); + gst_pad_set_chain_function (munge->sinkpad, + GST_DEBUG_FUNCPTR (rsn_audiomunge_chain)); + gst_pad_set_event_function (munge->sinkpad, + GST_DEBUG_FUNCPTR (rsn_audiomunge_sink_event)); + gst_element_add_pad (GST_ELEMENT (munge), munge->sinkpad); + + munge->srcpad = gst_pad_new_from_static_template (&src_template, "src"); + gst_pad_set_getcaps_function (munge->srcpad, + GST_DEBUG_FUNCPTR (gst_pad_proxy_getcaps)); + gst_element_add_pad (GST_ELEMENT (munge), munge->srcpad); +} + +static void +rsn_audiomunge_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + //RsnAudioMunge *munge = RSN_AUDIOMUNGE (object); + + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +rsn_audiomunge_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + //RsnAudioMunge *munge = RSN_AUDIOMUNGE (object); + + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +rsn_audiomunge_set_caps (GstPad * pad, GstCaps * caps) +{ + RsnAudioMunge *munge = RSN_AUDIOMUNGE (gst_pad_get_parent (pad)); + GstPad *otherpad; + gboolean ret; + + g_return_val_if_fail (munge != NULL, FALSE); + + otherpad = (pad == munge->srcpad) ? munge->sinkpad : munge->srcpad; + gst_object_unref (munge); + + ret = gst_pad_set_caps (otherpad, caps); + return ret; +} + +static void +rsn_audiomunge_reset (RsnAudioMunge * munge) +{ + munge->have_audio = FALSE; + munge->in_still = FALSE; + gst_segment_init (&munge->sink_segment, GST_FORMAT_TIME); +} + +static GstFlowReturn +rsn_audiomunge_chain (GstPad * pad, GstBuffer * buf) +{ + RsnAudioMunge *munge = RSN_AUDIOMUNGE (GST_OBJECT_PARENT (pad)); + + if (!munge->have_audio) { + g_print ("First audio after flush has TS %" GST_TIME_FORMAT "\n", + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); + } + + munge->have_audio = TRUE; + + /* just push out the incoming buffer without touching it */ + return gst_pad_push (munge->srcpad, buf); +} + +/* Create and send a silence buffer downstream */ +static GstFlowReturn +rsn_audiomunge_make_audio (RsnAudioMunge * munge, + GstClockTime start, GstClockTime fill_time) +{ + GstFlowReturn ret; + GstBuffer *audio_buf; + GstCaps *caps; + guint buf_size; + + /* Just generate a 48khz stereo buffer for now */ +#if 0 + caps = + gst_caps_from_string + ("audio/x-raw-int,rate=48000,channels=2,width=16,depth=16,signed=(boolean)true,endianness=1234"); + buf_size = 4 * (48000 * fill_time / GST_SECOND); +#else + caps = gst_caps_from_string ("audio/x-raw-float, endianness=(int)1234," + "width=(int)32, channels=(int)2, rate=(int)48000"); + buf_size = 2 * 4 * (48000 * fill_time / GST_SECOND); +#endif + + audio_buf = gst_buffer_new_and_alloc (buf_size); + + gst_buffer_set_caps (audio_buf, caps); + gst_caps_unref (caps); + + GST_BUFFER_TIMESTAMP (audio_buf) = start; + GST_BUFFER_DURATION (audio_buf) = fill_time; + GST_BUFFER_FLAG_SET (audio_buf, GST_BUFFER_FLAG_DISCONT); + + memset (GST_BUFFER_DATA (audio_buf), 0, buf_size); + + g_print ("Sending %u bytes (%" GST_TIME_FORMAT ") of audio data " + "with TS %" GST_TIME_FORMAT "\n", + buf_size, GST_TIME_ARGS (fill_time), GST_TIME_ARGS (start)); + + ret = gst_pad_push (munge->srcpad, audio_buf); + + return ret; +} + +static void +rsn_audiomunge_handle_dvd_event (RsnAudioMunge * munge, GstEvent * event) +{ + const GstStructure *s; + const gchar *event_type; + + s = gst_event_get_structure (event); + event_type = gst_structure_get_string (s, "event"); + if (event_type == NULL) + return; + + if (strcmp (event_type, "dvd-still") == 0) { + gboolean in_still; + + if (!gst_structure_get_boolean (s, "still-state", &in_still)) + return; + + /* Remember the still-frame state, so we can generate a pre-roll buffer + * when a new-segment arrives */ + munge->in_still = in_still; + + g_print ("**** AUDIO MUNGE: still-state now %d\n", munge->in_still); + } +} + +static gboolean +rsn_audiomunge_sink_event (GstPad * pad, GstEvent * event) +{ + gboolean ret = FALSE; + RsnAudioMunge *munge = RSN_AUDIOMUNGE (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_STOP: + rsn_audiomunge_reset (munge); + + g_print ("*********** AUDIO MUNGE: FLUSH\n"); + ret = gst_pad_push_event (munge->srcpad, event); + break; + case GST_EVENT_NEWSEGMENT: + { + GstSegment *segment; + gboolean update; + GstFormat format; + gdouble rate, arate; + gint64 start, stop, time; + + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + + /* we need TIME format */ + if (format != GST_FORMAT_TIME) + goto newseg_wrong_format; + + /* now configure the values */ + segment = &munge->sink_segment; + + gst_segment_set_newsegment_full (segment, update, + rate, arate, format, start, stop, time); + + if (munge->have_audio) { + g_print ("*********** AUDIO MUNGE NEWSEG: start %" GST_TIME_FORMAT + " stop %" GST_TIME_FORMAT " accum now %" GST_TIME_FORMAT + "\n", GST_TIME_ARGS (start), GST_TIME_ARGS (stop), + GST_TIME_ARGS (segment->accum)); + + ret = gst_pad_push_event (munge->srcpad, event); + break; + } + + /* + * FIXME: + * If the accum >= threshold or we're in a still frame and there's been + * no audio received, then we need to generate some audio data. + * If caused by a segment start update (time advancing in a gap) adjust + * the new-segment and send the buffer. + * + * Otherwise, send the buffer before the newsegment, so that it appears + * in the closing segment. + */ + if (segment->accum >= AUDIO_FILL_THRESHOLD || munge->in_still) { + g_print ("*********** Send audio mebbe: accum = %" GST_TIME_FORMAT + " still-state=%d\n", GST_TIME_ARGS (segment->accum), + munge->in_still); + /* Just generate a 100ms silence buffer for now. FIXME: Fill the gap */ + if (rsn_audiomunge_make_audio (munge, segment->start, + GST_SECOND / 10) == GST_FLOW_OK) + munge->have_audio = TRUE; + } else { + g_print ("*********** below thresh: accum = %" GST_TIME_FORMAT + "\n", GST_TIME_ARGS (segment->accum)); + } + + g_print ("*********** AUDIO MUNGE NEWSEG: start %" GST_TIME_FORMAT + " stop %" GST_TIME_FORMAT " accum now %" GST_TIME_FORMAT + "\n", GST_TIME_ARGS (start), GST_TIME_ARGS (stop), + GST_TIME_ARGS (segment->accum)); + + ret = gst_pad_push_event (munge->srcpad, event); + break; + } + case GST_EVENT_CUSTOM_DOWNSTREAM: + { + const GstStructure *s = gst_event_get_structure (event); + if (s && gst_structure_has_name (s, "application/x-gst-dvd")) + rsn_audiomunge_handle_dvd_event (munge, event); + + ret = gst_pad_push_event (munge->srcpad, event); + break; + } + default: + ret = gst_pad_push_event (munge->srcpad, event); + break; + } + + return ret; + +newseg_wrong_format: + + GST_DEBUG_OBJECT (munge, "received non TIME newsegment"); + gst_event_unref (event); + gst_object_unref (munge); + return FALSE; +} + +static GstStateChangeReturn +rsn_audiomunge_change_state (GstElement * element, GstStateChange transition) +{ + RsnAudioMunge *munge = RSN_AUDIOMUNGE (element); + GstStateChangeReturn ret; + + if (transition == GST_STATE_CHANGE_READY_TO_PAUSED) + rsn_audiomunge_reset (munge); + + ret = parent_class->change_state (element, transition); + + return ret; +} diff --git a/ext/resindvd/rsnaudiomunge.h b/ext/resindvd/rsnaudiomunge.h new file mode 100644 index 00000000..f5ada285 --- /dev/null +++ b/ext/resindvd/rsnaudiomunge.h @@ -0,0 +1,61 @@ +/* GStreamer + * Copyright (C) 2008 Jan Schmidt <thaytan@noraisin.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __RSNAUDIOMUNGE_H__ +#define __RSNAUDIOMUNGE_H__ + +#include <gst/gst.h> + +G_BEGIN_DECLS + +/* #defines don't like whitespacey bits */ +#define RSN_TYPE_AUDIOMUNGE (rsn_audiomunge_get_type()) +#define RSN_AUDIOMUNGE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),RSN_TYPE_AUDIOMUNGE,RsnAudioMunge)) +#define RSN_AUDIOMUNGE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),RSN_TYPE_AUDIOMUNGE,RsnAudioMungeClass)) +#define RSN_IS_AUDIOMUNGE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),RSN_TYPE_AUDIOMUNGE)) +#define RSN_IS_AUDIOMUNGE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),RSN_TYPE_AUDIOMUNGE)) + +typedef struct _RsnAudioMunge RsnAudioMunge; +typedef struct _RsnAudioMungeClass RsnAudioMungeClass; + +struct _RsnAudioMunge +{ + GstElement element; + + GstPad *sinkpad, *srcpad; + + GstSegment sink_segment; + gboolean have_audio; + gboolean in_still; +}; + +struct _RsnAudioMungeClass +{ + GstElementClass parent_class; +}; + +GType rsn_audiomunge_get_type (void); + +G_END_DECLS + +#endif /* __RSNAUDIOMUNGE_H__ */ diff --git a/ext/resindvd/rsnbasesrc.c b/ext/resindvd/rsnbasesrc.c new file mode 100644 index 00000000..c4d7f24c --- /dev/null +++ b/ext/resindvd/rsnbasesrc.c @@ -0,0 +1,2423 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000,2005 Wim Taymans <wim@fluendo.com> + * + * gstbasesrc.c: + * + * 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. + */ + +/* + * + * This is a temporary copy of GstBaseSrc/GstPushSrc for the resin + * DVD components, to work around a deadlock with source elements that + * send seeks to themselves. + * + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <stdlib.h> +#include <string.h> + +#include "rsnbasesrc.h" +#include <gst/base/gsttypefindhelper.h> +#include <gst/gstmarshal.h> +#include <gst/gst-i18n-lib.h> + +GST_DEBUG_CATEGORY_STATIC (rsn_base_src_debug); +#define GST_CAT_DEFAULT rsn_base_src_debug + +#define GST_LIVE_GET_LOCK(elem) (GST_BASE_SRC_CAST(elem)->live_lock) +#define GST_LIVE_LOCK(elem) g_mutex_lock(GST_LIVE_GET_LOCK(elem)) +#define GST_LIVE_TRYLOCK(elem) g_mutex_trylock(GST_LIVE_GET_LOCK(elem)) +#define GST_LIVE_UNLOCK(elem) g_mutex_unlock(GST_LIVE_GET_LOCK(elem)) +#define GST_LIVE_GET_COND(elem) (GST_BASE_SRC_CAST(elem)->live_cond) +#define GST_LIVE_WAIT(elem) g_cond_wait (GST_LIVE_GET_COND (elem), GST_LIVE_GET_LOCK (elem)) +#define GST_LIVE_TIMED_WAIT(elem, timeval) g_cond_timed_wait (GST_LIVE_GET_COND (elem), GST_LIVE_GET_LOCK (elem),\ + timeval) +#define GST_LIVE_SIGNAL(elem) g_cond_signal (GST_LIVE_GET_COND (elem)); +#define GST_LIVE_BROADCAST(elem) g_cond_broadcast (GST_LIVE_GET_COND (elem)); + +/* BaseSrc signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +#define DEFAULT_BLOCKSIZE 4096 +#define DEFAULT_NUM_BUFFERS -1 +#define DEFAULT_TYPEFIND FALSE +#define DEFAULT_DO_TIMESTAMP FALSE + +enum +{ + PROP_0, + PROP_BLOCKSIZE, + PROP_NUM_BUFFERS, + PROP_TYPEFIND, + PROP_DO_TIMESTAMP +}; + +#define GST_BASE_SRC_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RSN_TYPE_BASE_SRC, RsnBaseSrcPrivate)) + +struct _RsnBaseSrcPrivate +{ + gboolean last_sent_eos; /* last thing we did was send an EOS (we set this + * to avoid the sending of two EOS in some cases) */ + gboolean discont; + + /* two segments to be sent in the streaming thread with STREAM_LOCK */ + GstEvent *close_segment; + GstEvent *start_segment; + + /* startup latency is the time it takes between going to PLAYING and producing + * the first BUFFER with running_time 0. This value is included in the latency + * reporting. */ + GstClockTime latency; + /* timestamp offset, this is the offset add to the values of gst_times for + * pseudo live sources */ + GstClockTimeDiff ts_offset; + + gboolean do_timestamp; +}; + +static GstElementClass *parent_class = NULL; + +static void rsn_base_src_base_init (gpointer g_class); +static void rsn_base_src_class_init (RsnBaseSrcClass * klass); +static void rsn_base_src_init (RsnBaseSrc * src, gpointer g_class); +static void rsn_base_src_finalize (GObject * object); + + +GType +rsn_base_src_get_type (void) +{ + static GType base_src_type = 0; + + if (G_UNLIKELY (base_src_type == 0)) { + static const GTypeInfo base_src_info = { + sizeof (RsnBaseSrcClass), + (GBaseInitFunc) rsn_base_src_base_init, + NULL, + (GClassInitFunc) rsn_base_src_class_init, + NULL, + NULL, + sizeof (RsnBaseSrc), + 0, + (GInstanceInitFunc) rsn_base_src_init, + }; + + base_src_type = g_type_register_static (GST_TYPE_ELEMENT, + "RsnBaseSrc", &base_src_info, G_TYPE_FLAG_ABSTRACT); + } + return base_src_type; +} +static GstCaps *rsn_base_src_getcaps (GstPad * pad); +static gboolean rsn_base_src_setcaps (GstPad * pad, GstCaps * caps); +static void rsn_base_src_fixate (GstPad * pad, GstCaps * caps); + +static gboolean rsn_base_src_activate_push (GstPad * pad, gboolean active); +static gboolean rsn_base_src_activate_pull (GstPad * pad, gboolean active); +static void rsn_base_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void rsn_base_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static gboolean rsn_base_src_event_handler (GstPad * pad, GstEvent * event); +static gboolean rsn_base_src_send_event (GstElement * elem, GstEvent * event); +static gboolean rsn_base_src_default_event (RsnBaseSrc * src, GstEvent * event); +static const GstQueryType *rsn_base_src_get_query_types (GstElement * element); + +static gboolean rsn_base_src_query (GstPad * pad, GstQuery * query); + +static gboolean rsn_base_src_default_negotiate (RsnBaseSrc * basesrc); +static gboolean rsn_base_src_default_do_seek (RsnBaseSrc * src, + GstSegment * segment); +static gboolean rsn_base_src_default_query (RsnBaseSrc * src, GstQuery * query); +static gboolean rsn_base_src_default_prepare_seek_segment (RsnBaseSrc * src, + GstEvent * event, GstSegment * segment); + +static gboolean rsn_base_src_unlock (RsnBaseSrc * basesrc); +static gboolean rsn_base_src_unlock_stop (RsnBaseSrc * basesrc); +static gboolean rsn_base_src_start (RsnBaseSrc * basesrc); +static gboolean rsn_base_src_stop (RsnBaseSrc * basesrc); + +static GstStateChangeReturn rsn_base_src_change_state (GstElement * element, + GstStateChange transition); + +static void rsn_base_src_loop (GstPad * pad); +static gboolean rsn_base_src_pad_check_get_range (GstPad * pad); +static gboolean rsn_base_src_default_check_get_range (RsnBaseSrc * bsrc); +static GstFlowReturn rsn_base_src_pad_get_range (GstPad * pad, guint64 offset, + guint length, GstBuffer ** buf); +static GstFlowReturn rsn_base_src_get_range (RsnBaseSrc * src, guint64 offset, + guint length, GstBuffer ** buf); + +static void +rsn_base_src_base_init (gpointer g_class) +{ + GST_DEBUG_CATEGORY_INIT (rsn_base_src_debug, "basesrc", 0, "basesrc element"); +} + +static void +rsn_base_src_class_init (RsnBaseSrcClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstelement_class = GST_ELEMENT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (RsnBaseSrcPrivate)); + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->finalize = GST_DEBUG_FUNCPTR (rsn_base_src_finalize); + gobject_class->set_property = GST_DEBUG_FUNCPTR (rsn_base_src_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR (rsn_base_src_get_property); + + g_object_class_install_property (gobject_class, PROP_BLOCKSIZE, + g_param_spec_ulong ("blocksize", "Block size", + "Size in bytes to read per buffer (0 = default)", 0, G_MAXULONG, + DEFAULT_BLOCKSIZE, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_NUM_BUFFERS, + g_param_spec_int ("num-buffers", "num-buffers", + "Number of buffers to output before sending EOS", -1, G_MAXINT, + DEFAULT_NUM_BUFFERS, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_TYPEFIND, + g_param_spec_boolean ("typefind", "Typefind", + "Run typefind before negotiating", DEFAULT_TYPEFIND, + G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_DO_TIMESTAMP, + g_param_spec_boolean ("do-timestamp", "Do timestamp", + "Apply current stream time to buffers", DEFAULT_DO_TIMESTAMP, + G_PARAM_READWRITE)); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (rsn_base_src_change_state); + gstelement_class->send_event = GST_DEBUG_FUNCPTR (rsn_base_src_send_event); + gstelement_class->get_query_types = + GST_DEBUG_FUNCPTR (rsn_base_src_get_query_types); + + klass->negotiate = GST_DEBUG_FUNCPTR (rsn_base_src_default_negotiate); + klass->event = GST_DEBUG_FUNCPTR (rsn_base_src_default_event); + klass->do_seek = GST_DEBUG_FUNCPTR (rsn_base_src_default_do_seek); + klass->query = GST_DEBUG_FUNCPTR (rsn_base_src_default_query); + klass->check_get_range = + GST_DEBUG_FUNCPTR (rsn_base_src_default_check_get_range); + klass->prepare_seek_segment = + GST_DEBUG_FUNCPTR (rsn_base_src_default_prepare_seek_segment); +} + +static void +rsn_base_src_init (RsnBaseSrc * basesrc, gpointer g_class) +{ + GstPad *pad; + GstPadTemplate *pad_template; + + basesrc->priv = GST_BASE_SRC_GET_PRIVATE (basesrc); + + basesrc->is_live = FALSE; + basesrc->live_lock = g_mutex_new (); + basesrc->live_cond = g_cond_new (); + basesrc->num_buffers = DEFAULT_NUM_BUFFERS; + basesrc->num_buffers_left = -1; + + basesrc->can_activate_push = TRUE; + basesrc->pad_mode = GST_ACTIVATE_NONE; + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "src"); + g_return_if_fail (pad_template != NULL); + + GST_DEBUG_OBJECT (basesrc, "creating src pad"); + pad = gst_pad_new_from_template (pad_template, "src"); + + GST_DEBUG_OBJECT (basesrc, "setting functions on src pad"); + gst_pad_set_activatepush_function (pad, + GST_DEBUG_FUNCPTR (rsn_base_src_activate_push)); + gst_pad_set_activatepull_function (pad, + GST_DEBUG_FUNCPTR (rsn_base_src_activate_pull)); + gst_pad_set_event_function (pad, + GST_DEBUG_FUNCPTR (rsn_base_src_event_handler)); + gst_pad_set_query_function (pad, GST_DEBUG_FUNCPTR (rsn_base_src_query)); + gst_pad_set_checkgetrange_function (pad, + GST_DEBUG_FUNCPTR (rsn_base_src_pad_check_get_range)); + gst_pad_set_getrange_function (pad, + GST_DEBUG_FUNCPTR (rsn_base_src_pad_get_range)); + gst_pad_set_getcaps_function (pad, GST_DEBUG_FUNCPTR (rsn_base_src_getcaps)); + gst_pad_set_setcaps_function (pad, GST_DEBUG_FUNCPTR (rsn_base_src_setcaps)); + gst_pad_set_fixatecaps_function (pad, + GST_DEBUG_FUNCPTR (rsn_base_src_fixate)); + + /* hold pointer to pad */ + basesrc->srcpad = pad; + GST_DEBUG_OBJECT (basesrc, "adding src pad"); + gst_element_add_pad (GST_ELEMENT (basesrc), pad); + + basesrc->blocksize = DEFAULT_BLOCKSIZE; + basesrc->clock_id = NULL; + /* we operate in BYTES by default */ + rsn_base_src_set_format (basesrc, GST_FORMAT_BYTES); + basesrc->data.ABI.typefind = DEFAULT_TYPEFIND; + basesrc->priv->do_timestamp = DEFAULT_DO_TIMESTAMP; + + GST_OBJECT_FLAG_UNSET (basesrc, GST_BASE_SRC_STARTED); + + GST_DEBUG_OBJECT (basesrc, "init done"); +} + +static void +rsn_base_src_finalize (GObject * object) +{ + RsnBaseSrc *basesrc; + GstEvent **event_p; + + basesrc = GST_BASE_SRC (object); + + g_mutex_free (basesrc->live_lock); + g_cond_free (basesrc->live_cond); + + event_p = &basesrc->data.ABI.pending_seek; + gst_event_replace ((GstEvent **) event_p, NULL); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/** + * rsn_base_src_wait_playing: + * @src: the src + * + * If the #RsnBaseSrcClass::create method performs its own synchronisation against + * the clock it must unblock when going from PLAYING to the PAUSED state and call + * this method before continuing to produce the remaining data. + * + * This function will block until a state change to PLAYING happens (in which + * case this function returns #GST_FLOW_OK) or the processing must be stopped due + * to a state change to READY or a FLUSH event (in which case this function + * returns #GST_FLOW_WRONG_STATE). + * + * Since: 0.10.12 + * + * Returns: #GST_FLOW_OK if @src is PLAYING and processing can + * continue. Any other return value should be returned from the create vmethod. + */ +GstFlowReturn +rsn_base_src_wait_playing (RsnBaseSrc * src) +{ + /* block until the state changes, or we get a flush, or something */ + GST_LIVE_LOCK (src); + if (src->is_live) { + while (G_UNLIKELY (!src->live_running)) { + GST_DEBUG ("live source signal waiting"); + GST_LIVE_SIGNAL (src); + GST_DEBUG ("live source waiting for running state"); + GST_LIVE_WAIT (src); + GST_DEBUG ("live source unlocked"); + } + /* FIXME, use another variable to signal stopping so that we don't + * have to grab another lock. */ + GST_OBJECT_LOCK (src->srcpad); + if (G_UNLIKELY (GST_PAD_IS_FLUSHING (src->srcpad))) + goto flushing; + GST_OBJECT_UNLOCK (src->srcpad); + } + GST_LIVE_UNLOCK (src); + + return GST_FLOW_OK; + + /* ERRORS */ +flushing: + { + GST_DEBUG_OBJECT (src, "pad is flushing"); + GST_OBJECT_UNLOCK (src->srcpad); + GST_LIVE_UNLOCK (src); + return GST_FLOW_WRONG_STATE; + } +} + +/** + * rsn_base_src_set_live: + * @src: base source instance + * @live: new live-mode + * + * If the element listens to a live source, @live should + * be set to %TRUE. + * + * A live source will not produce data in the PAUSED state and + * will therefore not be able to participate in the PREROLL phase + * of a pipeline. To signal this fact to the application and the + * pipeline, the state change return value of the live source will + * be GST_STATE_CHANGE_NO_PREROLL. + */ +void +rsn_base_src_set_live (RsnBaseSrc * src, gboolean live) +{ + GST_LIVE_LOCK (src); + src->is_live = live; + GST_LIVE_UNLOCK (src); +} + +/** + * rsn_base_src_is_live: + * @src: base source instance + * + * Check if an element is in live mode. + * + * Returns: %TRUE if element is in live mode. + */ +gboolean +rsn_base_src_is_live (RsnBaseSrc * src) +{ + gboolean result; + + GST_LIVE_LOCK (src); + result = src->is_live; + GST_LIVE_UNLOCK (src); + + return result; +} + +/** + * rsn_base_src_set_format: + * @src: base source instance + * @format: the format to use + * + * Sets the default format of the source. This will be the format used + * for sending NEW_SEGMENT events and for performing seeks. + * + * If a format of GST_FORMAT_BYTES is set, the element will be able to + * operate in pull mode if the #RsnBaseSrc::is_seekable returns TRUE. + * + * @Since: 0.10.1 + */ +void +rsn_base_src_set_format (RsnBaseSrc * src, GstFormat format) +{ + gst_segment_init (&src->segment, format); +} + +/** + * rsn_base_src_query_latency: + * @src: the source + * @live: if the source is live + * @min_latency: the min latency of the source + * @max_latency: the max latency of the source + * + * Query the source for the latency parameters. @live will be TRUE when @src is + * configured as a live source. @min_latency will be set to the difference + * between the running time and the timestamp of the first buffer. + * @max_latency is always the undefined value of -1. + * + * This function is mostly used by subclasses. + * + * Returns: TRUE if the query succeeded. + * + * Since: 0.10.13 + */ +gboolean +rsn_base_src_query_latency (RsnBaseSrc * src, gboolean * live, + GstClockTime * min_latency, GstClockTime * max_latency) +{ + GstClockTime min; + + GST_LIVE_LOCK (src); + if (live) + *live = src->is_live; + + /* if we have a startup latency, report this one, else report 0. Subclasses + * are supposed to override the query function if they want something + * else. */ + if (src->priv->latency != -1) + min = src->priv->latency; + else + min = 0; + + if (min_latency) + *min_latency = min; + if (max_latency) + *max_latency = -1; + + GST_LOG_OBJECT (src, "latency: live %d, min %" GST_TIME_FORMAT + ", max %" GST_TIME_FORMAT, src->is_live, GST_TIME_ARGS (min), + GST_TIME_ARGS (-1)); + GST_LIVE_UNLOCK (src); + + return TRUE; +} + +/** + * rsn_base_src_set_do_timestamp: + * @src: the source + * @timestamp: enable or disable timestamping + * + * Configure @src to automatically timestamp outgoing buffers based on the + * current running_time of the pipeline. This property is mostly useful for live + * sources. + * + * Since: 0.10.15 + */ +void +rsn_base_src_set_do_timestamp (RsnBaseSrc * src, gboolean timestamp) +{ + GST_OBJECT_LOCK (src); + src->priv->do_timestamp = timestamp; + GST_OBJECT_UNLOCK (src); +} + +/** + * rsn_base_src_get_do_timestamp: + * @src: the source + * + * Query if @src timestamps outgoing buffers based on the current running_time. + * + * Returns: %TRUE if the base class will automatically timestamp outgoing buffers. + * + * Since: 0.10.15 + */ +gboolean +rsn_base_src_get_do_timestamp (RsnBaseSrc * src) +{ + gboolean res; + + GST_OBJECT_LOCK (src); + res = src->priv->do_timestamp; + GST_OBJECT_UNLOCK (src); + + return res; +} + +static gboolean +rsn_base_src_setcaps (GstPad * pad, GstCaps * caps) +{ + RsnBaseSrcClass *bclass; + RsnBaseSrc *bsrc; + gboolean res = TRUE; + + bsrc = GST_BASE_SRC (GST_PAD_PARENT (pad)); + bclass = GST_BASE_SRC_GET_CLASS (bsrc); + + if (bclass->set_caps) + res = bclass->set_caps (bsrc, caps); + + return res; +} + +static GstCaps * +rsn_base_src_getcaps (GstPad * pad) +{ + RsnBaseSrcClass *bclass; + RsnBaseSrc *bsrc; + GstCaps *caps = NULL; + + bsrc = GST_BASE_SRC (GST_PAD_PARENT (pad)); + bclass = GST_BASE_SRC_GET_CLASS (bsrc); + if (bclass->get_caps) + caps = bclass->get_caps (bsrc); + + if (caps == NULL) { + GstPadTemplate *pad_template; + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (bclass), "src"); + if (pad_template != NULL) { + caps = gst_caps_ref (gst_pad_template_get_caps (pad_template)); + } + } + return caps; +} + +static void +rsn_base_src_fixate (GstPad * pad, GstCaps * caps) +{ + RsnBaseSrcClass *bclass; + RsnBaseSrc *bsrc; + + bsrc = GST_BASE_SRC (gst_pad_get_parent (pad)); + bclass = GST_BASE_SRC_GET_CLASS (bsrc); + + if (bclass->fixate) + bclass->fixate (bsrc, caps); + + gst_object_unref (bsrc); +} + +static gboolean +rsn_base_src_default_query (RsnBaseSrc * src, GstQuery * query) +{ + gboolean res; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + { + GstFormat format; + + gst_query_parse_position (query, &format, NULL); + switch (format) { + case GST_FORMAT_PERCENT: + { + gint64 percent; + gint64 position; + gint64 duration; + + position = src->segment.last_stop; + duration = src->segment.duration; + + if (position != -1 && duration != -1) { + if (position < duration) + percent = gst_util_uint64_scale (GST_FORMAT_PERCENT_MAX, position, + duration); + else + percent = GST_FORMAT_PERCENT_MAX; + } else + percent = -1; + + gst_query_set_position (query, GST_FORMAT_PERCENT, percent); + res = TRUE; + break; + } + default: + { + gint64 position; + + position = src->segment.last_stop; + + if (position != -1) { + /* convert to requested format */ + res = + gst_pad_query_convert (src->srcpad, src->segment.format, + position, &format, &position); + } else + res = TRUE; + + gst_query_set_position (query, format, position); + break; + } + } + break; + } + case GST_QUERY_DURATION: + { + GstFormat format; + + gst_query_parse_duration (query, &format, NULL); + + GST_DEBUG_OBJECT (src, "duration query in format %s", + gst_format_get_name (format)); + switch (format) { + case GST_FORMAT_PERCENT: + gst_query_set_duration (query, GST_FORMAT_PERCENT, + GST_FORMAT_PERCENT_MAX); + res = TRUE; + break; + default: + { + gint64 duration; + + duration = src->segment.duration; + + if (duration != -1) { + /* convert to requested format */ + res = + gst_pad_query_convert (src->srcpad, src->segment.format, + duration, &format, &duration); + } else { + res = TRUE; + } + gst_query_set_duration (query, format, duration); + break; + } + } + break; + } + + case GST_QUERY_SEEKING: + { + gst_query_set_seeking (query, src->segment.format, + src->seekable, 0, src->segment.duration); + res = TRUE; + break; + } + case GST_QUERY_SEGMENT: + { + gint64 start, stop; + + /* no end segment configured, current duration then */ + if ((stop = src->segment.stop) == -1) + stop = src->segment.duration; + start = src->segment.start; + + /* adjust to stream time */ + if (src->segment.time != -1) { + start -= src->segment.time; + if (stop != -1) + stop -= src->segment.time; + } + gst_query_set_segment (query, src->segment.rate, src->segment.format, + start, stop); + res = TRUE; + break; + } + + case GST_QUERY_FORMATS: + { + gst_query_set_formats (query, 3, GST_FORMAT_DEFAULT, + GST_FORMAT_BYTES, GST_FORMAT_PERCENT); + res = TRUE; + break; + } + case GST_QUERY_CONVERT: + { + GstFormat src_fmt, dest_fmt; + gint64 src_val, dest_val; + + gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val); + + /* we can only convert between equal formats... */ + if (src_fmt == dest_fmt) { + dest_val = src_val; + res = TRUE; + } else + res = FALSE; + + gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val); + break; + } + case GST_QUERY_LATENCY: + { + GstClockTime min, max; + gboolean live; + + /* Subclasses should override and implement something usefull */ + res = rsn_base_src_query_latency (src, &live, &min, &max); + + GST_LOG_OBJECT (src, "report latency: live %d, min %" GST_TIME_FORMAT + ", max %" GST_TIME_FORMAT, live, GST_TIME_ARGS (min), + GST_TIME_ARGS (max)); + + gst_query_set_latency (query, live, min, max); + break; + } + case GST_QUERY_JITTER: + case GST_QUERY_RATE: + default: + res = FALSE; + break; + } + GST_DEBUG_OBJECT (src, "query %s returns %d", GST_QUERY_TYPE_NAME (query), + res); + return res; +} + +static gboolean +rsn_base_src_query (GstPad * pad, GstQuery * query) +{ + RsnBaseSrc *src; + RsnBaseSrcClass *bclass; + gboolean result = FALSE; + + src = GST_BASE_SRC (gst_pad_get_parent (pad)); + + bclass = GST_BASE_SRC_GET_CLASS (src); + + if (bclass->query) + result = bclass->query (src, query); + else + result = gst_pad_query_default (pad, query); + + gst_object_unref (src); + + return result; +} + +static gboolean +rsn_base_src_default_do_seek (RsnBaseSrc * src, GstSegment * segment) +{ + gboolean res = TRUE; + + /* update our offset if the start/stop position was updated */ + if (segment->format == GST_FORMAT_BYTES) { + segment->last_stop = segment->start; + segment->time = segment->start; + } else if (segment->start == 0) { + /* seek to start, we can implement a default for this. */ + segment->last_stop = 0; + segment->time = 0; + res = TRUE; + } else + res = FALSE; + + return res; +} + +static gboolean +rsn_base_src_do_seek (RsnBaseSrc * src, GstSegment * segment) +{ + RsnBaseSrcClass *bclass; + gboolean result = FALSE; + + bclass = GST_BASE_SRC_GET_CLASS (src); + + if (bclass->do_seek) + result = bclass->do_seek (src, segment); + + return result; +} + +#define SEEK_TYPE_IS_RELATIVE(t) (((t) != GST_SEEK_TYPE_NONE) && ((t) != GST_SEEK_TYPE_SET)) + +static gboolean +rsn_base_src_default_prepare_seek_segment (RsnBaseSrc * src, GstEvent * event, + GstSegment * segment) +{ + /* By default, we try one of 2 things: + * - For absolute seek positions, convert the requested position to our + * configured processing format and place it in the output segment \ + * - For relative seek positions, convert our current (input) values to the + * seek format, adjust by the relative seek offset and then convert back to + * the processing format + */ + GstSeekType cur_type, stop_type; + gint64 cur, stop; + GstSeekFlags flags; + GstFormat seek_format, dest_format; + gdouble rate; + gboolean update; + gboolean res = TRUE; + + gst_event_parse_seek (event, &rate, &seek_format, &flags, + &cur_type, &cur, &stop_type, &stop); + dest_format = segment->format; + + if (seek_format == dest_format) { + gst_segment_set_seek (segment, rate, seek_format, flags, + cur_type, cur, stop_type, stop, &update); + return TRUE; + } + + if (cur_type != GST_SEEK_TYPE_NONE) { + /* FIXME: Handle seek_cur & seek_end by converting the input segment vals */ + res = + gst_pad_query_convert (src->srcpad, seek_format, cur, &dest_format, + &cur); + cur_type = GST_SEEK_TYPE_SET; + } + + if (res && stop_type != GST_SEEK_TYPE_NONE) { + /* FIXME: Handle seek_cur & seek_end by converting the input segment vals */ + res = + gst_pad_query_convert (src->srcpad, seek_format, stop, &dest_format, + &stop); + stop_type = GST_SEEK_TYPE_SET; + } + + /* And finally, configure our output segment in the desired format */ + gst_segment_set_seek (segment, rate, dest_format, flags, cur_type, cur, + stop_type, stop, &update); + + if (!res) + goto no_format; + + return res; + +no_format: + { + GST_DEBUG_OBJECT (src, "undefined format given, seek aborted."); + return FALSE; + } +} + +static gboolean +rsn_base_src_prepare_seek_segment (RsnBaseSrc * src, GstEvent * event, + GstSegment * seeksegment) +{ + RsnBaseSrcClass *bclass; + gboolean result = FALSE; + + bclass = GST_BASE_SRC_GET_CLASS (src); + + if (bclass->prepare_seek_segment) + result = bclass->prepare_seek_segment (src, event, seeksegment); + + return result; +} + +/* this code implements the seeking. It is a good example + * handling all cases. + * + * A seek updates the currently configured segment.start + * and segment.stop values based on the SEEK_TYPE. If the + * segment.start value is updated, a seek to this new position + * should be performed. + * + * The seek can only be executed when we are not currently + * streaming any data, to make sure that this is the case, we + * acquire the STREAM_LOCK which is taken when we are in the + * _loop() function or when a getrange() is called. Normally + * we will not receive a seek if we are operating in pull mode + * though. + * + * When we are in the loop() function, we might be in the middle + * of pushing a buffer, which might block in a sink. To make sure + * that the push gets unblocked we push out a FLUSH_START event. + * Our loop function will get a WRONG_STATE return value from + * the push and will pause, effectively releasing the STREAM_LOCK. + * + * For a non-flushing seek, we pause the task, which might eventually + * release the STREAM_LOCK. We say eventually because when the sink + * blocks on the sample we might wait a very long time until the sink + * unblocks the sample. In any case we acquire the STREAM_LOCK and + * can continue the seek. A non-flushing seek is normally done in a + * running pipeline to perform seamless playback. + * In the case of a non-flushing seek we need to make sure that the + * data we output after the seek is continuous with the previous data, + * this is because a non-flushing seek does not reset the stream-time + * to 0. We do this by closing the currently running segment, ie. sending + * a new_segment event with the stop position set to the last processed + * position. + * + * After updating the segment.start/stop values, we prepare for + * streaming again. We push out a FLUSH_STOP to make the peer pad + * accept data again and we start our task again. + * + * A segment seek posts a message on the bus saying that the playback + * of the segment started. We store the segment flag internally because + * when we reach the segment.stop we have to post a segment.done + * instead of EOS when doing a segment seek. + */ +/* FIXME (0.11), we have the unlock gboolean here because most current + * implementations (fdsrc, -base/gst/tcp/, ...) unconditionally unlock, even when + * the streaming thread isn't running, resulting in bogus unlocks later when it + * starts. This is fixed by adding unlock_stop, but we should still avoid unlocking + * unnecessarily for backwards compatibility. Ergo, the unlock variable stays + * until 0.11 + */ +static gboolean +rsn_base_src_perform_seek (RsnBaseSrc * src, GstEvent * event, gboolean unlock) +{ + gboolean res = TRUE; + gdouble rate; + GstFormat seek_format, dest_format; + GstSeekFlags flags; + GstSeekType cur_type, stop_type; + gint64 cur, stop; + gboolean flush; + gboolean update; + gboolean relative_seek = FALSE; + gboolean seekseg_configured = FALSE; + GstSegment seeksegment; + + GST_DEBUG_OBJECT (src, "doing seek"); + + dest_format = src->segment.format; + + if (event) { + gst_event_parse_seek (event, &rate, &seek_format, &flags, + &cur_type, &cur, &stop_type, &stop); + + relative_seek = SEEK_TYPE_IS_RELATIVE (cur_type) || + SEEK_TYPE_IS_RELATIVE (stop_type); + + if (dest_format != seek_format && !relative_seek) { + /* If we have an ABSOLUTE position (SEEK_SET only), we can convert it + * here before taking the stream lock, otherwise we must convert it later, + * once we have the stream lock and can read the current position */ + gst_segment_init (&seeksegment, dest_format); + + if (!rsn_base_src_prepare_seek_segment (src, event, &seeksegment)) + goto prepare_failed; + + seekseg_configured = TRUE; + } + + flush = flags & GST_SEEK_FLAG_FLUSH; + } else { + flush = FALSE; + } + + /* send flush start */ + if (flush) + gst_pad_push_event (src->srcpad, gst_event_new_flush_start ()); + else + gst_pad_pause_task (src->srcpad); + + /* unblock streaming thread */ + if (unlock) + rsn_base_src_unlock (src); + + /* grab streaming lock, this should eventually be possible, either + * because the task is paused or our streaming thread stopped + * because our peer is flushing. */ + GST_PAD_STREAM_LOCK (src->srcpad); + + if (unlock) + rsn_base_src_unlock_stop (src); + + /* If we configured the seeksegment above, don't overwrite it now. Otherwise + * copy the current segment info into the temp segment that we can actually + * attempt the seek with. We only update the real segment if the seek suceeds. */ + if (!seekseg_configured) { + memcpy (&seeksegment, &src->segment, sizeof (GstSegment)); + + /* now configure the final seek segment */ + if (event) { + if (src->segment.format != seek_format) { + /* OK, here's where we give the subclass a chance to convert the relative + * seek into an absolute one in the processing format. We set up any + * absolute seek above, before taking the stream lock. */ + if (!rsn_base_src_prepare_seek_segment (src, event, &seeksegment)) { + GST_DEBUG_OBJECT (src, "Preparing the seek failed after flushing. " + "Aborting seek"); + res = FALSE; + } + } else { + /* The seek format matches our processing format, no need to ask the + * the subclass to configure the segment. */ + gst_segment_set_seek (&seeksegment, rate, seek_format, flags, + cur_type, cur, stop_type, stop, &update); + } + } + /* Else, no seek event passed, so we're just (re)starting the + current segment. */ + } + + if (res) { + GST_DEBUG_OBJECT (src, "segment configured from %" G_GINT64_FORMAT + " to %" G_GINT64_FORMAT ", position %" G_GINT64_FORMAT, + seeksegment.start, seeksegment.stop, seeksegment.last_stop); + + /* do the seek, segment.last_stop contains the new position. */ + res = rsn_base_src_do_seek (src, &seeksegment); + } + + /* and prepare to continue streaming */ + if (flush) { + /* send flush stop, peer will accept data and events again. We + * are not yet providing data as we still have the STREAM_LOCK. */ + gst_pad_push_event (src->srcpad, gst_event_new_flush_stop ()); + } else if (res && src->data.ABI.running) { + /* we are running the current segment and doing a non-flushing seek, + * close the segment first based on the last_stop. */ + GST_DEBUG_OBJECT (src, "closing running segment %" G_GINT64_FORMAT + " to %" G_GINT64_FORMAT, src->segment.start, src->segment.last_stop); + + /* queue the segment for sending in the stream thread */ + if (src->priv->close_segment) + gst_event_unref (src->priv->close_segment); + src->priv->close_segment = + gst_event_new_new_segment_full (TRUE, + src->segment.rate, src->segment.applied_rate, src->segment.format, + src->segment.start, src->segment.last_stop, src->segment.time); + } + + /* The subclass must have converted the segment to the processing format + * by now */ + if (res && seeksegment.format != dest_format) { + GST_DEBUG_OBJECT (src, "Subclass failed to prepare a seek segment " + "in the correct format. Aborting seek."); + res = FALSE; + } + + /* if successfull seek, we update our real segment and push + * out the new segment. */ + if (res) { + memcpy (&src->segment, &seeksegment, sizeof (GstSegment)); + + if (src->segment.flags & GST_SEEK_FLAG_SEGMENT) { + gst_element_post_message (GST_ELEMENT (src), + gst_message_new_segment_start (GST_OBJECT (src), + src->segment.format, src->segment.last_stop)); + } + + /* for deriving a stop position for the playback segment form the seek + * segment, we must take the duration when the stop is not set */ + if ((stop = src->segment.stop) == -1) + stop = src->segment.duration; + + GST_DEBUG_OBJECT (src, "Sending newsegment from %" G_GINT64_FORMAT + " to %" G_GINT64_FORMAT, src->segment.start, stop); + + /* now replace the old segment so that we send it in the stream thread the + * next time it is scheduled. */ + if (src->priv->start_segment) + gst_event_unref (src->priv->start_segment); + src->priv->start_segment = + gst_event_new_new_segment_full (FALSE, + src->segment.rate, src->segment.applied_rate, src->segment.format, + src->segment.last_stop, stop, src->segment.time); + } + + src->priv->discont = TRUE; + src->data.ABI.running = TRUE; + /* and restart the task in case it got paused explicitely or by + * the FLUSH_START event we pushed out. */ + gst_pad_start_task (src->srcpad, (GstTaskFunction) rsn_base_src_loop, + src->srcpad); + + /* and release the lock again so we can continue streaming */ + GST_PAD_STREAM_UNLOCK (src->srcpad); + + return res; + + /* ERROR */ +prepare_failed: + GST_DEBUG_OBJECT (src, "Preparing the seek failed before flushing. " + "Aborting seek"); + return FALSE; +} + +static const GstQueryType * +rsn_base_src_get_query_types (GstElement * element) +{ + static const GstQueryType query_types[] = { + GST_QUERY_DURATION, + GST_QUERY_POSITION, + GST_QUERY_SEEKING, + GST_QUERY_SEGMENT, + GST_QUERY_FORMATS, + GST_QUERY_LATENCY, + GST_QUERY_JITTER, + GST_QUERY_RATE, + GST_QUERY_CONVERT, + 0 + }; + + return query_types; +} + +/* all events send to this element directly. This is mainly done from the + * application. + */ +static gboolean +rsn_base_src_send_event (GstElement * element, GstEvent * event) +{ + RsnBaseSrc *src; + gboolean result = FALSE; + + src = GST_BASE_SRC (element); + + switch (GST_EVENT_TYPE (event)) { + /* bidirectional events */ + case GST_EVENT_FLUSH_START: + case GST_EVENT_FLUSH_STOP: + /* sending random flushes downstream can break stuff, + * especially sync since all segment info will get flushed */ + break; + + /* downstream serialized events */ + case GST_EVENT_EOS: + /* FIXME, queue EOS and make sure the task or pull function + * perform the EOS actions. */ + break; + case GST_EVENT_NEWSEGMENT: + /* sending random NEWSEGMENT downstream can break sync. */ + break; + case GST_EVENT_TAG: + /* sending tags could be useful, FIXME insert in dataflow */ + break; + case GST_EVENT_BUFFERSIZE: + /* does not seem to make much sense currently */ + break; + + /* upstream events */ + case GST_EVENT_QOS: + /* elements should override send_event and do something */ + break; + case GST_EVENT_SEEK: + { + gboolean started; + + GST_OBJECT_LOCK (src->srcpad); + if (GST_PAD_ACTIVATE_MODE (src->srcpad) == GST_ACTIVATE_PULL) + goto wrong_mode; + started = GST_PAD_ACTIVATE_MODE (src->srcpad) == GST_ACTIVATE_PUSH; + GST_OBJECT_UNLOCK (src->srcpad); + + if (started) { + /* when we are running in push mode, we can execute the + * seek right now, we need to unlock. */ + result = rsn_base_src_perform_seek (src, event, TRUE); + } else { + GstEvent **event_p; + + /* else we store the event and execute the seek when we + * get activated */ + GST_OBJECT_LOCK (src); + event_p = &src->data.ABI.pending_seek; + gst_event_replace ((GstEvent **) event_p, event); + GST_OBJECT_UNLOCK (src); + /* assume the seek will work */ + result = TRUE; + } + break; + } + case GST_EVENT_NAVIGATION: + /* could make sense for elements that do something with navigation events + * but then they would need to override the send_event function */ + break; + case GST_EVENT_LATENCY: + /* does not seem to make sense currently */ + break; + + /* custom events */ + case GST_EVENT_CUSTOM_UPSTREAM: + /* override send_event if you want this */ + break; + case GST_EVENT_CUSTOM_DOWNSTREAM: + case GST_EVENT_CUSTOM_BOTH: + /* FIXME, insert event in the dataflow */ + break; + case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: + case GST_EVENT_CUSTOM_BOTH_OOB: + /* insert a random custom event into the pipeline */ + GST_DEBUG_OBJECT (src, "pushing custom OOB event downstream"); + result = gst_pad_push_event (src->srcpad, event); + /* we gave away the ref to the event in the push */ + event = NULL; + break; + default: + break; + } +done: + /* if we still have a ref to the event, unref it now */ + if (event) + gst_event_unref (event); + + return result; + + /* ERRORS */ +wrong_mode: + { + GST_DEBUG_OBJECT (src, "cannot perform seek when operating in pull mode"); + GST_OBJECT_UNLOCK (src->srcpad); + result = FALSE; + goto done; + } +} + +static gboolean +rsn_base_src_default_event (RsnBaseSrc * src, GstEvent * event) +{ + gboolean result; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + /* is normally called when in push mode */ + if (!src->seekable) + goto not_seekable; + + result = rsn_base_src_perform_seek (src, event, TRUE); + break; + case GST_EVENT_FLUSH_START: + /* cancel any blocking getrange, is normally called + * when in pull mode. */ + result = rsn_base_src_unlock (src); + break; + case GST_EVENT_FLUSH_STOP: + result = rsn_base_src_unlock_stop (src); + break; + default: + result = TRUE; + break; + } + return result; + + /* ERRORS */ +not_seekable: + { + GST_DEBUG_OBJECT (src, "is not seekable"); + return FALSE; + } +} + +static gboolean +rsn_base_src_event_handler (GstPad * pad, GstEvent * event) +{ + RsnBaseSrc *src; + RsnBaseSrcClass *bclass; + gboolean result = FALSE; + + src = GST_BASE_SRC (gst_pad_get_parent (pad)); + bclass = GST_BASE_SRC_GET_CLASS (src); + + if (bclass->event) { + if (!(result = bclass->event (src, event))) + goto subclass_failed; + } + +done: + gst_event_unref (event); + gst_object_unref (src); + + return result; + + /* ERRORS */ +subclass_failed: + { + GST_DEBUG_OBJECT (src, "subclass refused event"); + goto done; + } +} + +static void +rsn_base_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + RsnBaseSrc *src; + + src = GST_BASE_SRC (object); + + switch (prop_id) { + case PROP_BLOCKSIZE: + src->blocksize = g_value_get_ulong (value); + break; + case PROP_NUM_BUFFERS: + src->num_buffers = g_value_get_int (value); + break; + case PROP_TYPEFIND: + src->data.ABI.typefind = g_value_get_boolean (value); + break; + case PROP_DO_TIMESTAMP: + src->priv->do_timestamp = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +rsn_base_src_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + RsnBaseSrc *src; + + src = GST_BASE_SRC (object); + + switch (prop_id) { + case PROP_BLOCKSIZE: + g_value_set_ulong (value, src->blocksize); + break; + case PROP_NUM_BUFFERS: + g_value_set_int (value, src->num_buffers); + break; + case PROP_TYPEFIND: + g_value_set_boolean (value, src->data.ABI.typefind); + break; + case PROP_DO_TIMESTAMP: + g_value_set_boolean (value, src->priv->do_timestamp); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* with STREAM_LOCK and LOCK */ +static GstClockReturn +rsn_base_src_wait (RsnBaseSrc * basesrc, GstClock * clock, GstClockTime time) +{ + GstClockReturn ret; + GstClockID id; + + id = gst_clock_new_single_shot_id (clock, time); + + basesrc->clock_id = id; + /* release the object lock while waiting */ + GST_OBJECT_UNLOCK (basesrc); + + ret = gst_clock_id_wait (id, NULL); + + GST_OBJECT_LOCK (basesrc); + gst_clock_id_unref (id); + basesrc->clock_id = NULL; + + return ret; +} + +/* perform synchronisation on a buffer. + * with STREAM_LOCK. + */ +static GstClockReturn +rsn_base_src_do_sync (RsnBaseSrc * basesrc, GstBuffer * buffer) +{ + GstClockReturn result; + GstClockTime start, end; + RsnBaseSrcClass *bclass; + GstClockTime base_time; + GstClock *clock; + GstClockTime now = GST_CLOCK_TIME_NONE, timestamp; + gboolean do_timestamp, first, pseudo_live; + + bclass = GST_BASE_SRC_GET_CLASS (basesrc); + + start = end = -1; + if (bclass->get_times) + bclass->get_times (basesrc, buffer, &start, &end); + + /* get buffer timestamp */ + timestamp = GST_BUFFER_TIMESTAMP (buffer); + + /* grab the lock to prepare for clocking and calculate the startup + * latency. */ + GST_OBJECT_LOCK (basesrc); + + /* if we are asked to sync against the clock we are a pseudo live element */ + pseudo_live = (start != -1 && basesrc->is_live); + /* check for the first buffer */ + first = (basesrc->priv->latency == -1); + + if (timestamp != -1 && pseudo_live) { + GstClockTime latency; + + /* we have a timestamp and a sync time, latency is the diff */ + if (timestamp <= start) + latency = start - timestamp; + else + latency = 0; + + if (first) { + GST_DEBUG_OBJECT (basesrc, "pseudo_live with latency %" GST_TIME_FORMAT, + GST_TIME_ARGS (latency)); + /* first time we calculate latency, just configure */ + basesrc->priv->latency = latency; + } else { + if (basesrc->priv->latency != latency) { + /* we have a new latency, FIXME post latency message */ + basesrc->priv->latency = latency; + GST_DEBUG_OBJECT (basesrc, "latency changed to %" GST_TIME_FORMAT, + GST_TIME_ARGS (latency)); + } + } + } else if (first) { + GST_DEBUG_OBJECT (basesrc, "no latency needed, live %d, sync %d", + basesrc->is_live, start != -1); + basesrc->priv->latency = 0; + } + + /* get clock, if no clock, we can't sync or do timestamps */ + if ((clock = GST_ELEMENT_CLOCK (basesrc)) == NULL) + goto no_clock; + + base_time = GST_ELEMENT_CAST (basesrc)->base_time; + + do_timestamp = basesrc->priv->do_timestamp; + + /* first buffer, calculate the timestamp offset */ + if (first) { + GstClockTime running_time; + + now = gst_clock_get_time (clock); + running_time = now - base_time; + + GST_LOG_OBJECT (basesrc, + "startup timestamp: %" GST_TIME_FORMAT ", running_time %" + GST_TIME_FORMAT, GST_TIME_ARGS (timestamp), + GST_TIME_ARGS (running_time)); + + if (pseudo_live && timestamp != -1) { + /* live source and we need to sync, add startup latency to all timestamps + * to get the real running_time. Live sources should always timestamp + * according to the current running time. */ + basesrc->priv->ts_offset = GST_CLOCK_DIFF (timestamp, running_time); + + GST_LOG_OBJECT (basesrc, "live with sync, ts_offset %" GST_TIME_FORMAT, + GST_TIME_ARGS (basesrc->priv->ts_offset)); + } else { + basesrc->priv->ts_offset = 0; + GST_LOG_OBJECT (basesrc, "no timestamp offset needed"); + } + + if (!GST_CLOCK_TIME_IS_VALID (timestamp)) { + if (do_timestamp) + timestamp = running_time; + else + timestamp = 0; + + GST_BUFFER_TIMESTAMP (buffer) = timestamp; + + GST_LOG_OBJECT (basesrc, "created timestamp: %" GST_TIME_FORMAT, + GST_TIME_ARGS (timestamp)); + } + + /* add the timestamp offset we need for sync */ + timestamp += basesrc->priv->ts_offset; + } else { + /* not the first buffer, the timestamp is the diff between the clock and + * base_time */ + if (do_timestamp && !GST_CLOCK_TIME_IS_VALID (timestamp)) { + now = gst_clock_get_time (clock); + + GST_BUFFER_TIMESTAMP (buffer) = now - base_time; + + GST_LOG_OBJECT (basesrc, "created timestamp: %" GST_TIME_FORMAT, + GST_TIME_ARGS (now - base_time)); + } + } + + /* if we don't have a buffer timestamp, we don't sync */ + if (!GST_CLOCK_TIME_IS_VALID (start)) + goto no_sync; + + if (basesrc->is_live && GST_CLOCK_TIME_IS_VALID (timestamp)) { + /* for pseudo live sources, add our ts_offset to the timestamp */ + GST_BUFFER_TIMESTAMP (buffer) += basesrc->priv->ts_offset; + start += basesrc->priv->ts_offset; + } + + GST_LOG_OBJECT (basesrc, + "waiting for clock, base time %" GST_TIME_FORMAT + ", stream_start %" GST_TIME_FORMAT, + GST_TIME_ARGS (base_time), GST_TIME_ARGS (start)); + + result = rsn_base_src_wait (basesrc, clock, start + base_time); + GST_OBJECT_UNLOCK (basesrc); + + GST_LOG_OBJECT (basesrc, "clock entry done: %d", result); + + return result; + + /* special cases */ +no_clock: + { + GST_DEBUG_OBJECT (basesrc, "we have no clock"); + GST_OBJECT_UNLOCK (basesrc); + return GST_CLOCK_OK; + } +no_sync: + { + GST_DEBUG_OBJECT (basesrc, "no sync needed"); + GST_OBJECT_UNLOCK (basesrc); + return GST_CLOCK_OK; + } +} + +static gboolean +rsn_base_src_update_length (RsnBaseSrc * src, guint64 offset, guint * length) +{ + guint64 size, maxsize; + RsnBaseSrcClass *bclass; + + bclass = GST_BASE_SRC_GET_CLASS (src); + + /* only operate if we are working with bytes */ + if (src->segment.format != GST_FORMAT_BYTES) + return TRUE; + + /* get total file size */ + size = (guint64) src->segment.duration; + + /* the max amount of bytes to read is the total size or + * up to the segment.stop if present. */ + if (src->segment.stop != -1) + maxsize = MIN (size, src->segment.stop); + else + maxsize = size; + + GST_DEBUG_OBJECT (src, + "reading offset %" G_GUINT64_FORMAT ", length %u, size %" G_GINT64_FORMAT + ", segment.stop %" G_GINT64_FORMAT ", maxsize %" G_GINT64_FORMAT, offset, + *length, size, src->segment.stop, maxsize); + + /* check size if we have one */ + if (maxsize != -1) { + /* if we run past the end, check if the file became bigger and + * retry. */ + if (G_UNLIKELY (offset + *length >= maxsize)) { + /* see if length of the file changed */ + if (bclass->get_size) + if (!bclass->get_size (src, &size)) + size = -1; + + gst_segment_set_duration (&src->segment, GST_FORMAT_BYTES, size); + + /* make sure we don't exceed the configured segment stop + * if it was set */ + if (src->segment.stop != -1) + maxsize = MIN (size, src->segment.stop); + else + maxsize = size; + + /* if we are at or past the end, EOS */ + if (G_UNLIKELY (offset >= maxsize)) + goto unexpected_length; + + /* else we can clip to the end */ + if (G_UNLIKELY (offset + *length >= maxsize)) + *length = maxsize - offset; + + } + } + + /* keep track of current position. segment is in bytes, we checked + * that above. */ + gst_segment_set_last_stop (&src->segment, GST_FORMAT_BYTES, offset); + + return TRUE; + + /* ERRORS */ +unexpected_length: + { + return FALSE; + } +} + +static GstFlowReturn +rsn_base_src_get_range (RsnBaseSrc * src, guint64 offset, guint length, + GstBuffer ** buf) +{ + GstFlowReturn ret; + RsnBaseSrcClass *bclass; + GstClockReturn status; + + bclass = GST_BASE_SRC_GET_CLASS (src); + + ret = rsn_base_src_wait_playing (src); + if (ret != GST_FLOW_OK) + goto stopped; + + if (G_UNLIKELY (!GST_OBJECT_FLAG_IS_SET (src, GST_BASE_SRC_STARTED))) + goto not_started; + + if (G_UNLIKELY (!bclass->create)) + goto no_function; + + if (G_UNLIKELY (!rsn_base_src_update_length (src, offset, &length))) + goto unexpected_length; + + /* normally we don't count buffers */ + if (G_UNLIKELY (src->num_buffers_left >= 0)) { + if (src->num_buffers_left == 0) + goto reached_num_buffers; + else + src->num_buffers_left--; + } + + GST_DEBUG_OBJECT (src, + "calling create offset %" G_GUINT64_FORMAT " length %u, time %" + G_GINT64_FORMAT, offset, length, src->segment.time); + + ret = bclass->create (src, offset, length, buf); + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto not_ok; + + /* no timestamp set and we are at offset 0, we can timestamp with 0 */ + if (offset == 0 && src->segment.time == 0 + && GST_BUFFER_TIMESTAMP (*buf) == -1) + GST_BUFFER_TIMESTAMP (*buf) = 0; + + /* now sync before pushing the buffer */ + status = rsn_base_src_do_sync (src, *buf); + switch (status) { + case GST_CLOCK_EARLY: + /* the buffer is too late. We currently don't drop the buffer. */ + GST_DEBUG_OBJECT (src, "buffer too late!, returning anyway"); + break; + case GST_CLOCK_OK: + /* buffer synchronised properly */ + GST_DEBUG_OBJECT (src, "buffer ok"); + break; + case GST_CLOCK_UNSCHEDULED: + /* this case is triggered when we were waiting for the clock and + * it got unlocked because we did a state change. We return + * WRONG_STATE in this case to stop the dataflow also get rid of the + * produced buffer. */ + GST_DEBUG_OBJECT (src, + "clock was unscheduled (%d), returning WRONG_STATE", status); + gst_buffer_unref (*buf); + *buf = NULL; + ret = GST_FLOW_WRONG_STATE; + break; + default: + /* all other result values are unexpected and errors */ + GST_ELEMENT_ERROR (src, CORE, CLOCK, + (_("Internal clock error.")), + ("clock returned unexpected return value %d", status)); + gst_buffer_unref (*buf); + *buf = NULL; + ret = GST_FLOW_ERROR; + break; + } + return ret; + + /* ERROR */ +stopped: + { + GST_DEBUG_OBJECT (src, "wait_playing returned %d (%s)", ret, + gst_flow_get_name (ret)); + return ret; + } +not_ok: + { + GST_DEBUG_OBJECT (src, "create returned %d (%s)", ret, + gst_flow_get_name (ret)); + return ret; + } +not_started: + { + GST_DEBUG_OBJECT (src, "getrange but not started"); + return GST_FLOW_WRONG_STATE; + } +no_function: + { + GST_DEBUG_OBJECT (src, "no create function"); + return GST_FLOW_ERROR; + } +unexpected_length: + { + GST_DEBUG_OBJECT (src, "unexpected length %u (offset=%" G_GUINT64_FORMAT + ", size=%" G_GINT64_FORMAT ")", length, offset, src->segment.duration); + return GST_FLOW_UNEXPECTED; + } +reached_num_buffers: + { + GST_DEBUG_OBJECT (src, "sent all buffers"); + return GST_FLOW_UNEXPECTED; + } +} + +static GstFlowReturn +rsn_base_src_pad_get_range (GstPad * pad, guint64 offset, guint length, + GstBuffer ** buf) +{ + RsnBaseSrc *src; + GstFlowReturn res; + + src = GST_BASE_SRC (gst_pad_get_parent (pad)); + + res = rsn_base_src_get_range (src, offset, length, buf); + + gst_object_unref (src); + + return res; +} + +static gboolean +rsn_base_src_default_check_get_range (RsnBaseSrc * src) +{ + gboolean res; + + if (!GST_OBJECT_FLAG_IS_SET (src, GST_BASE_SRC_STARTED)) { + GST_LOG_OBJECT (src, "doing start/stop to check get_range support"); + if (G_LIKELY (rsn_base_src_start (src))) + rsn_base_src_stop (src); + } + + /* we can operate in getrange mode if the native format is bytes + * and we are seekable, this condition is set in the random_access + * flag and is set in the _start() method. */ + res = src->random_access; + + return res; +} + +static gboolean +rsn_base_src_check_get_range (RsnBaseSrc * src) +{ + RsnBaseSrcClass *bclass; + gboolean res; + + bclass = GST_BASE_SRC_GET_CLASS (src); + + if (bclass->check_get_range == NULL) + goto no_function; + + res = bclass->check_get_range (src); + GST_LOG_OBJECT (src, "%s() returned %d", + GST_DEBUG_FUNCPTR_NAME (bclass->check_get_range), (gint) res); + + return res; + + /* ERRORS */ +no_function: + { + GST_WARNING_OBJECT (src, "no check_get_range function set"); + return FALSE; + } +} + +static gboolean +rsn_base_src_pad_check_get_range (GstPad * pad) +{ + RsnBaseSrc *src; + gboolean res; + + src = GST_BASE_SRC (gst_pad_get_parent (pad)); + + res = rsn_base_src_check_get_range (src); + + gst_object_unref (src); + + return res; +} + +static void +rsn_base_src_loop (GstPad * pad) +{ + RsnBaseSrc *src; + GstBuffer *buf = NULL; + GstFlowReturn ret; + gint64 position; + gboolean eos; + + eos = FALSE; + + src = GST_BASE_SRC (gst_pad_get_parent (pad)); + + src->priv->last_sent_eos = FALSE; + + /* if we operate in bytes, we can calculate an offset */ + if (src->segment.format == GST_FORMAT_BYTES) + position = src->segment.last_stop; + else + position = -1; + + ret = rsn_base_src_get_range (src, position, src->blocksize, &buf); + if (G_UNLIKELY (ret != GST_FLOW_OK)) { + GST_INFO_OBJECT (src, "pausing after rsn_base_src_get_range() = %s", + gst_flow_get_name (ret)); + goto pause; + } + /* this should not happen */ + if (G_UNLIKELY (buf == NULL)) + goto null_buffer; + + /* push events to close/start our segment before we push the buffer. */ + if (src->priv->close_segment) { + gst_pad_push_event (pad, src->priv->close_segment); + src->priv->close_segment = NULL; + } + if (src->priv->start_segment) { + gst_pad_push_event (pad, src->priv->start_segment); + src->priv->start_segment = NULL; + } + + /* figure out the new position */ + switch (src->segment.format) { + case GST_FORMAT_BYTES: + position += GST_BUFFER_SIZE (buf); + break; + case GST_FORMAT_TIME: + { + GstClockTime start, duration; + + start = GST_BUFFER_TIMESTAMP (buf); + duration = GST_BUFFER_DURATION (buf); + + if (GST_CLOCK_TIME_IS_VALID (start)) + position = start; + else + position = src->segment.last_stop; + + if (GST_CLOCK_TIME_IS_VALID (duration)) + position += duration; + break; + } + case GST_FORMAT_DEFAULT: + position = GST_BUFFER_OFFSET_END (buf); + break; + default: + position = -1; + break; + } + if (position != -1) { + if (src->segment.stop != -1) { + if (position >= src->segment.stop) { + eos = TRUE; + position = src->segment.stop; + } + } + gst_segment_set_last_stop (&src->segment, src->segment.format, position); + } + + if (G_UNLIKELY (src->priv->discont)) { + buf = gst_buffer_make_metadata_writable (buf); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + src->priv->discont = FALSE; + } + + ret = gst_pad_push (pad, buf); + if (G_UNLIKELY (ret != GST_FLOW_OK)) { + GST_INFO_OBJECT (src, "pausing after gst_pad_push() = %s", + gst_flow_get_name (ret)); + goto pause; + } + + if (eos) { + GST_INFO_OBJECT (src, "pausing after EOS"); + ret = GST_FLOW_UNEXPECTED; + goto pause; + } + +done: + gst_object_unref (src); + return; + + /* special cases */ +pause: + { + const gchar *reason = gst_flow_get_name (ret); + + GST_DEBUG_OBJECT (src, "pausing task, reason %s", reason); + src->data.ABI.running = FALSE; + gst_pad_pause_task (pad); + if (GST_FLOW_IS_FATAL (ret) || ret == GST_FLOW_NOT_LINKED) { + if (ret == GST_FLOW_UNEXPECTED) { + /* perform EOS logic */ + if (src->segment.flags & GST_SEEK_FLAG_SEGMENT) { + gst_element_post_message (GST_ELEMENT_CAST (src), + gst_message_new_segment_done (GST_OBJECT_CAST (src), + src->segment.format, src->segment.last_stop)); + } else { + gst_pad_push_event (pad, gst_event_new_eos ()); + src->priv->last_sent_eos = TRUE; + } + } else { + /* for fatal errors we post an error message, post the error + * first so the app knows about the error first. */ + GST_ELEMENT_ERROR (src, STREAM, FAILED, + (_("Internal data flow error.")), + ("streaming task paused, reason %s (%d)", reason, ret)); + gst_pad_push_event (pad, gst_event_new_eos ()); + src->priv->last_sent_eos = TRUE; + } + } + goto done; + } +null_buffer: + { + GST_ELEMENT_ERROR (src, STREAM, FAILED, + (_("Internal data flow error.")), ("element returned NULL buffer")); + /* we finished the segment on error */ + src->data.ABI.running = FALSE; + gst_pad_pause_task (pad); + gst_pad_push_event (pad, gst_event_new_eos ()); + src->priv->last_sent_eos = TRUE; + goto done; + } +} + +/* this will always be called between start() and stop(). So you can rely on + * resources allocated by start() and freed from stop(). This needs to be added + * to the docs at some point. */ +static gboolean +rsn_base_src_unlock (RsnBaseSrc * basesrc) +{ + RsnBaseSrcClass *bclass; + gboolean result = TRUE; + + GST_DEBUG ("unlock"); + /* unblock whatever the subclass is doing */ + bclass = GST_BASE_SRC_GET_CLASS (basesrc); + if (bclass->unlock) + result = bclass->unlock (basesrc); + + GST_DEBUG ("unschedule clock"); + /* and unblock the clock as well, if any */ + GST_OBJECT_LOCK (basesrc); + if (basesrc->clock_id) { + gst_clock_id_unschedule (basesrc->clock_id); + } + GST_OBJECT_UNLOCK (basesrc); + + GST_DEBUG ("unlock done"); + + return result; +} + +/* this will always be called between start() and stop(). So you can rely on + * resources allocated by start() and freed from stop(). This needs to be added + * to the docs at some point. */ +static gboolean +rsn_base_src_unlock_stop (RsnBaseSrc * basesrc) +{ + RsnBaseSrcClass *bclass; + gboolean result = TRUE; + + GST_DEBUG_OBJECT (basesrc, "unlock stop"); + + /* Finish a previous unblock request, allowing subclasses to flush command + * queues or whatever they need to do */ + bclass = GST_BASE_SRC_GET_CLASS (basesrc); + if (bclass->unlock_stop) + result = bclass->unlock_stop (basesrc); + + GST_DEBUG_OBJECT (basesrc, "unlock stop done"); + + return result; +} + +/* default negotiation code. + * + * Take intersection between src and sink pads, take first + * caps and fixate. + */ +static gboolean +rsn_base_src_default_negotiate (RsnBaseSrc * basesrc) +{ + GstCaps *thiscaps; + GstCaps *caps = NULL; + GstCaps *peercaps = NULL; + gboolean result = FALSE; + + /* first see what is possible on our source pad */ + thiscaps = gst_pad_get_caps (GST_BASE_SRC_PAD (basesrc)); + GST_DEBUG_OBJECT (basesrc, "caps of src: %" GST_PTR_FORMAT, thiscaps); + /* nothing or anything is allowed, we're done */ + if (thiscaps == NULL || gst_caps_is_any (thiscaps)) + goto no_nego_needed; + + /* get the peer caps */ + peercaps = gst_pad_peer_get_caps (GST_BASE_SRC_PAD (basesrc)); + GST_DEBUG_OBJECT (basesrc, "caps of peer: %" GST_PTR_FORMAT, peercaps); + if (peercaps) { + GstCaps *icaps; + + /* get intersection */ + icaps = gst_caps_intersect (thiscaps, peercaps); + GST_DEBUG_OBJECT (basesrc, "intersect: %" GST_PTR_FORMAT, icaps); + gst_caps_unref (thiscaps); + gst_caps_unref (peercaps); + if (icaps) { + /* take first (and best, since they are sorted) possibility */ + caps = gst_caps_copy_nth (icaps, 0); + gst_caps_unref (icaps); + } + } else { + /* no peer, work with our own caps then */ + caps = thiscaps; + } + if (caps) { + caps = gst_caps_make_writable (caps); + gst_caps_truncate (caps); + + /* now fixate */ + if (!gst_caps_is_empty (caps)) { + gst_pad_fixate_caps (GST_BASE_SRC_PAD (basesrc), caps); + GST_DEBUG_OBJECT (basesrc, "fixated to: %" GST_PTR_FORMAT, caps); + + if (gst_caps_is_any (caps)) { + /* hmm, still anything, so element can do anything and + * nego is not needed */ + result = TRUE; + } else if (gst_caps_is_fixed (caps)) { + /* yay, fixed caps, use those then */ + gst_pad_set_caps (GST_BASE_SRC_PAD (basesrc), caps); + result = TRUE; + } + } + gst_caps_unref (caps); + } + return result; + +no_nego_needed: + { + GST_DEBUG_OBJECT (basesrc, "no negotiation needed"); + if (thiscaps) + gst_caps_unref (thiscaps); + return TRUE; + } +} + +static gboolean +rsn_base_src_negotiate (RsnBaseSrc * basesrc) +{ + RsnBaseSrcClass *bclass; + gboolean result = TRUE; + + bclass = GST_BASE_SRC_GET_CLASS (basesrc); + + if (bclass->negotiate) + result = bclass->negotiate (basesrc); + + return result; +} + +static gboolean +rsn_base_src_start (RsnBaseSrc * basesrc) +{ + RsnBaseSrcClass *bclass; + gboolean result; + guint64 size; + + if (GST_OBJECT_FLAG_IS_SET (basesrc, GST_BASE_SRC_STARTED)) + return TRUE; + + GST_DEBUG_OBJECT (basesrc, "starting source"); + + basesrc->num_buffers_left = basesrc->num_buffers; + + gst_segment_init (&basesrc->segment, basesrc->segment.format); + basesrc->data.ABI.running = FALSE; + + bclass = GST_BASE_SRC_GET_CLASS (basesrc); + if (bclass->start) + result = bclass->start (basesrc); + else + result = TRUE; + + if (!result) + goto could_not_start; + + GST_OBJECT_FLAG_SET (basesrc, GST_BASE_SRC_STARTED); + + /* figure out the size */ + if (basesrc->segment.format == GST_FORMAT_BYTES) { + if (bclass->get_size) { + if (!(result = bclass->get_size (basesrc, &size))) + size = -1; + } else { + result = FALSE; + size = -1; + } + GST_DEBUG_OBJECT (basesrc, "setting size %" G_GUINT64_FORMAT, size); + /* only update the size when operating in bytes, subclass is supposed + * to set duration in the start method for other formats */ + gst_segment_set_duration (&basesrc->segment, GST_FORMAT_BYTES, size); + } else { + size = -1; + } + + GST_DEBUG_OBJECT (basesrc, + "format: %d, have size: %d, size: %" G_GUINT64_FORMAT ", duration: %" + G_GINT64_FORMAT, basesrc->segment.format, result, size, + basesrc->segment.duration); + + /* check if we can seek */ + if (bclass->is_seekable) + basesrc->seekable = bclass->is_seekable (basesrc); + else + basesrc->seekable = FALSE; + + GST_DEBUG_OBJECT (basesrc, "is seekable: %d", basesrc->seekable); + + /* update for random access flag */ + basesrc->random_access = basesrc->seekable && + basesrc->segment.format == GST_FORMAT_BYTES; + + GST_DEBUG_OBJECT (basesrc, "is random_access: %d", basesrc->random_access); + + /* run typefind if we are random_access and the typefinding is enabled. */ + if (basesrc->random_access && basesrc->data.ABI.typefind && size != -1) { + GstCaps *caps; + + caps = gst_type_find_helper (basesrc->srcpad, size); + gst_pad_set_caps (basesrc->srcpad, caps); + gst_caps_unref (caps); + } else { + /* use class or default negotiate function */ + if (!rsn_base_src_negotiate (basesrc)) + goto could_not_negotiate; + } + + return TRUE; + + /* ERROR */ +could_not_start: + { + GST_DEBUG_OBJECT (basesrc, "could not start"); + /* subclass is supposed to post a message. We don't have to call _stop. */ + return FALSE; + } +could_not_negotiate: + { + GST_DEBUG_OBJECT (basesrc, "could not negotiate, stopping"); + GST_ELEMENT_ERROR (basesrc, STREAM, FORMAT, + ("Could not negotiate format"), ("Check your filtered caps, if any")); + /* we must call stop */ + rsn_base_src_stop (basesrc); + return FALSE; + } +} + +static gboolean +rsn_base_src_stop (RsnBaseSrc * basesrc) +{ + RsnBaseSrcClass *bclass; + gboolean result = TRUE; + + if (!GST_OBJECT_FLAG_IS_SET (basesrc, GST_BASE_SRC_STARTED)) + return TRUE; + + GST_DEBUG_OBJECT (basesrc, "stopping source"); + + bclass = GST_BASE_SRC_GET_CLASS (basesrc); + if (bclass->stop) + result = bclass->stop (basesrc); + + if (result) + GST_OBJECT_FLAG_UNSET (basesrc, GST_BASE_SRC_STARTED); + + return result; +} + +static gboolean +rsn_base_src_deactivate (RsnBaseSrc * basesrc, GstPad * pad) +{ + gboolean result; + + GST_LIVE_LOCK (basesrc); + basesrc->live_running = TRUE; + GST_LIVE_SIGNAL (basesrc); + GST_LIVE_UNLOCK (basesrc); + + /* step 1, unblock clock sync (if any) */ + result = rsn_base_src_unlock (basesrc); + + /* step 2, make sure streaming finishes */ + result &= gst_pad_stop_task (pad); + + /* step 3, clear the unblock condition */ + result &= rsn_base_src_unlock_stop (basesrc); + + return result; +} + +static gboolean +rsn_base_src_activate_push (GstPad * pad, gboolean active) +{ + RsnBaseSrc *basesrc; + GstEvent *event; + + basesrc = GST_BASE_SRC (GST_OBJECT_PARENT (pad)); + + /* prepare subclass first */ + if (active) { + GST_DEBUG_OBJECT (basesrc, "Activating in push mode"); + + if (G_UNLIKELY (!basesrc->can_activate_push)) + goto no_push_activation; + + if (G_UNLIKELY (!rsn_base_src_start (basesrc))) + goto error_start; + + basesrc->priv->last_sent_eos = FALSE; + + /* do initial seek, which will start the task */ + GST_OBJECT_LOCK (basesrc); + event = basesrc->data.ABI.pending_seek; + basesrc->data.ABI.pending_seek = NULL; + GST_OBJECT_UNLOCK (basesrc); + + /* no need to unlock anything, the task is certainly + * not running here. The perform seek code will start the task when + * finished. */ + if (G_UNLIKELY (!rsn_base_src_perform_seek (basesrc, event, FALSE))) + goto seek_failed; + + if (event) + gst_event_unref (event); + } else { + GST_DEBUG_OBJECT (basesrc, "Deactivating in push mode"); + /* call the unlock function and stop the task */ + if (G_UNLIKELY (!rsn_base_src_deactivate (basesrc, pad))) + goto deactivate_failed; + + /* now we can stop the source */ + if (G_UNLIKELY (!rsn_base_src_stop (basesrc))) + goto error_stop; + } + return TRUE; + + /* ERRORS */ +no_push_activation: + { + GST_WARNING_OBJECT (basesrc, "Subclass disabled push-mode activation"); + return FALSE; + } +error_start: + { + GST_WARNING_OBJECT (basesrc, "Failed to start in push mode"); + return FALSE; + } +seek_failed: + { + GST_ERROR_OBJECT (basesrc, "Failed to perform initial seek"); + rsn_base_src_stop (basesrc); + if (event) + gst_event_unref (event); + return FALSE; + } +deactivate_failed: + { + GST_ERROR_OBJECT (basesrc, "Failed to deactivate in push mode"); + return FALSE; + } +error_stop: + { + GST_DEBUG_OBJECT (basesrc, "Failed to stop in push mode"); + return FALSE; + } +} + +static gboolean +rsn_base_src_activate_pull (GstPad * pad, gboolean active) +{ + RsnBaseSrc *basesrc; + + basesrc = GST_BASE_SRC (GST_OBJECT_PARENT (pad)); + + /* prepare subclass first */ + if (active) { + GST_DEBUG_OBJECT (basesrc, "Activating in pull mode"); + if (G_UNLIKELY (!rsn_base_src_start (basesrc))) + goto error_start; + + /* if not random_access, we cannot operate in pull mode for now */ + if (G_UNLIKELY (!rsn_base_src_check_get_range (basesrc))) + goto no_get_range; + } else { + GST_DEBUG_OBJECT (basesrc, "Deactivating in pull mode"); + /* call the unlock function. We have no task to stop. */ + if (G_UNLIKELY (!rsn_base_src_deactivate (basesrc, pad))) + goto deactivate_failed; + + /* don't send EOS when going from PAUSED => READY when in pull mode */ + basesrc->priv->last_sent_eos = TRUE; + + if (G_UNLIKELY (!rsn_base_src_stop (basesrc))) + goto error_stop; + } + return TRUE; + + /* ERRORS */ +error_start: + { + GST_ERROR_OBJECT (basesrc, "Failed to start in pull mode"); + return FALSE; + } +no_get_range: + { + GST_ERROR_OBJECT (basesrc, "Cannot operate in pull mode, stopping"); + rsn_base_src_stop (basesrc); + return FALSE; + } +deactivate_failed: + { + GST_ERROR_OBJECT (basesrc, "Failed to deactivate in pull mode"); + return FALSE; + } +error_stop: + { + GST_ERROR_OBJECT (basesrc, "Failed to stop in pull mode"); + return FALSE; + } +} + +static GstStateChangeReturn +rsn_base_src_change_state (GstElement * element, GstStateChange transition) +{ + RsnBaseSrc *basesrc; + GstStateChangeReturn result; + gboolean no_preroll = FALSE; + + basesrc = GST_BASE_SRC (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_LIVE_LOCK (element); + basesrc->priv->latency = -1; + if (basesrc->is_live) { + no_preroll = TRUE; + basesrc->live_running = FALSE; + } + basesrc->priv->last_sent_eos = FALSE; + basesrc->priv->discont = TRUE; + GST_LIVE_UNLOCK (element); + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + GST_LIVE_LOCK (element); + if (basesrc->is_live) { + gboolean start; + + rsn_base_src_unlock_stop (basesrc); + /* for live sources we restart the timestamp correction */ + basesrc->priv->latency = -1; + basesrc->live_running = TRUE; + GST_LIVE_SIGNAL (element); + /* have to restart the task in case it stopped because of the unlock when + * we went to PAUSED. Only do this if we operating in push mode. */ + GST_OBJECT_LOCK (basesrc->srcpad); + start = (GST_PAD_ACTIVATE_MODE (basesrc->srcpad) == GST_ACTIVATE_PUSH); + GST_OBJECT_UNLOCK (basesrc->srcpad); + if (start) + gst_pad_start_task (basesrc->srcpad, + (GstTaskFunction) rsn_base_src_loop, basesrc->srcpad); + } + GST_LIVE_UNLOCK (element); + break; + default: + break; + } + + if ((result = + GST_ELEMENT_CLASS (parent_class)->change_state (element, + transition)) == GST_STATE_CHANGE_FAILURE) + goto failure; + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + GST_LIVE_LOCK (element); + if (basesrc->is_live) { + rsn_base_src_unlock (basesrc); + no_preroll = TRUE; + basesrc->live_running = FALSE; + } + GST_LIVE_UNLOCK (element); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + { + GstEvent **event_p; + + /* FIXME, deprecate this behaviour, it is very dangerous. + * the prefered way of sending EOS downstream is by sending + * the EOS event to the element */ + if (!basesrc->priv->last_sent_eos) { + GST_DEBUG_OBJECT (basesrc, "Sending EOS event"); + gst_pad_push_event (basesrc->srcpad, gst_event_new_eos ()); + basesrc->priv->last_sent_eos = TRUE; + } + event_p = &basesrc->data.ABI.pending_seek; + gst_event_replace (event_p, NULL); + event_p = &basesrc->priv->close_segment; + gst_event_replace (event_p, NULL); + event_p = &basesrc->priv->start_segment; + gst_event_replace (event_p, NULL); + break; + } + case GST_STATE_CHANGE_READY_TO_NULL: + break; + default: + break; + } + + if (no_preroll && result == GST_STATE_CHANGE_SUCCESS) + result = GST_STATE_CHANGE_NO_PREROLL; + + return result; + + /* ERRORS */ +failure: + { + GST_DEBUG_OBJECT (basesrc, "parent failed state change"); + return result; + } +} diff --git a/ext/resindvd/rsnbasesrc.h b/ext/resindvd/rsnbasesrc.h new file mode 100644 index 00000000..9365c20b --- /dev/null +++ b/ext/resindvd/rsnbasesrc.h @@ -0,0 +1,257 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2005 Wim Taymans <wim@fluendo.com> + * + * gstbasesrc.h: + * + * 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. + */ + +/* + * + * This is a temporary copy of GstBaseSrc/GstPushSrc for the resin + * DVD components, to work around a deadlock with source elements that + * send seeks to themselves. + * + */ + +#ifndef __GST_BASE_SRC_H__ +#define __GST_BASE_SRC_H__ + +#include <gst/gst.h> + +G_BEGIN_DECLS + +#define RSN_TYPE_BASE_SRC (rsn_base_src_get_type()) +#define GST_BASE_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),RSN_TYPE_BASE_SRC,RsnBaseSrc)) +#define GST_BASE_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),RSN_TYPE_BASE_SRC,RsnBaseSrcClass)) +#define GST_BASE_SRC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), RSN_TYPE_BASE_SRC, RsnBaseSrcClass)) +#define GST_IS_BASE_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),RSN_TYPE_BASE_SRC)) +#define GST_IS_BASE_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),RSN_TYPE_BASE_SRC)) +#define GST_BASE_SRC_CAST(obj) ((RsnBaseSrc *)(obj)) + +/** + * RsnBaseSrcFlags: + * @GST_BASE_SRC_STARTED: has source been started + * @GST_BASE_SRC_FLAG_LAST: offset to define more flags + * + * The #GstElement flags that a basesrc element may have. + */ +typedef enum { + GST_BASE_SRC_STARTED = (GST_ELEMENT_FLAG_LAST << 0), + /* padding */ + GST_BASE_SRC_FLAG_LAST = (GST_ELEMENT_FLAG_LAST << 2) +} RsnBaseSrcFlags; + +typedef struct _RsnBaseSrc RsnBaseSrc; +typedef struct _RsnBaseSrcClass RsnBaseSrcClass; +typedef struct _RsnBaseSrcPrivate RsnBaseSrcPrivate; + +/** + * GST_BASE_SRC_PAD: + * @obj: base source instance + * + * Gives the pointer to the #GstPad object of the element. + */ +#define GST_BASE_SRC_PAD(obj) (GST_BASE_SRC_CAST (obj)->srcpad) + + +/** + * RsnBaseSrc: + * @element: the parent element. + * + * The opaque #RsnBaseSrc data structure. + */ +struct _RsnBaseSrc { + GstElement element; + + /*< protected >*/ + GstPad *srcpad; + + /* available to subclass implementations */ + /* MT-protected (with LIVE_LOCK) */ + GMutex *live_lock; + GCond *live_cond; + gboolean is_live; + gboolean live_running; + + /* MT-protected (with LOCK) */ + gint blocksize; /* size of buffers when operating push based */ + gboolean can_activate_push; /* some scheduling properties */ + GstActivateMode pad_mode; + gboolean seekable; + gboolean random_access; + + GstClockID clock_id; /* for syncing */ + GstClockTime end_time; + + /* MT-protected (with STREAM_LOCK) */ + GstSegment segment; + gboolean need_newsegment; + + guint64 offset; /* current offset in the resource, unused */ + guint64 size; /* total size of the resource, unused */ + + gint num_buffers; + gint num_buffers_left; + + /*< private >*/ + union { + struct { + /* FIXME: those fields should be moved into the private struct */ + gboolean typefind; + gboolean running; + GstEvent *pending_seek; + } ABI; + gpointer _gst_reserved[GST_PADDING_LARGE-1]; + } data; + + RsnBaseSrcPrivate *priv; +}; + +/** + * RsnBaseSrcClass: + * @parent_class: Element parent class + * @get_caps: Called to get the caps to report + * @set_caps: Notify subclass of changed output caps + * @negotiate: Negotiated the caps with the peer. + * @newsegment: Generate and send a new_segment event (UNUSED) + * @start: Start processing. Subclasses should open resources and prepare + * to produce data. + * @stop: Stop processing. Subclasses should use this to close resources. + * @get_times: Given a buffer, return the start and stop time when it + * should be pushed out. The base class will sync on the clock using + * these times. + * @get_size: Return the total size of the resource, in the configured format. + * @is_seekable: Check if the source can seek + * @unlock: Unlock any pending access to the resource. Subclasses should + * unblock any blocked function ASAP + * @unlock_stop: Clear the previous unlock request. Subclasses should clear + * any state they set during unlock(), such as clearing command queues. + * @event: Override this to implement custom event handling. + * @create: Ask the subclass to create a buffer with offset and size. + * @do_seek: Perform seeking on the resource to the indicated segment. + * @prepare_seek_segment: Prepare the GstSegment that will be passed to the + * do_seek vmethod for executing a seek request. Sub-classes should override + * this if they support seeking in formats other than the configured native + * format. By default, it tries to convert the seek arguments to the + * configured native format and prepare a segment in that format. + * Since: 0.10.13 + * @query: Handle a requested query. + * @check_get_range: Check whether the source would support pull-based + * operation if it were to be opened now. This vfunc is optional, but + * should be implemented if possible to avoid unnecessary start/stop + * cycles. The default implementation will open and close the resource + * to find out whether get_range is supported, and that is usually + * undesirable. + * @fixate: Called during negotation if caps need fixating. Implement instead of + * setting a fixate function on the source pad. + * + * Subclasses can override any of the available virtual methods or not, as + * needed. At the minimum, the @create method should be overridden to produce + * buffers. + */ +struct _RsnBaseSrcClass { + GstElementClass parent_class; + + /*< public >*/ + /* virtual methods for subclasses */ + + /* get caps from subclass */ + GstCaps* (*get_caps) (RsnBaseSrc *src); + /* notify the subclass of new caps */ + gboolean (*set_caps) (RsnBaseSrc *src, GstCaps *caps); + + /* decide on caps */ + gboolean (*negotiate) (RsnBaseSrc *src); + + /* generate and send a newsegment (UNUSED) */ + gboolean (*newsegment) (RsnBaseSrc *src); + + /* start and stop processing, ideal for opening/closing the resource */ + gboolean (*start) (RsnBaseSrc *src); + gboolean (*stop) (RsnBaseSrc *src); + + /* given a buffer, return start and stop time when it should be pushed + * out. The base class will sync on the clock using these times. */ + void (*get_times) (RsnBaseSrc *src, GstBuffer *buffer, + GstClockTime *start, GstClockTime *end); + + /* get the total size of the resource in bytes */ + gboolean (*get_size) (RsnBaseSrc *src, guint64 *size); + + /* check if the resource is seekable */ + gboolean (*is_seekable) (RsnBaseSrc *src); + /* unlock any pending access to the resource. subclasses should unlock + * any function ASAP. */ + gboolean (*unlock) (RsnBaseSrc *src); + + /* notify subclasses of an event */ + gboolean (*event) (RsnBaseSrc *src, GstEvent *event); + + /* ask the subclass to create a buffer with offset and size */ + GstFlowReturn (*create) (RsnBaseSrc *src, guint64 offset, guint size, + GstBuffer **buf); + + /* additions that change padding... */ + /* notify subclasses of a seek */ + gboolean (*do_seek) (RsnBaseSrc *src, GstSegment *segment); + /* notify subclasses of a query */ + gboolean (*query) (RsnBaseSrc *src, GstQuery *query); + + /* check whether the source would support pull-based operation if + * it were to be opened now. This vfunc is optional, but should be + * implemented if possible to avoid unnecessary start/stop cycles. + * The default implementation will open and close the resource to + * find out whether get_range is supported and that is usually + * undesirable. */ + gboolean (*check_get_range) (RsnBaseSrc *src); + + /* called if, in negotation, caps need fixating */ + void (*fixate) (RsnBaseSrc *src, GstCaps *caps); + + /* Clear any pending unlock request, as we succeeded in unlocking */ + gboolean (*unlock_stop) (RsnBaseSrc *src); + + /* Prepare the segment on which to perform do_seek(), converting to the + * current basesrc format. */ + gboolean (*prepare_seek_segment) (RsnBaseSrc *src, GstEvent *seek, + GstSegment *segment); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING_LARGE - 6]; +}; + +GType rsn_base_src_get_type (void); + +GstFlowReturn rsn_base_src_wait_playing (RsnBaseSrc *src); + +void rsn_base_src_set_live (RsnBaseSrc *src, gboolean live); +gboolean rsn_base_src_is_live (RsnBaseSrc *src); + +void rsn_base_src_set_format (RsnBaseSrc *src, GstFormat format); + +gboolean rsn_base_src_query_latency (RsnBaseSrc *src, gboolean * live, + GstClockTime * min_latency, + GstClockTime * max_latency); + +void rsn_base_src_set_do_timestamp (RsnBaseSrc *src, gboolean live); +gboolean rsn_base_src_get_do_timestamp (RsnBaseSrc *src); + +G_END_DECLS + +#endif /* __GST_BASE_SRC_H__ */ diff --git a/ext/resindvd/rsnpushsrc.c b/ext/resindvd/rsnpushsrc.c new file mode 100644 index 00000000..363b41d6 --- /dev/null +++ b/ext/resindvd/rsnpushsrc.c @@ -0,0 +1,101 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000,2005 Wim Taymans <wim@fluendo.com> + * + * gstpushsrc.c: + * + * 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. + */ + +/* + * + * This is a temporary copy of GstBaseSrc/GstPushSrc for the resin + * DVD components, to work around a deadlock with source elements that + * send seeks to themselves. + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <stdlib.h> +#include <string.h> + +#include "rsnpushsrc.h" +#include <gst/gstmarshal.h> + +GST_DEBUG_CATEGORY_STATIC (rsn_push_src_debug); +#define GST_CAT_DEFAULT rsn_push_src_debug + +#define _do_init(type) \ + GST_DEBUG_CATEGORY_INIT (rsn_push_src_debug, "pushsrc", 0, \ + "pushsrc element"); + +GST_BOILERPLATE_FULL (RsnPushSrc, rsn_push_src, RsnBaseSrc, RSN_TYPE_BASE_SRC, + _do_init); + +static gboolean rsn_push_src_check_get_range (RsnBaseSrc * src); +static GstFlowReturn rsn_push_src_create (RsnBaseSrc * bsrc, guint64 offset, + guint length, GstBuffer ** ret); + +static void +rsn_push_src_base_init (gpointer g_class) +{ + /* nop */ +} + +static void +rsn_push_src_class_init (RsnPushSrcClass * klass) +{ + RsnBaseSrcClass *gstbasesrc_class = (RsnBaseSrcClass *) klass; + + gstbasesrc_class->create = GST_DEBUG_FUNCPTR (rsn_push_src_create); + gstbasesrc_class->check_get_range = + GST_DEBUG_FUNCPTR (rsn_push_src_check_get_range); +} + +static void +rsn_push_src_init (RsnPushSrc * pushsrc, RsnPushSrcClass * klass) +{ + /* nop */ +} + +static gboolean +rsn_push_src_check_get_range (RsnBaseSrc * src) +{ + /* a pushsrc can by default never operate in pull mode override + * if you want something different. */ + return FALSE; +} + +static GstFlowReturn +rsn_push_src_create (RsnBaseSrc * bsrc, guint64 offset, guint length, + GstBuffer ** ret) +{ + GstFlowReturn fret; + RsnPushSrc *src; + RsnPushSrcClass *pclass; + + src = GST_PUSH_SRC (bsrc); + pclass = GST_PUSH_SRC_GET_CLASS (src); + if (pclass->create) + fret = pclass->create (src, ret); + else + fret = GST_FLOW_ERROR; + + return fret; +} diff --git a/ext/resindvd/rsnpushsrc.h b/ext/resindvd/rsnpushsrc.h new file mode 100644 index 00000000..eb50f031 --- /dev/null +++ b/ext/resindvd/rsnpushsrc.h @@ -0,0 +1,76 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> + * 2000 Wim Taymans <wtay@chello.be> + * 2005 Wim Taymans <wim@fluendo.com> + * + * gstpushsrc.h: + * + * 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. + */ + +/* + * + * This is a temporary copy of GstBaseSrc/GstPushSrc for the resin + * DVD components, to work around a deadlock with source elements that + * send seeks to themselves. + * + */ +#ifndef __GST_PUSH_SRC_H__ +#define __GST_PUSH_SRC_H__ + +#include <gst/gst.h> +#include "rsnbasesrc.h" + +G_BEGIN_DECLS + +#define RSN_TYPE_PUSH_SRC (rsn_push_src_get_type()) +#define GST_PUSH_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),RSN_TYPE_PUSH_SRC,RsnPushSrc)) +#define GST_PUSH_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),RSN_TYPE_PUSH_SRC,RsnPushSrcClass)) +#define GST_PUSH_SRC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), RSN_TYPE_PUSH_SRC, RsnPushSrcClass)) +#define GST_IS_PUSH_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),RSN_TYPE_PUSH_SRC)) +#define GST_IS_PUSH_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),RSN_TYPE_PUSH_SRC)) + +typedef struct _RsnPushSrc RsnPushSrc; +typedef struct _RsnPushSrcClass RsnPushSrcClass; + +/** + * RsnPushSrc: + * @parent: the parent base source object. + * + * The opaque #RsnPushSrc data structure. + */ +struct _RsnPushSrc { + RsnBaseSrc parent; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +struct _RsnPushSrcClass { + RsnBaseSrcClass parent_class; + + /* ask the subclass to create a buffer */ + GstFlowReturn (*create) (RsnPushSrc *src, GstBuffer **buf); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType rsn_push_src_get_type(void); + +G_END_DECLS + +#endif /* __GST_PUSH_SRC_H__ */ diff --git a/ext/resindvd/rsnstreamselector.c b/ext/resindvd/rsnstreamselector.c new file mode 100644 index 00000000..b422ac38 --- /dev/null +++ b/ext/resindvd/rsnstreamselector.c @@ -0,0 +1,784 @@ +/* GStreamer + * Copyright (C) 2003 Julien Moutte <julien@moutte.net> + * Copyright (C) 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net> + * Copyright (C) 2005 Jan Schmidt <thaytan@mad.scientist.com> + * Copyright (C) 2007 Wim Taymans <wim.taymans@gmail.com> + * + * 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. + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> + +#include "rsnstreamselector.h" + +GST_DEBUG_CATEGORY_STATIC (stream_selector_debug); +#define GST_CAT_DEFAULT stream_selector_debug + +static const GstElementDetails rsn_stream_selector_details = +GST_ELEMENT_DETAILS ("StreamSelector", + "Generic", + "N-to-1 input stream_selectoring", + "Julien Moutte <julien@moutte.net>\n" + "Ronald S. Bultje <rbultje@ronald.bitfreak.net>\n" + "Jan Schmidt <thaytan@mad.scientist.com>\n" + "Wim Taymans <wim.taymans@gmail.com>"); + +static GstStaticPadTemplate rsn_stream_selector_sink_factory = +GST_STATIC_PAD_TEMPLATE ("sink%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate rsn_stream_selector_src_factory = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +enum +{ + PROP_0, + PROP_N_PADS, + PROP_ACTIVE_PAD, + PROP_LAST +}; + +static gboolean rsn_stream_selector_is_active_sinkpad (RsnStreamSelector * sel, + GstPad * pad); +static GstPad *rsn_stream_selector_activate_sinkpad (RsnStreamSelector * sel, + GstPad * pad); +static GstPad *rsn_stream_selector_get_linked_pad (GstPad * pad, + gboolean strict); + +#define RSN_TYPE_SELECTOR_PAD \ + (gst_selector_pad_get_type()) +#define GST_SELECTOR_PAD(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), RSN_TYPE_SELECTOR_PAD, RsnSelectorPad)) +#define GST_SELECTOR_PAD_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), RSN_TYPE_SELECTOR_PAD, RsnSelectorPadClass)) +#define RSN_IS_SELECTOR_PAD(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), RSN_TYPE_SELECTOR_PAD)) +#define RSN_IS_SELECTOR_PAD_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), RSN_TYPE_SELECTOR_PAD)) +#define GST_SELECTOR_PAD_CAST(obj) \ + ((RsnSelectorPad *)(obj)) + +typedef struct _RsnSelectorPad RsnSelectorPad; +typedef struct _RsnSelectorPadClass RsnSelectorPadClass; + +struct _RsnSelectorPad +{ + GstPad parent; + + gboolean active; + gboolean eos; + gboolean segment_pending; + GstSegment segment; + GstTagList *tags; +}; + +struct _RsnSelectorPadClass +{ + GstPadClass parent; +}; + +static void gst_selector_pad_class_init (RsnSelectorPadClass * klass); +static void gst_selector_pad_init (RsnSelectorPad * pad); +static void gst_selector_pad_finalize (GObject * object); + +static void gst_selector_pad_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +static GstPadClass *selector_pad_parent_class = NULL; + +static void gst_selector_pad_reset (RsnSelectorPad * pad); +static gboolean gst_selector_pad_event (GstPad * pad, GstEvent * event); +static GstCaps *gst_selector_pad_getcaps (GstPad * pad); +static GList *gst_selector_pad_get_linked_pads (GstPad * pad); +static GstFlowReturn gst_selector_pad_chain (GstPad * pad, GstBuffer * buf); +static GstFlowReturn gst_selector_pad_bufferalloc (GstPad * pad, + guint64 offset, guint size, GstCaps * caps, GstBuffer ** buf); + +enum +{ + PROP_PAD_0, + PROP_PAD_TAGS, + PROP_PAD_ACTIVE, + PROP_PAD_LAST +}; + +static GType +gst_selector_pad_get_type (void) +{ + static GType selector_pad_type = 0; + + if (!selector_pad_type) { + static const GTypeInfo selector_pad_info = { + sizeof (RsnSelectorPadClass), + NULL, + NULL, + (GClassInitFunc) gst_selector_pad_class_init, + NULL, + NULL, + sizeof (RsnSelectorPad), + 0, + (GInstanceInitFunc) gst_selector_pad_init, + }; + + selector_pad_type = + g_type_register_static (GST_TYPE_PAD, "RsnSelectorPad", + &selector_pad_info, 0); + } + return selector_pad_type; +} + +static void +gst_selector_pad_class_init (RsnSelectorPadClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) klass; + + selector_pad_parent_class = g_type_class_peek_parent (klass); + + gobject_class->finalize = gst_selector_pad_finalize; + gobject_class->get_property = + GST_DEBUG_FUNCPTR (gst_selector_pad_get_property); + + g_object_class_install_property (gobject_class, PROP_PAD_TAGS, + g_param_spec_boxed ("tags", "Tags", + "The currently active tags on the pad", GST_TYPE_TAG_LIST, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_PAD_ACTIVE, + g_param_spec_boolean ("active", "Active", + "If the pad is currently active", FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_selector_pad_init (RsnSelectorPad * pad) +{ + gst_selector_pad_reset (pad); +} + +static void +gst_selector_pad_finalize (GObject * object) +{ + RsnSelectorPad *pad; + + pad = GST_SELECTOR_PAD_CAST (object); + + if (pad->tags) + gst_tag_list_free (pad->tags); + + G_OBJECT_CLASS (selector_pad_parent_class)->finalize (object); +} + +static void +gst_selector_pad_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + RsnSelectorPad *pad; + + pad = GST_SELECTOR_PAD (object); + + switch (prop_id) { + case PROP_PAD_TAGS: + GST_OBJECT_LOCK (object); + g_value_set_boxed (value, pad->tags); + GST_OBJECT_UNLOCK (object); + break; + case PROP_PAD_ACTIVE: + { + RsnStreamSelector *sel; + + sel = RSN_STREAM_SELECTOR (gst_pad_get_parent (pad)); + g_value_set_boolean (value, rsn_stream_selector_is_active_sinkpad (sel, + GST_PAD_CAST (pad))); + gst_object_unref (sel); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_selector_pad_reset (RsnSelectorPad * pad) +{ + pad->active = FALSE; + pad->eos = FALSE; + gst_segment_init (&pad->segment, GST_FORMAT_UNDEFINED); +} + +/* strictly get the linked pad from the sinkpad. If the pad is active we return + * the srcpad else we return NULL */ +static GList * +gst_selector_pad_get_linked_pads (GstPad * pad) +{ + GstPad *otherpad; + + otherpad = rsn_stream_selector_get_linked_pad (pad, TRUE); + if (!otherpad) + return NULL; + + /* need to drop the ref, internal linked pads is not MT safe */ + gst_object_unref (otherpad); + + return g_list_append (NULL, otherpad); +} + +static gboolean +gst_selector_pad_event (GstPad * pad, GstEvent * event) +{ + gboolean res = TRUE; + gboolean forward = TRUE; + RsnStreamSelector *sel; + RsnSelectorPad *selpad; + GstPad *active_sinkpad; + + sel = RSN_STREAM_SELECTOR (gst_pad_get_parent (pad)); + selpad = GST_SELECTOR_PAD_CAST (pad); + + /* only forward if we are dealing with the active sinkpad */ + active_sinkpad = rsn_stream_selector_activate_sinkpad (sel, pad); + forward = (active_sinkpad == pad); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_STOP: + gst_selector_pad_reset (selpad); + break; + case GST_EVENT_NEWSEGMENT: + { + gboolean update; + GstFormat format; + gdouble rate, arate; + gint64 start, stop, time; + + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + + GST_DEBUG_OBJECT (selpad, + "configured NEWSEGMENT update %d, rate %lf, applied rate %lf, " + "format %d, " + "%" G_GINT64_FORMAT " -- %" G_GINT64_FORMAT ", time %" + G_GINT64_FORMAT, update, rate, arate, format, start, stop, time); + + gst_segment_set_newsegment_full (&selpad->segment, update, + rate, arate, format, start, stop, time); + /* if we are not going to forward the segment, mark the segment as + * pending */ + if (!forward) + selpad->segment_pending = TRUE; + break; + } + case GST_EVENT_TAG: + { + GstTagList *tags; + + GST_OBJECT_LOCK (selpad); + if (selpad->tags) + gst_tag_list_free (selpad->tags); + gst_event_parse_tag (event, &tags); + if (tags) + tags = gst_tag_list_copy (tags); + selpad->tags = tags; + GST_DEBUG_OBJECT (sel, "received tags %" GST_PTR_FORMAT, selpad->tags); + GST_OBJECT_UNLOCK (selpad); + break; + } + case GST_EVENT_EOS: + selpad->eos = TRUE; + break; + default: + break; + } + if (forward) + res = gst_pad_push_event (sel->srcpad, event); + else + gst_event_unref (event); + + gst_object_unref (sel); + + return res; +} + +static GstCaps * +gst_selector_pad_getcaps (GstPad * pad) +{ + RsnStreamSelector *sel; + GstCaps *caps; + + sel = RSN_STREAM_SELECTOR (gst_pad_get_parent (pad)); + + GST_DEBUG_OBJECT (sel, "Getting caps of srcpad peer"); + caps = gst_pad_peer_get_caps (sel->srcpad); + if (caps == NULL) + caps = gst_caps_new_any (); + + gst_object_unref (sel); + + return caps; +} + +static GstFlowReturn +gst_selector_pad_bufferalloc (GstPad * pad, guint64 offset, + guint size, GstCaps * caps, GstBuffer ** buf) +{ + RsnStreamSelector *sel; + GstFlowReturn result; + GstPad *active_sinkpad; + + sel = RSN_STREAM_SELECTOR (gst_pad_get_parent (pad)); + + active_sinkpad = rsn_stream_selector_activate_sinkpad (sel, pad); + + /* Fallback allocation for buffers from pads except the selected one */ + if (pad != active_sinkpad) { + GST_DEBUG_OBJECT (sel, + "Pad %s:%s is not selected. Performing fallback allocation", + GST_DEBUG_PAD_NAME (pad)); + + *buf = NULL; + result = GST_FLOW_OK; + } else { + result = gst_pad_alloc_buffer (sel->srcpad, offset, size, caps, buf); + + /* FIXME: HACK. If buffer alloc returns not-linked, perform a fallback + * allocation. This should NOT be necessary, because playbin should + * properly block the source pad from running until it's finished hooking + * everything up, but playbin needs refactoring first. */ + if (result == GST_FLOW_NOT_LINKED) { + GST_DEBUG_OBJECT (sel, + "No peer pad yet - performing fallback allocation for pad %s:%s", + GST_DEBUG_PAD_NAME (pad)); + + *buf = NULL; + result = GST_FLOW_OK; + } + } + + gst_object_unref (sel); + + return result; +} + +static GstFlowReturn +gst_selector_pad_chain (GstPad * pad, GstBuffer * buf) +{ + RsnStreamSelector *sel; + GstFlowReturn res; + GstPad *active_sinkpad; + RsnSelectorPad *selpad; + GstClockTime timestamp; + GstSegment *seg; + + sel = RSN_STREAM_SELECTOR (gst_pad_get_parent (pad)); + selpad = GST_SELECTOR_PAD_CAST (pad); + seg = &selpad->segment; + + active_sinkpad = rsn_stream_selector_activate_sinkpad (sel, pad); + + timestamp = GST_BUFFER_TIMESTAMP (buf); + if (GST_CLOCK_TIME_IS_VALID (timestamp)) { + GST_DEBUG_OBJECT (sel, "received timestamp %" GST_TIME_FORMAT, + GST_TIME_ARGS (timestamp)); + gst_segment_set_last_stop (seg, seg->format, timestamp); + } + + /* Ignore buffers from pads except the selected one */ + if (pad != active_sinkpad) + goto ignore; + + /* if we have a pending segment, push it out now */ + if (selpad->segment_pending) { + gst_pad_push_event (sel->srcpad, gst_event_new_new_segment_full (FALSE, + seg->rate, seg->applied_rate, seg->format, seg->start, seg->stop, + seg->time)); + + selpad->segment_pending = FALSE; + } + + /* forward */ + GST_DEBUG_OBJECT (sel, "Forwarding buffer %p from pad %s:%s", buf, + GST_DEBUG_PAD_NAME (pad)); + res = gst_pad_push (sel->srcpad, buf); +done: + gst_object_unref (sel); + return res; + /* dropped buffers */ +ignore: + { + GST_DEBUG_OBJECT (sel, "Ignoring buffer %p from pad %s:%s", + buf, GST_DEBUG_PAD_NAME (pad)); + gst_buffer_unref (buf); + res = GST_FLOW_NOT_LINKED; + goto done; + } +} + +static void rsn_stream_selector_dispose (GObject * object); +static void rsn_stream_selector_finalize (GObject * object); + +static void rsn_stream_selector_init (RsnStreamSelector * sel); +static void rsn_stream_selector_base_init (RsnStreamSelectorClass * klass); +static void rsn_stream_selector_class_init (RsnStreamSelectorClass * klass); + +static void rsn_stream_selector_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void rsn_stream_selector_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +static GstPad *rsn_stream_selector_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * unused); +static void rsn_stream_selector_release_pad (GstElement * element, + GstPad * pad); +static GList *rsn_stream_selector_get_linked_pads (GstPad * pad); +static GstCaps *rsn_stream_selector_getcaps (GstPad * pad); + +static GstElementClass *parent_class = NULL; + +GType +rsn_stream_selector_get_type (void) +{ + static GType stream_selector_type = 0; + + if (!stream_selector_type) { + static const GTypeInfo stream_selector_info = { + sizeof (RsnStreamSelectorClass), + (GBaseInitFunc) rsn_stream_selector_base_init, + NULL, + (GClassInitFunc) rsn_stream_selector_class_init, + NULL, + NULL, + sizeof (RsnStreamSelector), + 0, + (GInstanceInitFunc) rsn_stream_selector_init, + }; + stream_selector_type = + g_type_register_static (GST_TYPE_ELEMENT, + "RsnStreamSelector", &stream_selector_info, 0); + GST_DEBUG_CATEGORY_INIT (stream_selector_debug, + "streamselector", 0, "A stream-selector element"); + } + + return stream_selector_type; +} + +static void +rsn_stream_selector_base_init (RsnStreamSelectorClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_set_details (element_class, &rsn_stream_selector_details); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&rsn_stream_selector_sink_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&rsn_stream_selector_src_factory)); +} + +static void +rsn_stream_selector_class_init (RsnStreamSelectorClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->dispose = rsn_stream_selector_dispose; + gobject_class->finalize = rsn_stream_selector_finalize; + + gobject_class->set_property = + GST_DEBUG_FUNCPTR (rsn_stream_selector_set_property); + gobject_class->get_property = + GST_DEBUG_FUNCPTR (rsn_stream_selector_get_property); + + g_object_class_install_property (gobject_class, PROP_N_PADS, + g_param_spec_uint ("n-pads", "Number of Pads", + "The number of sink pads", 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_ACTIVE_PAD, + g_param_spec_object ("active-pad", "Active Pad", + "The currently active sink pad", GST_TYPE_PAD, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gstelement_class->request_new_pad = rsn_stream_selector_request_new_pad; + gstelement_class->release_pad = rsn_stream_selector_release_pad; +} + +static void +rsn_stream_selector_init (RsnStreamSelector * sel) +{ + sel->srcpad = gst_pad_new ("src", GST_PAD_SRC); + gst_pad_set_internal_link_function (sel->srcpad, + GST_DEBUG_FUNCPTR (rsn_stream_selector_get_linked_pads)); + gst_pad_set_getcaps_function (sel->srcpad, + GST_DEBUG_FUNCPTR (rsn_stream_selector_getcaps)); + gst_element_add_pad (GST_ELEMENT (sel), sel->srcpad); + /* sinkpad management */ + sel->padcount = 0; + sel->active_sinkpad = NULL; + gst_segment_init (&sel->segment, GST_FORMAT_UNDEFINED); +} + +static void +rsn_stream_selector_dispose (GObject * object) +{ + RsnStreamSelector *sel = RSN_STREAM_SELECTOR (object); + + if (sel->active_sinkpad) { + gst_object_unref (sel->active_sinkpad); + sel->active_sinkpad = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +rsn_stream_selector_finalize (GObject * object) +{ + RsnStreamSelector *sel; + + sel = RSN_STREAM_SELECTOR (object); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +rsn_stream_selector_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + RsnStreamSelector *sel = RSN_STREAM_SELECTOR (object); + + switch (prop_id) { + case PROP_ACTIVE_PAD: + { + GstPad *pad = NULL; + GstPad **active_pad_p; + + pad = g_value_get_object (value); + + GST_OBJECT_LOCK (object); + if (pad != sel->active_sinkpad) { + RsnSelectorPad *selpad; + + selpad = GST_SELECTOR_PAD_CAST (pad); + /* we can only activate pads that have data received */ + if (selpad && !selpad->active) { + GST_DEBUG_OBJECT (sel, "No data received on pad %" GST_PTR_FORMAT, + pad); + } else { + active_pad_p = &sel->active_sinkpad; + gst_object_replace ((GstObject **) active_pad_p, + GST_OBJECT_CAST (pad)); + GST_DEBUG_OBJECT (sel, "New active pad is %" GST_PTR_FORMAT, + sel->active_sinkpad); + } + } + GST_OBJECT_UNLOCK (object); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +rsn_stream_selector_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + RsnStreamSelector *sel = RSN_STREAM_SELECTOR (object); + + switch (prop_id) { + case PROP_N_PADS: + GST_OBJECT_LOCK (object); + g_value_set_uint (value, sel->n_pads); + GST_OBJECT_UNLOCK (object); + break; + case PROP_ACTIVE_PAD:{ + GST_OBJECT_LOCK (object); + g_value_set_object (value, sel->active_sinkpad); + GST_OBJECT_UNLOCK (object); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstPad * +rsn_stream_selector_get_linked_pad (GstPad * pad, gboolean strict) +{ + RsnStreamSelector *sel; + GstPad *otherpad = NULL; + + sel = RSN_STREAM_SELECTOR (gst_pad_get_parent (pad)); + + GST_OBJECT_LOCK (sel); + if (pad == sel->srcpad) + otherpad = sel->active_sinkpad; + else if (pad == sel->active_sinkpad || !strict) + otherpad = sel->srcpad; + if (otherpad) + gst_object_ref (otherpad); + GST_OBJECT_UNLOCK (sel); + gst_object_unref (sel); + return otherpad; +} + +static GstCaps * +rsn_stream_selector_getcaps (GstPad * pad) +{ + GstPad *otherpad; + GstObject *parent; + GstCaps *caps; + + otherpad = rsn_stream_selector_get_linked_pad (pad, FALSE); + parent = gst_object_get_parent (GST_OBJECT (pad)); + if (!otherpad) { + GST_DEBUG_OBJECT (parent, + "Pad %s:%s not linked, returning ANY", GST_DEBUG_PAD_NAME (pad)); + caps = gst_caps_new_any (); + } else { + GST_DEBUG_OBJECT (parent, + "Pad %s:%s is linked (to %s:%s), returning peer caps", + GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (otherpad)); + /* if the peer has caps, use those. If the pad is not linked, this function + * returns NULL and we return ANY */ + if (!(caps = gst_pad_peer_get_caps (otherpad))) + caps = gst_caps_new_any (); + gst_object_unref (otherpad); + } + + gst_object_unref (parent); + return caps; +} + +/* check if the pad is the active sinkpad */ +static gboolean +rsn_stream_selector_is_active_sinkpad (RsnStreamSelector * sel, GstPad * pad) +{ + RsnSelectorPad *selpad; + gboolean res; + + selpad = GST_SELECTOR_PAD_CAST (pad); + + GST_OBJECT_LOCK (sel); + res = (pad == sel->active_sinkpad); + GST_OBJECT_UNLOCK (sel); + + return res; +} + +/* Get or create the active sinkpad */ +static GstPad * +rsn_stream_selector_activate_sinkpad (RsnStreamSelector * sel, GstPad * pad) +{ + GstPad *active_sinkpad; + RsnSelectorPad *selpad; + + selpad = GST_SELECTOR_PAD_CAST (pad); + + GST_OBJECT_LOCK (sel); + selpad->active = TRUE; + active_sinkpad = sel->active_sinkpad; + if (active_sinkpad == NULL) { + /* first pad we get an alloc on becomes the activated pad by default */ + active_sinkpad = sel->active_sinkpad = gst_object_ref (pad); + GST_DEBUG_OBJECT (sel, "Activating pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + } + GST_OBJECT_UNLOCK (sel); + + return active_sinkpad; +} + +static GList * +rsn_stream_selector_get_linked_pads (GstPad * pad) +{ + GstPad *otherpad; + + otherpad = rsn_stream_selector_get_linked_pad (pad, TRUE); + if (!otherpad) + return NULL; + /* need to drop the ref, internal linked pads is not MT safe */ + gst_object_unref (otherpad); + return g_list_append (NULL, otherpad); +} + +static GstPad * +rsn_stream_selector_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * unused) +{ + RsnStreamSelector *sel; + gchar *name = NULL; + GstPad *sinkpad = NULL; + + sel = RSN_STREAM_SELECTOR (element); + g_return_val_if_fail (templ->direction == GST_PAD_SINK, NULL); + GST_LOG_OBJECT (sel, "Creating new pad %d", sel->padcount); + GST_OBJECT_LOCK (sel); + name = g_strdup_printf ("sink%d", sel->padcount++); + sinkpad = g_object_new (RSN_TYPE_SELECTOR_PAD, + "name", name, "direction", templ->direction, "template", templ, NULL); + g_free (name); + sel->n_pads++; + GST_OBJECT_UNLOCK (sel); + + gst_pad_set_event_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_selector_pad_event)); + gst_pad_set_getcaps_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_selector_pad_getcaps)); + gst_pad_set_chain_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_selector_pad_chain)); + gst_pad_set_internal_link_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_selector_pad_get_linked_pads)); + gst_pad_set_bufferalloc_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_selector_pad_bufferalloc)); + + gst_pad_set_active (sinkpad, TRUE); + gst_element_add_pad (GST_ELEMENT (sel), sinkpad); + return sinkpad; +} + +static void +rsn_stream_selector_release_pad (GstElement * element, GstPad * pad) +{ + RsnStreamSelector *sel; + + sel = RSN_STREAM_SELECTOR (element); + GST_LOG_OBJECT (sel, "Releasing pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + + GST_OBJECT_LOCK (sel); + /* if the pad was the active pad, makes us select a new one */ + if (sel->active_sinkpad == pad) { + GST_DEBUG_OBJECT (sel, "Deactivating pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + sel->active_sinkpad = NULL; + } + sel->n_pads--; + GST_OBJECT_UNLOCK (sel); + + gst_pad_set_active (pad, FALSE); + gst_element_remove_pad (GST_ELEMENT (sel), pad); +} diff --git a/ext/resindvd/rsnstreamselector.h b/ext/resindvd/rsnstreamselector.h new file mode 100644 index 00000000..aa51699b --- /dev/null +++ b/ext/resindvd/rsnstreamselector.h @@ -0,0 +1,62 @@ +/* GStreamer + * Copyright (C) 2003 Julien Moutte <julien@moutte.net> + * Copyright (C) 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __RSN_STREAM_SELECTOR_H__ +#define __RSN_STREAM_SELECTOR_H__ + +#include <gst/gst.h> + +G_BEGIN_DECLS + +#define RSN_TYPE_STREAM_SELECTOR \ + (rsn_stream_selector_get_type()) +#define RSN_STREAM_SELECTOR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), RSN_TYPE_STREAM_SELECTOR, RsnStreamSelector)) +#define RSN_STREAM_SELECTOR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), RSN_TYPE_STREAM_SELECTOR, RsnStreamSelectorClass)) +#define RSN_IS_STREAM_SELECTOR(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), RSN_TYPE_STREAM_SELECTOR)) +#define RSN_IS_STREAM_SELECTOR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), RSN_TYPE_STREAM_SELECTOR)) + +typedef struct _RsnStreamSelector RsnStreamSelector; +typedef struct _RsnStreamSelectorClass RsnStreamSelectorClass; + +struct _RsnStreamSelector { + GstElement element; + + GstPad *srcpad; + + GstPad *active_sinkpad; + guint n_pads; + guint padcount; + + GstSegment segment; +}; + +struct _RsnStreamSelectorClass { + GstElementClass parent_class; +}; + +GType rsn_stream_selector_get_type (void); + +G_END_DECLS + +#endif /* __RSN_STREAM_SELECTOR_H__ */ diff --git a/gst/dvdspu/gstdvdspu.c b/gst/dvdspu/gstdvdspu.c index c16743bd..680d9101 100644 --- a/gst/dvdspu/gstdvdspu.c +++ b/gst/dvdspu/gstdvdspu.c @@ -395,12 +395,22 @@ gst_dvd_spu_video_event (GstPad * pad, GstEvent * event) const GstStructure *structure = gst_event_get_structure (event); const char *event_type; + if (structure == NULL) { + res = gst_pad_event_default (pad, event); + break; + } + if (!gst_structure_has_name (structure, "application/x-gst-dvd")) { res = gst_pad_event_default (pad, event); break; } event_type = gst_structure_get_string (structure, "event"); + if (event_type == NULL) { + res = gst_pad_event_default (pad, event); + break; + } + GST_DEBUG_OBJECT (dvdspu, "DVD event of type %s on video pad", event_type); |